1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "servers.h"
4
5 #include "file.h"
6 #include "dict.h"
7 #include "timer.h"
8 #include "query.h"
9 #include "paths.h"
10 #include "buffer.h"
11 #include "endpoint.h"
12 #include "database.h"
13 #include "resolver.h"
14 #include "eventloop.h"
15 #include "connection.h"
16 #include "configuration.h"
17
18 #include <errno.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <sys/types.h>
23 #include <sys/wait.h>
24 #include <sys/stat.h>
25 #include <sys/socket.h>
26 #include <signal.h>
27 #include <pwd.h>
28 #include <grp.h>
29
30
31 static const char * buildinfo[] = {
32 #include "buildinfo.inc"
33 ""
34 };
35
36
37 static const char * servers[] = {
38 "logd",
39 "archiveopteryx"
40 };
41 static const int nservers = sizeof( servers ) / sizeof( servers[0] );
42
43
error(const EString & s)44 static void error( const EString & s )
45 {
46 fprintf( stderr, "%s\n", s.cstr() );
47 exit( -1 );
48 }
49
50
pidFile(const char * s)51 static EString pidFile( const char * s )
52 {
53 EString pf( Configuration::compiledIn( Configuration::PidFileDir ) );
54 pf.append( "/" );
55 pf.append( s );
56 pf.append( ".pid" );
57 return pf;
58 }
59
60
serverPid(const char * s)61 static int serverPid( const char * s )
62 {
63 EString pf = pidFile( s );
64 File f( pf, File::Read );
65 if ( !f.valid() )
66 return -1;
67
68 bool ok;
69 int pid = f.contents().stripCRLF().number( &ok );
70 if ( !ok ) {
71 fprintf( stderr, "aox: Bad pid file: %s\n", pf.cstr() );
72 return -1;
73 }
74
75 return pid;
76 }
77
78
79 class Path
80 : public Garbage
81 {
82 public:
83 enum Type {
84 ReadableFile,
85 ReadableDir,
86 WritableFile,
87 WritableDir,
88 CreatableFile,
89 CreatableSocket,
90 ExistingSocket,
91 JailDir
92 };
93
94 Path( const EString &, Type );
95 bool checked;
96 bool ok;
97 void check();
98 static bool allOk;
99
100 Path * parent;
101 const char * message;
102 EStringList variables;
103 EString name;
104 Type type;
105
106 static uint uid;
107 static uint gid;
108 };
109
110
111 uint Path::uid;
112 uint Path::gid;
113 bool Path::allOk;
114 static Dict<Path> paths;
115
116
addPath(Path::Type type,Configuration::Text variable)117 static void addPath( Path::Type type,
118 Configuration::Text variable )
119 {
120 EString name = Configuration::text( variable );
121 Path * p = paths.find( name );
122 if ( name.startsWith( "/" ) ) {
123 if ( !p ) {
124 p = new Path( name, type );
125 paths.insert( name, p );
126 }
127 }
128
129 while ( p ) {
130 if ( p->type != type )
131 // this isn't 100% good enough, is it... let's write the
132 // huge code to produce the right message if it ever bites
133 // anyone.
134 p->message = "has conflicting permission requirements";
135 p->variables.append( Configuration::name( variable ) );
136 p = p->parent;
137 }
138 }
139
140
addPath(Path::Type type,Configuration::CompileTimeSetting variable)141 static void addPath( Path::Type type,
142 Configuration::CompileTimeSetting variable )
143 {
144 EString name = Configuration::compiledIn( variable );
145 Path * p = paths.find( name );
146 if ( name.startsWith( "/" ) ) {
147 if ( !p ) {
148 p = new Path( name, type );
149 paths.insert( name, p );
150 }
151 }
152 }
153
154
parentOf(const EString & name)155 static EString parentOf( const EString & name )
156 {
157 uint i = name.length();
158 while ( i > 0 && name[i] != '/' )
159 i--;
160 EString pn = name.mid( 0, i );
161 if ( i == 0 )
162 pn = "/";
163 return pn;
164 }
165
166
Path(const EString & s,Type t)167 Path::Path( const EString & s, Type t )
168 : Garbage(),
169 checked( false ), ok( true ),
170 parent( 0 ), message( 0 ),
171 name( s ), type( t )
172 {
173 EString pn = parentOf( name );
174 if ( pn.length() < name.length() &&
175 pn != Configuration::text( Configuration::JailDir ) )
176 {
177 Path * p = paths.find( pn );
178 if ( !p ) {
179 if ( t == CreatableFile ||
180 t == WritableFile ||
181 t == CreatableSocket )
182 p = new Path( pn, WritableDir );
183 else
184 p = new Path( pn, ReadableDir );
185 paths.insert( pn, p );
186 }
187 }
188 }
189
190
check()191 void Path::check()
192 {
193 if ( checked )
194 return;
195 if ( parent && !parent->checked )
196 parent->check();
197
198 checked = true;
199 if ( parent && !parent->ok ) {
200 ok = false;
201 return;
202 }
203
204 struct stat st;
205 uint rights = 0;
206 bool isdir = false;
207 bool isfile = false;
208 const char * message = 0;
209 bool exist = false;
210 if ( stat( name.cstr(), &st ) >= 0 ) {
211 exist = true;
212 if ( st.st_uid == uid )
213 rights = st.st_mode >> 6;
214 else if ( st.st_gid == gid )
215 rights = st.st_mode >> 3;
216 else
217 rights = st.st_mode;
218 rights &= 7;
219 isdir = S_ISDIR(st.st_mode);
220 isfile = S_ISREG(st.st_mode);
221 }
222
223 switch( type ) {
224 case ReadableFile:
225 if ( !exist )
226 message = "does not exist";
227 else if ( !isfile )
228 message = "is not a normal file";
229 else if ( (rights & 4) != 4 )
230 message = "is not readable";
231 break;
232 case ReadableDir:
233 if ( !exist )
234 message = "does not exist";
235 else if ( !isdir )
236 message = "is not a directory";
237 else if ( (rights & 5) != 5 )
238 message = "is not readable and searchable";
239 break;
240 case WritableFile:
241 if ( exist && !isfile )
242 message = "is not a normal file";
243 else if ( (rights & 2) != 2 )
244 message = "is not writable";
245 break;
246 case WritableDir:
247 if ( !exist )
248 message = "does not exist";
249 else if ( !isdir )
250 message = "is not a directory";
251 else if ( (rights & 3) != 3 )
252 message = "is not writable and searchable";
253 break;
254 case CreatableFile:
255 if ( exist && !isfile )
256 message = "is not a normal file";
257 break;
258 case CreatableSocket:
259 if ( exist &&
260 !S_ISSOCK( st.st_mode ) &&
261 !S_ISFIFO( st.st_mode ) )
262 message = "is not a socket or FIFO";
263 break;
264 case ExistingSocket:
265 if ( !exist ||
266 !(S_ISSOCK( st.st_mode ) ||
267 S_ISFIFO( st.st_mode ) ||
268 st.st_mode & S_IFCHR ) )
269 message = "is not a socket/FIFO";
270 break;
271 case JailDir:
272 if ( !isdir )
273 message = "is not a directory";
274 if ( rights == 0 )
275 message = "does not have the required 001 (o+x) permissions";
276 else if ( rights != 1 )
277 message = "has more than the required 001 (o+x) permissions";
278 break;
279 }
280
281 if ( !message )
282 return;
283 fprintf( stderr, "%s %s.\n", name.cstr(), message );
284 variables.removeDuplicates();
285 EStringList::Iterator i( variables );
286 while ( i ) {
287 fprintf( stderr, " - affected variable: %s\n", i->cstr() );
288 ++i;
289 }
290 ok = false;
291 allOk = false;
292 }
293
294
checkFilePermissions()295 static void checkFilePermissions()
296 {
297 EString user( Configuration::text( Configuration::JailUser ) );
298 struct passwd * pw = getpwnam( user.cstr() );
299 if ( !pw )
300 error( user + " (jail-user) is not a valid username." );
301 if ( pw->pw_uid == 0 )
302 error( user + " (jail-user) has UID 0." );
303
304 EString group( Configuration::text( Configuration::JailGroup ) );
305 struct group * gr = getgrnam( group.cstr() );
306 if ( !gr )
307 error( group + " (jail-group) is not a valid group." );
308
309 Path::uid = pw->pw_uid;
310 Path::gid = gr->gr_gid;
311 Path::allOk = true;
312
313 if ( Configuration::text( Configuration::MessageCopy ).lower() != "none" )
314 addPath( Path::WritableDir, Configuration::MessageCopyDir );
315 addPath( Path::JailDir, Configuration::JailDir );
316 if ( Configuration::toggle( Configuration::UseTls ) ) {
317 EString c = Configuration::text( Configuration::TlsCertFile );
318 if ( c.isEmpty() ) {
319 c = Configuration::compiledIn( Configuration::LibDir );
320 c.append( "/automatic-key.pem" );
321 }
322 addPath( Path::ReadableFile, Configuration::TlsCertFile );
323 }
324 addPath( Path::ExistingSocket, Configuration::EntropySource );
325 EString lf = Configuration::text( Configuration::LogFile );
326 if ( lf != "-" && !lf.startsWith( "syslog/" ) )
327 addPath( Path::CreatableFile, Configuration::LogFile );
328 addPath( Path::ReadableDir, Configuration::BinDir );
329 addPath( Path::ReadableDir, Configuration::PidFileDir );
330 addPath( Path::ReadableDir, Configuration::SbinDir );
331 addPath( Path::ReadableDir, Configuration::ManDir );
332 addPath( Path::ReadableDir, Configuration::LibDir );
333 addPath( Path::ReadableDir, Configuration::InitDir );
334
335 List<Configuration::Text>::Iterator
336 it( Configuration::addressVariables() );
337 while ( it ) {
338 EString s( Configuration::text( *it ) );
339 if ( s[0] == '/' &&
340 ( *it == Configuration::DbAddress ||
341 *it == Configuration::SmartHostAddress ) )
342 addPath( Path::ExistingSocket, *it );
343 else if ( s[0] == '/' )
344 addPath( Path::CreatableSocket, *it );
345 ++it;
346 }
347
348 Dict<Path>::Iterator i( paths );
349 while ( i ) {
350 i->check();
351 ++i;
352 }
353
354 if ( !Path::allOk )
355 error( "Checking as user " + user + " (uid " + fn( Path::uid ) +
356 "), group " + group + " (gid " + fn( Path::gid ) + ")" );
357 }
358
359
checkListener(bool use,Configuration::Text address,Configuration::Scalar port,const EString & description)360 static void checkListener( bool use,
361 Configuration::Text address,
362 Configuration::Scalar port,
363 const EString & description )
364 {
365 if ( !use ) {
366 if ( Configuration::present( address ) ||
367 Configuration::present( port ) )
368 fprintf( stderr,
369 "Warning: %s is configured, but disabled",
370 description.cstr() );
371 return;
372 }
373
374 EString a( Configuration::text( address ) );
375 uint p( Configuration::scalar( port ) );
376
377 EStringList addresses;
378 if ( a.isEmpty() ) {
379 if ( Configuration::toggle( Configuration::UseIPv6 ) )
380 addresses.append( "::" );
381 if ( Configuration::toggle( Configuration::UseIPv4 ) )
382 addresses.append( "0.0.0.0" );
383 }
384 else {
385 EStringList::Iterator it( Resolver::resolve( a ) );
386 while ( it ) {
387 addresses.append( *it );
388 ++it;
389 }
390 }
391
392 EStringList::Iterator it( addresses );
393 while ( it ) {
394 Endpoint e( *it, p );
395
396 if ( !e.valid() )
397 error( "Invalid address specified for " +
398 description + " = " + e.string().quoted() );
399
400 if ( e.protocol() == Endpoint::Unix ) {
401 fprintf( stderr,
402 "Warning: Configuring %s to point to a "
403 "Unix socket ('%s') is untested and not "
404 "recommended.\n", description.cstr(),
405 e.string().cstr() );
406 }
407
408 // We bind to that address (and port 0, to make bind assign
409 // a random number) to see if it's a valid local address at
410 // all. I can't see any way to check if we can listen to a
411 // Unix socket here without upsetting anything that may be
412 // listening to it already.
413
414 Endpoint e2( e );
415 e2.zeroPort();
416
417 int s = 0;
418 if ( e2.protocol() != Endpoint::Unix &&
419 ( ( s = Connection::socket( e2.protocol() ) ) < 0 ||
420 bind( s, e2.sockaddr(), e2.sockaddrSize() ) < 0 ) )
421 {
422 error( "Couldn't bind socket for " + description +
423 " = " + e2.string().quoted() );
424 }
425 if ( s > 0 )
426 close( s );
427
428 // I put code to connect to the specified address:port here,
429 // but then took it out again. For one thing, it means that
430 // the Starter needs to call the Checker with some parameter
431 // that is forwarded here (which looks increasingly clumsy),
432 // so that we can decide whether to try the connect or not.
433 // But the other problem is that it isn't useful: if any of
434 // the listen attempts fail, the server will not start; and
435 // in that case, detecting the problem here won't be useful
436 // (in restart, the server is running while we check).
437
438 ++it;
439 }
440 }
441
442
checkInetAddresses()443 static void checkInetAddresses()
444 {
445 checkListener(
446 true,
447 Configuration::LogAddress, Configuration::LogPort,
448 "log-address:port"
449 );
450
451 checkListener(
452 Configuration::toggle( Configuration::UseImap ),
453 Configuration::ImapAddress, Configuration::ImapPort,
454 "imap-address:port"
455 );
456
457 checkListener(
458 Configuration::toggle( Configuration::UseImaps ),
459 Configuration::ImapsAddress, Configuration::ImapsPort,
460 "imaps-address:port"
461 );
462
463 checkListener(
464 Configuration::toggle( Configuration::UsePop ),
465 Configuration::PopAddress, Configuration::PopPort,
466 "pop-address:port"
467 );
468
469 checkListener(
470 Configuration::toggle( Configuration::UseLmtp ),
471 Configuration::LmtpAddress, Configuration::LmtpPort,
472 "lmtp-address:port"
473 );
474
475 checkListener(
476 Configuration::toggle( Configuration::UseSmtp ),
477 Configuration::SmtpAddress, Configuration::SmtpPort,
478 "smtp-address:port"
479 );
480
481 checkListener(
482 Configuration::toggle( Configuration::UseSmtps ),
483 Configuration::SmtpsAddress, Configuration::SmtpsPort,
484 "smtps-address:port"
485 );
486
487 checkListener(
488 Configuration::toggle( Configuration::UseSmtpSubmit ),
489 Configuration::SmtpSubmitAddress, Configuration::SmtpSubmitPort,
490 "smtp-submit-address:port"
491 );
492
493 checkListener(
494 Configuration::toggle( Configuration::UseSieve ),
495 Configuration::ManageSieveAddress,
496 Configuration::ManageSievePort,
497 "managesieve-address:port"
498 );
499 }
500
501
checkMiscellaneous()502 static void checkMiscellaneous()
503 {
504 if ( Configuration::toggle( Configuration::UseSmtp ) ||
505 Configuration::toggle( Configuration::UseLmtp ) )
506 {
507 EString mc( Configuration::text( Configuration::MessageCopy ) );
508 EString mcd( Configuration::text( Configuration::MessageCopyDir ) );
509 if ( mc == "all" || mc == "errors" || mc == "delivered" ) {
510 struct stat st;
511 if ( mcd.isEmpty() )
512 error( "message-copy-directory not set" );
513 else if ( ::stat( mcd.cstr(), &st ) < 0 ||
514 !S_ISDIR( st.st_mode ) )
515 error( "message-copy-directory is not a directory" );
516
517 // We should also check that the directory is writable by
518 // whatever user the server will be running as.
519 }
520 else if ( mc == "none" ) {
521 if ( Configuration::present( Configuration::MessageCopyDir ) )
522 fprintf( stderr, "Note: Disregarding message-copy-directory "
523 "(value %s) because message-copy is set to none\n",
524 mcd.cstr() );
525 }
526 else {
527 error( "Invalid value for message-copy: " + mc );
528 }
529 }
530
531 if ( !Configuration::toggle( Configuration::UseTls ) ) {
532 if ( Configuration::toggle( Configuration::UseImaps ) )
533 error( "use-imaps enabled, but use-tls disabled" );
534 if ( Configuration::toggle( Configuration::UsePops ) )
535 error( "use-pops enabled, but use-tls disabled" );
536 if ( Configuration::toggle( Configuration::UseSmtps ) )
537 error( "use-smtps enabled, but use-tls disabled" );
538 }
539
540 EString sA( Configuration::text( Configuration::SmartHostAddress ) );
541 uint sP( Configuration::scalar( Configuration::SmartHostPort ) );
542
543 if ( Configuration::toggle( Configuration::UseSmtp ) &&
544 Configuration::scalar( Configuration::SmtpPort ) == sP &&
545 ( Configuration::text( Configuration::SmtpAddress ) == sA ||
546 ( Configuration::text( Configuration::SmtpAddress ) == "" &&
547 sA == "127.0.0.1" ) ) )
548 {
549 error( "smarthost-address/port are the same as "
550 "smtp-address/port" );
551 }
552
553 if ( Configuration::toggle( Configuration::UseLmtp ) &&
554 Configuration::scalar( Configuration::LmtpPort ) == sP &&
555 ( Configuration::text( Configuration::LmtpAddress ) == sA ||
556 ( Configuration::text( Configuration::LmtpAddress ) == "" &&
557 sA == "127.0.0.1" ) ) )
558 {
559 error( "smarthost-address/port are the same as "
560 "lmtp-address/port" );
561 }
562
563 if ( Configuration::toggle( Configuration::UseSmtpSubmit ) &&
564 Configuration::scalar( Configuration::SmtpSubmitPort ) == sP &&
565 ( Configuration::text( Configuration::SmtpSubmitAddress ) == sA ||
566 ( Configuration::text( Configuration::SmtpSubmitAddress ) == "" &&
567 sA == "127.0.0.1" ) ) )
568 {
569 error( "smarthost-address/port are the same as "
570 "smtp-submit-address/port" );
571 }
572
573 if ( Configuration::scalar( Configuration::DbMaxHandles ) < 2 ) {
574 fprintf( stderr, "db-max-address should be at least 2, 3 is better\n" );
575 }
576 }
577
578
579 class CheckerData
580 : public Garbage
581 {
582 public:
CheckerData()583 CheckerData()
584 : verbose( 0 ), owner( 0 ), q( 0 ), done( false )
585 {}
586
587 int verbose;
588 EventHandler * owner;
589 Query * q;
590 bool done;
591 };
592
593
594 /*! \class Checker servers.h
595 Checks that the server configuration and environment are sensible.
596 This class is meant to be shared by start/restart.
597 */
598
599 /*! Creates a new Checker for \a owner. If \a verbose is >0, then
600 explanatory messages are printed in addition to any errors that
601 may occur.
602 */
603
Checker(int verbose,EventHandler * owner)604 Checker::Checker( int verbose, EventHandler * owner )
605 : d( new CheckerData )
606 {
607 d->verbose = verbose;
608 d->owner = owner;
609 }
610
611
612 /*! Performs various configuration checks, and notifies the owner when
613 they are done() or if something failed().
614 */
615
execute()616 void Checker::execute()
617 {
618 if ( !d->q ) {
619 Database::setup( 1 );
620
621 checkFilePermissions();
622 checkInetAddresses();
623 checkMiscellaneous();
624
625 d->q = new Query( "select login from users where "
626 "lower(login)='anonymous'", this );
627 d->q->execute();
628 }
629
630 if ( !d->q->done() )
631 return;
632
633 if ( d->q->failed() )
634 error( "Couldn't execute a simple Postgres query: " +
635 d->q->error() );
636
637 Row * r = d->q->nextRow();
638 if ( r ) {
639 if ( !Configuration::toggle( Configuration::AuthAnonymous ) )
640 fprintf( stderr, "Note: auth-anonymous is disabled, but "
641 "there is an anonymous user.\nThe anonymous user "
642 "will not be used. You may wish to delete it:\n\n"
643 "\taox delete user anonymous\n" );
644 }
645 else {
646 if ( Configuration::toggle( Configuration::AuthAnonymous ) )
647 fprintf( stderr, "Note: auth-anonymous is enabled, but will "
648 "not work, because there is no anonymous user,\nYou "
649 "may want to add one with:\n\n"
650 "\taox add user anonymous anonymous "
651 "anon@example.org\n" );
652 }
653
654 d->done = true;
655 d->owner->execute();
656 }
657
658
659 /*! Returns true if this Checker has finished its work (successfully or
660 otherwise), and false if it is still working.
661 */
662
done() const663 bool Checker::done() const
664 {
665 return d->done;
666 }
667
668
669 /*! Returns true if this Checker found a problem with the configuration
670 that merits more than a warning, and false otherwise, in which case
671 it is safe to continue to stop or start the servers.
672 */
673
failed() const674 bool Checker::failed() const
675 {
676 return false;
677 }
678
679
680 class StarterData
681 : public Garbage
682 {
683 public:
StarterData()684 StarterData()
685 : verbose( 0 ), owner( 0 ), done( false )
686 {}
687
688 int verbose;
689 EventHandler * owner;
690 bool done;
691 };
692
693
694 /*! \class Starter servers.h
695 Starts the servers.
696 */
697
698 /*! Creates a new Starter for \a owner. If \a verbose is >0, then
699 explanatory messages are printed in addition to any errors that
700 may occur.
701 */
702
Starter(int verbose,EventHandler * owner)703 Starter::Starter( int verbose, EventHandler * owner )
704 : d( new StarterData )
705 {
706 d->verbose = verbose;
707 d->owner = owner;
708 }
709
710
711 /*! Starts the servers, and notifies the owner when they are done() or
712 if something failed().
713 */
714
execute()715 void Starter::execute()
716 {
717 EString sbin( Configuration::compiledIn( Configuration::SbinDir ) );
718 if ( chdir( sbin.cstr() ) < 0 )
719 error( "Couldn't chdir to SBINDIR (" + sbin + ")" );
720
721 int i = 0;
722 bool started = false;
723 while ( i < nservers )
724 if ( startServer( servers[i++] ) )
725 started = true;
726
727 if ( !started )
728 printf( "No processes need to be started.\n" );
729
730 d->done = true;
731 d->owner->execute();
732 }
733
734
735 /*! Returns true if \a service is needed, and false if not (typically
736 due to configuration, e.g. use-imap for logd).
737 */
738
needed(const EString & service)739 bool Starter::needed( const EString & service )
740 {
741 bool use = true;
742
743 if ( service == "logd" )
744 use = Configuration::present( Configuration::LogFile ) &&
745 !Configuration::text(
746 Configuration::LogFile ).startsWith( "syslog/" );
747 else if ( service == "archiveopteryx" )
748 use = Configuration::toggle( Configuration::UseImap ) ||
749 Configuration::toggle( Configuration::UseImaps ) ||
750 Configuration::toggle( Configuration::UseSmtp ) ||
751 Configuration::toggle( Configuration::UseLmtp ) ||
752 Configuration::toggle( Configuration::UsePop );
753 return use;
754 }
755
756
757 /*! Starts the server named \a s and returns true, or false if the
758 server did not need to be started.
759 */
760
startServer(const char * s)761 bool Starter::startServer( const char * s )
762 {
763 EString srv( Configuration::compiledIn( Configuration::SbinDir ) );
764 srv.append( "/" );
765 srv.append( s );
766
767 bool use = needed( s );
768
769 if ( !use ) {
770 if ( d->verbose > 0 )
771 printf( "Don't need to start %s\n", srv.cstr() );
772 return false;
773 }
774
775 int p = serverPid( s );
776 if ( p != -1 ) {
777 if ( kill( p, 0 ) != 0 && errno == ESRCH ) {
778 File::unlink( pidFile( s ) );
779 }
780 else {
781 if ( d->verbose > 0 )
782 printf( "%s(%d) is already running\n", s, p );
783 return false;
784 }
785 }
786
787 if ( d->verbose > 0 )
788 printf( "Starting %s\n", srv.cstr() );
789
790 pid_t pid = fork();
791 if ( pid < 0 ) {
792 error( "Couldn't fork to exec(" + srv + ")" );
793 }
794 else if ( pid == 0 ) {
795 execl( srv.cstr(), srv.cstr(), "-f", (char *)NULL );
796 exit( -1 );
797 }
798 else {
799 int status = 0;
800 if ( waitpid( pid, &status, 0 ) < 0 ||
801 ( WIFEXITED( status ) && WEXITSTATUS( status ) != 0 ) )
802 error( "Couldn't exec(" + srv + ")" );
803 }
804
805 return true;
806 }
807
808
809 /*! Returns true if this Starter has finished its work (successfully or
810 otherwise), and false if it is still working.
811 */
812
done() const813 bool Starter::done() const
814 {
815 return d->done;
816 }
817
818
819 /*! Returns true if this Starter failed to start the servers, and false
820 if the servers were started successfully.
821 */
822
failed() const823 bool Starter::failed() const
824 {
825 return false;
826 }
827
828
829
830 class ServerPinger
831 : public Connection
832 {
833 public:
ServerPinger(Configuration::Text a,Configuration::Scalar p,EventHandler * owner)834 ServerPinger( Configuration::Text a, Configuration::Scalar p,
835 EventHandler * owner )
836 : Connection(), up( false ), o( owner ) {
837 EString addr;
838 if ( Configuration::text( a ).isEmpty() ) {
839 addr = "127.0.0.1";
840 }
841 else {
842 EStringList::Iterator it(
843 Resolver::resolve( Configuration::text( a ) ) );
844 if ( it )
845 addr = *it;
846 }
847 if ( addr.isEmpty() ) {
848 up = false;
849 }
850 else {
851 connect( Endpoint( addr, Configuration::scalar( p ) ) );
852 EventLoop::global()->addConnection( this );
853 }
854 }
react(Event e)855 void react( Event e ) {
856 switch ( e ) {
857 case Read:
858 case Timeout:
859 case Shutdown:
860 break;
861
862 case Connect:
863 setState( Closing );
864 up = true;
865 break;
866
867 case Error:
868 setState( Closing );
869 up = false;
870 break;
871
872 case Close:
873 up = false;
874 break;
875 }
876 o->execute();
877 }
probing() const878 bool probing() const {
879 if ( up )
880 return false;
881 if ( state() == Connecting )
882 return true;
883 return false;
884 }
serverUp() const885 bool serverUp() const {
886 return up;
887 }
888 bool up;
889 EventHandler * o;
890 };
891
892
893 class StopperData
894 : public Garbage
895 {
896 public:
StopperData()897 StopperData()
898 : state( 0 ), verbose( 0 ), owner( 0 ), timer( 0 ),
899 done( false ), pingers( 0 )
900 {
901 int i = 0;
902 while ( i < nservers )
903 pids[i++] = 0;
904 }
905
906 int state;
907 int verbose;
908 EventHandler * owner;
909 Timer * timer;
910 int pids[nservers];
911 bool done;
912 List<ServerPinger> * pingers;
913 };
914
915
916 /*! \class Stopper servers.h
917 Stops the running servers.
918 */
919
920 /*! Creates a new Stopper for \a owner. If \a verbose is >0, then
921 explanatory messages are printed in addition to any errors that
922 may occur.
923 */
924
Stopper(int verbose,EventHandler * owner)925 Stopper::Stopper( int verbose, EventHandler * owner )
926 : d( new StopperData )
927 {
928 d->verbose = verbose;
929 d->owner = owner;
930 }
931
932
933 /*! Performs various configuration checks, and notifies the owner when
934 they are done() or if something failed().
935 */
936
execute()937 void Stopper::execute()
938 {
939 // We decide what servers are running by looking at the pid files,
940 // kill those servers with SIGTERM, try a few times to connect,
941 // and if we still can connect after 2-3 seconds, then we use
942 // SIGKILL.
943
944 if ( d->state == 0 ) {
945 // State 0: Send SIGTERM and see if that helps
946 int i = 0;
947 int n = 0;
948 while ( i < nservers ) {
949 d->pids[i] = serverPid( servers[nservers-i-1] );
950 if ( d->pids[i] != -1 ) {
951 if ( d->verbose > 0 && !n )
952 printf( "Stopping servers: " );
953 n++;
954 if ( d->verbose > 0 )
955 printf( "%s%s", servers[nservers-i-1],
956 i == nservers-1 ? "" : " " );
957 }
958 i++;
959 }
960 if ( d->verbose > 0 && n )
961 printf( ".\n" );
962
963 i = 0;
964 while ( i < nservers ) {
965 if ( d->pids[i] != -1 ) {
966 if ( d->verbose > 1 )
967 printf( "Sending SIGTERM to %s (pid %d)\n",
968 servers[nservers-i-1], d->pids[i] );
969 File::unlink( pidFile( servers[nservers-i-1] ) );
970 kill( d->pids[i], SIGTERM );
971 }
972 i++;
973 }
974
975 if ( n > 0 ) {
976 d->state = 1;
977 d->timer = new Timer( this, 2 );
978 }
979 else {
980 d->state = 2;
981 }
982 }
983
984 if ( d->timer && !d->timer->active() && d->state < 2 ) {
985 // Servers didn't cooperate. We send SIGKILL and see whether
986 // that does the trick.
987 int i = 0;
988 while ( i < nservers ) {
989 if ( d->pids[i] != -1 && kill( d->pids[i], 0 ) == 0 ) {
990 if ( d->verbose > 1 )
991 printf( "Sending SIGKILL to %s (pid %d)\n",
992 servers[nservers-i-1], d->pids[i] );
993 kill( d->pids[i], SIGKILL );
994 }
995 i++;
996 }
997 d->state = 2;
998 }
999
1000 if ( d->state == 1 ) {
1001 // State 1: We try to connect to each server. If all attempts
1002 // are refused, we're done. If an attempt succeeds, we try
1003 // again. That's a busylock. The timer above will break it.
1004
1005 if ( !d->pingers ) {
1006 d->pingers = new List<ServerPinger>;
1007
1008 if ( Configuration::toggle( Configuration::UseImap ) )
1009 d->pingers->append(
1010 new ServerPinger( Configuration::ImapAddress,
1011 Configuration::ImapPort, this ) );
1012 if ( Configuration::present( Configuration::LogFile ) &&
1013 !Configuration::text(
1014 Configuration::LogFile ).startsWith( "syslog/" ) )
1015 d->pingers->append(
1016 new ServerPinger( Configuration::LogAddress,
1017 Configuration::LogPort, this ) );
1018 }
1019
1020 List<ServerPinger>::Iterator i( d->pingers );
1021 while ( i ) {
1022 if ( i->probing() ) {
1023 return;
1024 }
1025 else if ( i->serverUp() ) {
1026 i = d->pingers->first();
1027 while ( i ) {
1028 i->close();
1029 EventLoop::global()->removeConnection( i );
1030 ++i;
1031 }
1032 d->pingers = 0;
1033 (void)new Timer( this, 0 ); // this leaks memory
1034 return;
1035 }
1036 ++i;
1037 }
1038 if ( !i )
1039 d->state = 2;
1040 }
1041
1042 if ( d->state < 2 )
1043 return;
1044
1045 d->done = true;
1046 d->owner->execute();
1047 }
1048
1049
1050 /*! Returns true if this Stopper has finished its work (successfully or
1051 otherwise), and false if it is still working.
1052 */
1053
done() const1054 bool Stopper::done() const
1055 {
1056 return d->done;
1057 }
1058
1059
1060 /*! Returns true if this Stopper was unable to stop the servers, and
1061 false if the servers were stopped successfully.
1062 */
1063
failed() const1064 bool Stopper::failed() const
1065 {
1066 return false;
1067 }
1068
1069
1070
selfSignCertificate()1071 static void selfSignCertificate()
1072 {
1073 EString keyFile( Configuration::text( Configuration::TlsCertFile ) );
1074
1075 if ( keyFile.isEmpty() ) {
1076 keyFile = Configuration::compiledIn( Configuration::LibDir );
1077 keyFile.append( "/automatic-key.pem" );
1078 }
1079
1080 File key( keyFile );
1081 if ( !key.contents().isEmpty() )
1082 return; // could verify here, for the expiry date
1083
1084 File osslcf( "/tmp/aox-ossl.conf", File::Write );
1085 osslcf.write( "[ req ]\n"
1086 " default_bits = 1024\n"
1087 " default_keyfile = privkey.pem\n"
1088 " distinguished_name = req_distinguished_name\n"
1089 " attributes = req_attributes\n"
1090 " x509_extensions = v3_ca\n"
1091 " prompt = no\n"
1092 "\n"
1093 " dirstring_type = nobmp\n"
1094 "\n"
1095 "[ req_distinguished_name ]\n"
1096 " CN=" + Configuration::hostname() + "\n"
1097 "\n"
1098 "[ req_attributes ]\n"
1099 " challengePassword = \"\"\n"
1100 "\n"
1101 " [ v3_ca ]\n"
1102 "\n"
1103 " nsCertType = server\n"
1104 " nsComment = \"Automatically generated self-signed certificate\"\n"
1105 " subjectKeyIdentifier=hash\n"
1106 " authorityKeyIdentifier=keyid:always,issuer:always\n"
1107 " basicConstraints = CA:true\n" );
1108
1109
1110
1111 int r = system( "openssl req -config /tmp/aox-ossl.conf -x509 -days 1764 -newkey rsa:1024 -nodes -keyout /tmp/aox-ossl.pem -out /tmp/aox-ossl.pem" );
1112 if ( r == -1 )
1113 error( "Needed to execute openssl req, but failed" );
1114
1115 // one one hand, File::write() does no checking. On the other,
1116 // this does at least not pass user-supplied data to the shell.
1117 File ossl( "/tmp/aox-ossl.pem" );
1118 File result( keyFile, File::Write );
1119 result.write( ossl.contents() );
1120 result.write( "\n"
1121 " This certificate was autogenerated by Archiveopteryx,\n"
1122 " since Archiveopteryx was configured to use TLS, but\n"
1123 " no certificate was specified. You may want to replace\n"
1124 " it with a CA-supplied certificate.\n"
1125 "\n" );
1126
1127 File::unlink( "/tmp/aox-ossl.pem" );
1128
1129 printf( "Created self-signed certificate for\n %s\n"
1130 "and stored it in\n %s\n"
1131 "Please verify that file's permissions.\n",
1132 Configuration::hostname().cstr(),
1133 keyFile.cstr() );
1134 }
1135
1136
1137 static AoxFactory<CheckConfig>
1138 f( "check", "config", "Check that the configuration is sane.",
1139 " Synopsis: aox check config\n\n"
1140 " Reads the configuration and reports any problems it finds.\n" );
1141
1142
1143 /*! \class CheckConfig servers.h
1144 This class handles the "aox check config" command.
1145 */
1146
CheckConfig(EStringList * args)1147 CheckConfig::CheckConfig( EStringList * args )
1148 : AoxCommand( args )
1149 {
1150 }
1151
1152
execute()1153 void CheckConfig::execute()
1154 {
1155 if ( !checker ) {
1156 parseOptions();
1157 end();
1158
1159 selfSignCertificate();
1160
1161 checker = new Checker( opt( 'v' ), this );
1162 checker->execute();
1163 }
1164
1165 if ( !checker->done() )
1166 return;
1167
1168 finish();
1169 }
1170
1171
1172
1173
1174
1175 class StartData
1176 : public Garbage
1177 {
1178 public:
StartData()1179 StartData()
1180 : checker( 0 ), starter( 0 )
1181 {}
1182
1183 Checker * checker;
1184 Starter * starter;
1185 };
1186
1187
1188 static AoxFactory<Start>
1189 f2( "start", "", "Start the server(s).",
1190 " Synopsis: aox start [-v]\n\n"
1191 " Starts Archiveopteryx and helper servers in the correct order.\n"
1192 " The -v flag enables (slightly) verbose diagnostic output.\n" );
1193
1194
1195 /*! \class Start servers.h
1196 This class handles the "aox start" command.
1197 */
1198
Start(EStringList * args)1199 Start::Start( EStringList * args )
1200 : AoxCommand( args ), d( new StartData )
1201 {
1202 }
1203
1204
execute()1205 void Start::execute()
1206 {
1207 if ( !d->checker ) {
1208 parseOptions();
1209 end();
1210
1211 EString pfd( Configuration::compiledIn( Configuration::PidFileDir ) );
1212 if ( pfd.startsWith( "/var/run/" ) ) {
1213 // /var/run is wiped out at boot on many systems, which can
1214 // bother us. so if our pidfiledir is in a subdirectory of
1215 // that, we'll create it as needed.
1216 uint l = 9;
1217 bool ok = true;
1218 bool any = false;
1219 while ( l <= pfd.length() ) {
1220 if ( l == pfd.length() || pfd[l] == '/' ) {
1221 struct stat st;
1222 if ( stat( pfd.mid( 0, l ).cstr(), &st ) < 0 ) {
1223 int m = mkdir( pfd.mid( 0, l ).cstr(), 01777 );
1224 int c = chmod( pfd.mid( 0, l ).cstr(), 01777 );
1225 if ( m >= 0 && c >= 0 )
1226 any = true;
1227 if ( m < 0 || c < 0 )
1228 ok = false;
1229 }
1230 }
1231 ++l;
1232 }
1233 // this message probably disappears, but that's a general
1234 // problem - what should aox do about log messages before
1235 // logd is there?
1236 if ( any && ok )
1237 log( "Created pid file directory: " + pfd );
1238 }
1239
1240 selfSignCertificate();
1241
1242 d->checker = new Checker( opt( 'v' ), this );
1243 d->checker->execute();
1244 }
1245
1246 if ( !d->checker->done() )
1247 return;
1248
1249 if ( !d->starter ) {
1250 if ( d->checker->failed() ) {
1251 finish();
1252 return;
1253 }
1254
1255 d->starter = new Starter( opt( 'v' ), this );
1256 d->starter->execute();
1257 }
1258
1259 if ( !d->starter->done() )
1260 return;
1261
1262 finish();
1263 }
1264
1265
1266
1267 static AoxFactory<Stop>
1268 f3( "stop", "", "Start the server(s).",
1269 " Synopsis: aox stop [-v]\n\n"
1270 " Stops Archiveopteryx and helper servers in the correct order.\n"
1271 " The -v flag enables (slightly) verbose diagnostic output.\n" );
1272
1273
1274 /*! \class Stop servers.h
1275 This class handles the "aox stop" command.
1276 */
1277
Stop(EStringList * args)1278 Stop::Stop( EStringList * args )
1279 : AoxCommand( args )
1280 {
1281 }
1282
1283
execute()1284 void Stop::execute()
1285 {
1286 if ( !stopper ) {
1287 parseOptions();
1288 end();
1289
1290 stopper = new Stopper( opt( 'v' ), this );
1291 stopper->execute();
1292 }
1293
1294 if ( !stopper->done() )
1295 return;
1296
1297 finish();
1298 }
1299
1300
1301
1302 class RestartData
1303 : public Garbage
1304 {
1305 public:
RestartData()1306 RestartData()
1307 : checker( 0 ), stopper( 0 ), starter( 0 )
1308 {}
1309
1310 Checker * checker;
1311 Stopper * stopper;
1312 Starter * starter;
1313 };
1314
1315
1316 static AoxFactory<Restart>
1317 f4( "restart", "", "Restart the servers.",
1318 " Synopsis: aox restart [-v]\n\n"
1319 " Restarts Archiveopteryx and its helpers in the correct order.\n"
1320 " (Currently equivalent to start && stop.)\n\n"
1321 " The -v flag enables (slightly) verbose diagnostic output.\n" );
1322
1323
1324 /*! \class Restart servers.h
1325 This class handles the "aox restart" command.
1326 */
1327
Restart(EStringList * args)1328 Restart::Restart( EStringList * args )
1329 : AoxCommand( args ), d( new RestartData )
1330 {
1331 }
1332
1333
execute()1334 void Restart::execute()
1335 {
1336 if ( !d->checker ) {
1337 parseOptions();
1338 end();
1339
1340 selfSignCertificate();
1341
1342 d->checker = new Checker( opt( 'v' ), this );
1343 d->checker->execute();
1344 }
1345
1346 if ( !d->checker->done() )
1347 return;
1348
1349 if ( !d->stopper ) {
1350 if ( d->checker->failed() ) {
1351 finish();
1352 return;
1353 }
1354
1355 d->stopper = new Stopper( opt( 'v' ), this );
1356 d->stopper->execute();
1357 }
1358
1359 if ( !d->stopper->done() )
1360 return;
1361
1362 if ( !d->starter ) {
1363 if ( d->stopper->failed() ) {
1364 finish();
1365 return;
1366 }
1367
1368 d->starter = new Starter( opt( 'v' ), this );
1369 d->starter->execute();
1370 }
1371
1372 if ( !d->starter->done() )
1373 return;
1374
1375 finish();
1376 }
1377
1378
1379
1380 static AoxFactory<ShowStatus>
1381 f5( "show", "status", "Display a summary of the running servers.",
1382 " Synopsis: aox show status [-v]\n\n"
1383 " Displays a summary of the running servers.\n"
1384 " The -v flag enables (slightly) verbose diagnostic output.\n" );
1385
1386
1387 /*! \class ShowStatus servers.h
1388 This class handles the "aox show status" command.
1389 */
1390
ShowStatus(EStringList * args)1391 ShowStatus::ShowStatus( EStringList * args )
1392 : AoxCommand( args )
1393 {
1394 execute();
1395 }
1396
1397
execute()1398 void ShowStatus::execute()
1399 {
1400 parseOptions();
1401 end();
1402
1403 printf( "Servers: " );
1404 if ( opt( 'v' ) > 0 )
1405 printf( "\n " );
1406
1407 int i = 0;
1408 while ( i < nservers ) {
1409 int pid = serverPid( servers[i] );
1410 printf( "%s", servers[i] );
1411
1412 const char * noState =
1413 Starter::needed( servers[i] ) ? "not running" : "not started";
1414
1415 if ( pid < 0 ) {
1416 printf( " (%s)", noState );
1417 }
1418 else if ( kill( pid, 0 ) != 0 && errno == ESRCH ) {
1419 if ( opt( 'v' ) > 0 )
1420 printf( " (%s, stale pidfile)", noState );
1421 else
1422 printf( " (%s)", noState );
1423 }
1424 else if ( opt( 'v' ) > 0 ) {
1425 printf( " (%d)", pid );
1426 }
1427
1428 if ( i != nservers-1 ) {
1429 if ( opt( 'v' ) > 0 )
1430 printf( "\n " );
1431 else
1432 printf( ", " );
1433 }
1434 i++;
1435 }
1436
1437 if ( opt( 'v' ) == 0 )
1438 printf( "." );
1439 printf( "\n" );
1440
1441 finish();
1442 }
1443
1444
1445
1446 static AoxFactory<ShowBuild>
1447 f6( "show", "build", "Display build settings.",
1448 " Synopsis: aox show build\n\n"
1449 " Displays the build settings used for this installation.\n"
1450 " (As configured in Jamsettings.)\n" );
1451
1452
1453 /*! \class ShowBuild servers.h
1454 This class handles the "aox show build" command.
1455 */
1456
ShowBuild(EStringList * args)1457 ShowBuild::ShowBuild( EStringList * args )
1458 : AoxCommand( args )
1459 {
1460 execute();
1461 }
1462
1463
execute()1464 void ShowBuild::execute()
1465 {
1466 end();
1467
1468 printf( "Archiveopteryx version %s, "
1469 "http://archiveopteryx.org/%s\n",
1470 Configuration::compiledIn( Configuration::Version ),
1471 Configuration::compiledIn( Configuration::Version ) );
1472
1473 printf( "Built on " __DATE__ " " __TIME__ "\n" );
1474
1475 int i = 0;
1476 while ( buildinfo[i] && *buildinfo[i] )
1477 printf( "%s\n", buildinfo[i++] );
1478
1479 printf( "Jamsettings:\n" );
1480 printf( "CONFIGDIR = %s\n",
1481 Configuration::compiledIn( Configuration::ConfigDir ) );
1482 printf( "PIDFILEDIR = %s\n",
1483 Configuration::compiledIn( Configuration::PidFileDir ) );
1484 printf( "BINDIR = %s\n",
1485 Configuration::compiledIn( Configuration::BinDir ) );
1486 printf( "SBINDIR = %s\n",
1487 Configuration::compiledIn( Configuration::SbinDir ) );
1488 printf( "MANDIR = %s\n",
1489 Configuration::compiledIn( Configuration::ManDir ) );
1490 printf( "LIBDIR = %s\n",
1491 Configuration::compiledIn( Configuration::LibDir ) );
1492 printf( "INITDIR = %s\n",
1493 Configuration::compiledIn( Configuration::InitDir ) );
1494 printf( "AOXUSER = %s\n",
1495 Configuration::compiledIn( Configuration::AoxUser ) );
1496 printf( "AOXGROUP = %s\n",
1497 Configuration::compiledIn( Configuration::AoxGroup ) );
1498 printf( "VERSION = %s\n",
1499 Configuration::compiledIn( Configuration::Version ) );
1500
1501 finish();
1502 }
1503
1504
1505
1506 static AoxFactory<ShowConfiguration>
1507 f7( "show", "configuration", "Display configuration variables.",
1508 " Synopsis: aox show conf [ -p -v ] [variable-name]\n\n"
1509 " Displays variables configured in archiveopteryx.conf.\n\n"
1510 " If a variable-name is specified, only that variable\n"
1511 " is displayed.\n\n"
1512 " The -v flag displays only the value of the variable.\n"
1513 " The -p flag restricts the results to variables whose\n"
1514 " value has been changed from the default.\n\n"
1515 " configuration may be abbreviated as cf.\n\n"
1516 " Examples:\n\n"
1517 " aox show configuration\n"
1518 " aox show cf -p\n"
1519 " aox show cf -v imap-address\n" );
1520
1521 static AoxFactory<ShowConfiguration>
1522 f8( "show", "cf", &f7 );
1523
1524 static AoxFactory<ShowConfiguration>
1525 f9( "show", "conf", &f7 );
1526
1527
1528 /*! \class ShowConfiguration servers.h
1529 This class handles the "aox show configuration" command.
1530 */
1531
ShowConfiguration(EStringList * args)1532 ShowConfiguration::ShowConfiguration( EStringList * args )
1533 : AoxCommand( args )
1534 {
1535 }
1536
1537
execute()1538 void ShowConfiguration::execute()
1539 {
1540 SortedList<EString> output;
1541
1542 parseOptions();
1543 EString pat = next();
1544 end();
1545
1546 uint i = 0;
1547 while ( i < Configuration::NumScalars ) {
1548 Configuration::Scalar j = (Configuration::Scalar)i++;
1549
1550 EString n( Configuration::name( j ) );
1551 EString v( fn( Configuration::scalar( j ) ) );
1552 addVariable( &output, n, v, pat, Configuration::present( j ) );
1553 }
1554
1555 i = 0;
1556 while ( i < Configuration::NumToggles ) {
1557 Configuration::Toggle j = (Configuration::Toggle)i++;
1558
1559 EString n( Configuration::name( j ) );
1560 EString v( Configuration::toggle( j ) ? "on" : "off" );
1561 addVariable( &output, n, v, pat, Configuration::present( j ) );
1562 }
1563
1564 i = 0;
1565 while ( i < Configuration::NumTexts ) {
1566 Configuration::Text j = (Configuration::Text)i++;
1567
1568 EString n( Configuration::name( j ) );
1569 EString v( Configuration::text( j ) );
1570 if ( j != Configuration::DbPassword &&
1571 j != Configuration::DbOwnerPassword ) {
1572 if ( !v.boring() )
1573 v = v.quoted();
1574 addVariable( &output, n, v, pat, Configuration::present( j ) );
1575 }
1576 }
1577
1578 EStringList::Iterator it( output );
1579 while ( it ) {
1580 printf( "%s\n", it->cstr() );
1581 ++it;
1582 }
1583
1584 finish();
1585 }
1586
1587
1588 /*! Adds the variable named \a n with value \a v to the output list
1589 \a l if it matches \a pat and is explicitly \a mentioned.
1590 */
1591
addVariable(SortedList<EString> * l,EString n,EString v,EString pat,bool mentioned)1592 void ShowConfiguration::addVariable( SortedList< EString > * l,
1593 EString n, EString v, EString pat,
1594 bool mentioned )
1595 {
1596 int np = opt( 'p' );
1597 int nv = opt( 'v' );
1598
1599 if ( ( pat.isEmpty() || n == pat ) &&
1600 ( np == 0 || mentioned ) )
1601 {
1602 EString * s = new EString;
1603
1604 if ( nv == 0 ) {
1605 s->append( n );
1606 s->append( " = " );
1607 }
1608 s->append( v );
1609 l->insert( s );
1610 }
1611 }
1612