1 // Copyright Oryx Mail Systems GmbH. All enquiries to info@oryx.com, please.
7 // write, getpid, getdtablesize, close, dup, getuid, geteuid, chroot,
8 // chdir, setregid, setreuid, fork
20 #include <sys/types.h>
23 // sigaction, sigemptyset
26 // our own includes, _after_ the system header files. lots of system
27 // header files break if we've already defined UINT_MAX, etc.
35 #include "logclient.h"
36 #include "eventloop.h"
37 #include "connection.h"
38 #include "configuration.h"
39 #include "eventloop.h"
40 #include "allocator.h"
50 ServerData( const char * n )
51 : name( n ), stage( Server::Configuration ),
52 secured( false ), fork( false ), useCache( USECACHE ),
53 chrootMode( Server::JailDir ),
54 queries( new List< Query > ),
65 Server::ChrootMode chrootMode;
66 List< Query > *queries;
67 List<pid_t> * children;
72 ServerData * Server::d;
75 /*! \class Server server.h
77 The Server class performs the server startup functions that are
78 common to most/all Archiveopteryx servers. The functions are
79 performed in a fixed order - you call setup( x ) to continue up to
84 /*! Constructs a Server for \a name. \a name will be used for the pid
85 file, etc. \a argc and \a argv are parsed to find command-line
89 Server::Server( const char * name, int argc, char * argv[] )
91 d = new ServerData( name );
92 Allocator::addEternal( d, "Server data" );
96 while ( (c=getopt( argc, argv, "fc:C" )) != -1 ) {
100 fprintf( stderr, "%s: -f specified twice\n", name );
106 if ( !d->configFile.isEmpty() ) {
107 fprintf( stderr, "%s: -c specified twice\n", name );
111 d->configFile = optarg;
112 File tmp( d->configFile );
113 if ( !tmp.valid() ) {
115 "%s: Config file %s not accessible/readable\n",
116 name, tmp.name().cstr() );
122 // -C is undocumented on purpose. it should not change
123 // anything except performance, and exists only for
125 d->useCache = !d->useCache;
133 if ( argc > optind ) {
134 fprintf( stderr, "%s: Parse error for argument %d (%s)\n",
135 name, optind, argv[optind] );
138 if ( uc || !d->useCache )
139 fprintf( stdout, "%s: Will%s use caches\n",
140 name, d->useCache ? "" : " not" );
144 /*! Notifies the Server that it is to chroot according to \a mode. If
145 \a mode is JailDir, secure() will chroot into the jail directory
146 and check that '/' is inaccesssible. If \a mode is LogDir,
147 secure() will chroot into the logfile directory, where the server
148 hopefully can access the logfile.
151 void Server::setChrootMode( ChrootMode mode )
153 d->chrootMode = mode;
157 /*! Performs server setup for each stage up to but NOT including \a s. */
159 void Server::setup( Stage s )
162 while ( d->stage < s ) {
163 switch ( d->stage ) {
180 // This just gives us a good place to stop in main.
197 d->stage = (Stage)(d->stage + 1);
199 } catch ( Exception e ) {
200 // don't allocate memory or call anything here.
204 c = "Invariant failed during server startup.";
207 c = "Segfault detected during server startup.";
210 c = "Out of memory during server startup.";
213 c = "FD error during server startup.";
220 ::write( 2, "\n", 1 );
226 /*! Reads server configuration, either from the default config file or
227 from the one supplied in argc.
230 void Server::configuration()
232 if ( d->configFile.isEmpty() )
233 Configuration::setup( "archiveopteryx.conf" );
235 Configuration::setup( d->configFile );
236 if ( d->useCache && !Configuration::scalar( Configuration::MemoryLimit ) )
241 /*! Resolves any domain names used in the configuration file before we
245 void Server::nameResolution()
247 List<Configuration::Text>::Iterator i( Configuration::addressVariables() );
249 const EStringList & r
250 = Resolver::resolve( Configuration::text( *i ) );
252 log( EString("Unable to resolve ") +
253 Configuration::name( *i ) +
254 " = " + Configuration::text( *i ),
259 if ( !Log::disastersYet() )
262 EStringList::Iterator e( Resolver::errors() );
270 /*! Closes all files except stdout and stderr. Attaches stdin to
271 /dev/null in case something uses it. stderr is kept open so
272 that we can tell our daddy about any disasters.
277 int s = getdtablesize();
280 if ( s != 2 && s != 1 )
283 s = open( "/dev/null", O_RDWR );
289 /*! Creates the global logging context, and sets up a LogClient if no
290 Logger has been created already.
292 This also creates the Loop object, so that the LogClient doesn't
293 feel alone in the world, abandoned by its parents, depressed and
297 void Server::logSetup()
300 if ( !Logger::global() )
301 LogClient::setup( d->name );
302 Scope::current()->setLog( new Log );
303 log( name() + ", Archiveopteryx version " +
304 Configuration::compiledIn( Configuration::Version ) );
305 Allocator::setReporting( true );
309 static void shutdownLoop( int )
311 Server::killChildren();
312 if ( EventLoop::global() )
313 EventLoop::global()->stop( 2 );
317 static void closeGuiltyConnection( int )
323 static void dumpCoreAndGoOn( int )
328 // we're now a child process. we can dump core and the real server
331 // do we need to do anything about the files? no? I think not.
337 /*! Called by signal handling to kill any children started in fork(). */
339 void Server::killChildren()
341 List<pid_t>::Iterator child( d->children );
343 ::kill( *child, SIGTERM );
349 /*! Initializes the global event loop. */
355 sa.sa_sigaction = 0; // may be union with sa_handler above
356 sigemptyset( &sa.sa_mask ); // we block no other signals
357 sa.sa_flags = 0; // in particular, we don't want SA_RESETHAND
359 // we cannot reread files, so we ignore sighup
360 sa.sa_handler = SIG_IGN;
361 ::sigaction( SIGHUP, &sa, 0 );
363 // sigint and sigterm both should stop the server
364 sa.sa_handler = shutdownLoop;
365 ::sigaction( SIGINT, &sa, 0 );
366 ::sigaction( SIGTERM, &sa, 0 );
368 // sigpipe happens if we're writing to an already-closed fd. we'll
369 // discover that it's closed a little later.
370 sa.sa_handler = SIG_IGN;
371 ::sigaction( SIGPIPE, &sa, 0 );
373 // if we dereference a null pointer, we usually can close some fd
375 sa.sa_handler = closeGuiltyConnection;
376 ::sigaction( SIGSEGV, &sa, 0 );
378 // a custom signal to dump core and go on
379 sa.sa_handler = dumpCoreAndGoOn;
380 ::sigaction( SIGUSR1, &sa, 0 );
384 /*! Forks the server as required by -f and the configuration variable
387 If -f is specified, the parent exits in this function and does not
388 return from this function.
390 As many processes as specified by server-processes return.
400 log( "Unable to fork. Error code " + fn( errno ),
403 } else if ( p > 0 ) {
406 d->mainProcess = true;
408 if ( d->name == "archiveopteryx" ) {
409 children = Configuration::scalar( Configuration::ServerProcesses );
411 d->children = new List<pid_t>;
414 while ( d->children && i < children ) {
415 pid_t * child = new pid_t;
418 log( "Unable to fork server; pressing on. Error code " +
419 fn( errno ), Log::Error );
421 else if ( *child > 0 ) {
422 log( "Process " + fn( getpid() ) + " forked " + fn( *child ) );
423 d->children->append( child );
426 d->mainProcess = false;
428 EventLoop::global()->closeAllExceptListeners();
429 log( "Process " + fn( getpid() ) + " started" );
430 if ( Configuration::toggle( Configuration::UseStatistics ) ) {
432 = Configuration::scalar( Configuration::StatisticsPort );
433 log( "Using port " + fn( port + i ) +
434 " for statistics queries" );
435 Configuration::add( "statistics-port = " + fn( port + i ) );
443 /*! Writes the server's pid to an almost hardcoded pidfile. We don't
444 lock the file, since most of these servers don't have a problem
445 with multiple instances of themselves. The pidfile is just a
446 convenience for tools like start-stop-daemon.
449 void Server::pidFile()
451 if ( !d->mainProcess )
454 EString dir( Configuration::compiledIn( Configuration::PidFileDir ) );
456 EString n = dir + "/" + d->name + ".pid";
457 File f( n, File::Write );
459 f.write( fn( getpid() ) + "\n" );
461 log( "Unable to write to PID file " + n );
465 /*! Logs the startup details. By this time, the logger must be in
469 void Server::logStartup()
471 log( "Starting server " + d->name +
472 " (host " + Configuration::hostname() + ")" +
473 " (pid " + fn( getpid() ) + ") " +
474 EString( d->secured ? "securely" : "insecurely" ) );
478 /*! Loses all rights. Dies with an error if that isn't possible, or if
482 void Server::secure()
484 if ( Configuration::present( Configuration::DbOwnerPassword ) ) {
485 log( "db-owner-password specified in archiveopteryx.conf "
486 "(should be in aoxsuper.conf)",
490 bool security = Configuration::toggle( Configuration::Security );
492 if ( getuid() == 0 || geteuid() == 0 )
493 log( "Warning: Starting " + d->name + " insecurely as root" );
498 EString user( Configuration::text( Configuration::JailUser ) );
499 struct passwd * pw = getpwnam( user.cstr() );
501 log( "Cannot secure server " + d->name +
502 " since " + user + " is not a valid login (says getpwnam())",
506 if ( pw->pw_uid == 0 ) {
507 log( "Cannot secure server " + d->name + " since " + user +
513 EString group( Configuration::text( Configuration::JailGroup ) );
514 struct group * gr = getgrnam( group.cstr() );
516 log( "Cannot secure server " + d->name +
517 " since " + group + " is not a valid group (says getgrnam())",
522 EString cfn( d->configFile );
524 cfn = Configuration::configFile();
527 if ( stat( cfn.cstr(), &st ) < 0 ) {
528 log( "Cannot stat configuration file " + cfn,
532 if ( st.st_uid != pw->pw_uid ) {
533 log( "Configuration file " + cfn +
534 " must be owned by " + user +
535 " (uid " + fn( pw->pw_uid ) + ")" +
536 " (is owned by uid " +
537 fn( st.st_uid ) + ")",
541 if ( (gid_t)st.st_gid != (gid_t)gr->gr_gid ) {
542 log( "Configuration file " + cfn +
543 " must be in group " + user +
544 " (gid " + fn( gr->gr_gid ) + ")" +
546 fn( st.st_gid ) + ")",
550 if ( (st.st_mode & 027) != 0 ) {
551 log( "Configuration file " + cfn +
552 " must be readable for user " + user + "/group " + group +
554 fn( st.st_mode & 0777, 8 ) + ", should be " +
555 fn( st.st_mode & 0740, 8 ) + ")",
561 switch ( d->chrootMode ) {
563 root = Configuration::text( Configuration::MessageCopyDir );
566 root = Configuration::text( Configuration::JailDir );
569 root = Configuration::compiledIn( Configuration::LibDir );
570 if ( !root.endsWith( "/" ) )
572 root.append( "tlsproxy" );
575 root = Configuration::text( Configuration::LogFile );
577 root = Configuration::text( Configuration::JailDir );
579 else if ( root.startsWith( "syslog/" ) ) {
583 uint i = root.length();
584 while ( i > 0 && root[i] != '/' )
587 log( "Cannot secure server " + d->name +
588 " since logfile does not contain '/'",
590 log( "Value of logfile: " + root, Log::Info );
597 if ( chroot( root.cstr() ) ) {
598 log( "Cannot secure server " + d->name + " since chroot( \"" +
599 root + "\" ) failed with error " + fn( errno ),
603 if ( chdir( "/" ) ) {
604 log( "Cannot secure server " + d->name + " since chdir( \"/\" ) "
605 "failed in jail directory (\"" + root + "\") with error " +
610 File::setRoot( root );
612 if ( setregid( gr->gr_gid, gr->gr_gid ) ) {
613 log( "Cannot secure server " + d->name + " since setregid( " +
614 fn( gr->gr_gid ) + ", " + fn( gr->gr_gid ) + " ) "
615 "failed with error " + fn( errno ),
620 if ( setgroups( 1, (gid_t*)&(gr->gr_gid) ) ) {
621 log( "Cannot secure server " + d->name + " since setgroups( 1, [" +
622 fn( gr->gr_gid ) + "] ) failed with error " + fn( errno ),
627 if ( setreuid( pw->pw_uid, pw->pw_uid ) ) {
628 log( "Cannot secure server " + d->name + " since setreuid( " +
629 fn( pw->pw_uid ) + ", " + fn( pw->pw_uid ) + " ) "
630 "failed with error " + fn( errno ),
635 if ( d->chrootMode == JailDir ) {
636 // check that the jail directory really is a jail
637 DIR * slash = opendir( "/" ); // checks 'x' access
638 int fd = open( "/does/not/exist", O_RDONLY ); // checks 'r'
639 if ( slash || fd >= 0 || errno != EACCES ) {
640 log( "Cannot secure server " + d->name +
641 " since jail directory " + root +
642 " is accessible to user " + user,
650 // one final check...
651 if ( geteuid() != pw->pw_uid || getuid() != pw->pw_uid ) {
652 log( "Cannot secure server " + d->name +
653 " since setreuid() failed. Desired uid " +
654 fn( pw->pw_uid ) + ", got uid " + fn( getuid() ) +
655 " and euid " + fn( geteuid() ),
661 log( "Secured server " + d->name + " using jail directory " + root +
662 ", uid " + fn( pw->pw_uid ) + ", gid " + fn( gr->gr_gid ) );
667 /*! Finishes setup and runs the main loop of the server. */
672 Configuration::report();
675 List< Connection >::Iterator it( EventLoop::global()->connections() );
677 if ( it->type() == Connection::Listener )
682 if ( listeners == 0 ) {
683 log( "No active listeners. " + d->name + " exiting.", Log::Disaster );
687 if ( Scope::current()->log()->disastersYet() ) {
688 log( "Aborting server " + d->name + " due to earlier problems." );
695 EventLoop::global()->start();
697 if ( Scope::current()->log()->disastersYet() )
703 /*! This static function returns the name of the application.
704 Is server the right way to publicise this name?
707 EString Server::name()
715 /*! Returns true if this server is configured to cache this and that,
716 and false if it shouldn't cache.
718 Running without cache is a debugging aid.
722 bool Server::useCache()