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