1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2 
3 #include "scope.h"
4 #include "estring.h"
5 #include "allocator.h"
6 #include "estringlist.h"
7 #include "stderrlogger.h"
8 #include "configuration.h"
9 #include "transaction.h"
10 #include "eventloop.h"
11 #include "database.h"
12 #include "entropy.h"
13 #include "schema.h"
14 #include "granter.h"
15 #include "query.h"
16 #include "event.h"
17 #include "file.h"
18 #include "md5.h"
19 
20 #include <errno.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <unistd.h>
27 #include <fcntl.h>
28 #include <pwd.h>
29 #include <grp.h>
30 #include <termios.h>
31 
32 
33 uid_t postgres = 0;
34 class Dispatcher * d;
35 bool report = false;
36 bool silent = false;
37 uint verbosity = 0;
38 
39 EString * db = 0;
40 EString * dbname = 0;
41 EString * dbsocket = 0;
42 EString * dbaddress = 0;
43 EString * dbuser = 0;
44 EString * dbpass = 0;
45 EString * dbowner = 0;
46 EString * dbownerpass = 0;
47 EString * dbpgpass = 0;
48 EString * dbschema = 0;
49 
50 bool privateSchema = false;
51 
52 uint dbport = 0;
53 bool askPass = false;
54 
55 int todo = 0;
56 bool generatedPass = false;
57 bool generatedOwnerPass = false;
58 
59 const char * PGUSER;
60 const char * AOXUSER;
61 const char * AOXGROUP;
62 const char * DBADDRESS;
63 
64 
65 void help();
66 void error( EString );
67 EString pgErr( const Query * );
68 bool exists( const EString & );
69 void configure();
70 void findPostgres();
71 void findPgUser();
72 void badSocket( EString * );
73 bool checkSocket( EString * );
74 void readPassword();
75 void readPgPass();
76 void aoxGroup();
77 void aoxUser();
78 void database();
79 void configFile();
80 void superConfig();
81 void permissions();
82 int psql( const EString & );
83 
84 void checkVersion();
85 void checkEncoding();
86 void createUser();
87 void createSuperuser();
88 void createDatabase();
89 void createLang();
90 void createExtensions();
91 void createNamespace();
92 void checkOwnership();
93 void grantUsage();
94 void splitPrivileges();
95 void createSchema();
96 void upgradeSchema();
97 void grantPrivileges();
98 
99 
100 /*! \nodoc */
101 
102 
main(int ac,char * av[])103 int main( int ac, char *av[] )
104 {
105     Scope global;
106     Log * l = new Log;
107     Allocator::addEternal( l, "log object" );
108     global.setLog( l );
109 
110     PGUSER = Configuration::compiledIn( Configuration::PgUser );
111     AOXUSER = Configuration::compiledIn( Configuration::AoxUser );
112     AOXGROUP = Configuration::compiledIn( Configuration::AoxGroup );
113     DBADDRESS = Configuration::compiledIn( Configuration::DefaultDbAddress );
114 
115     av++;
116     while ( ac-- > 1 ) {
117         EString s( *av++ );
118 
119         if ( s == "-?" || s == "-h" || s == "--help" ) {
120             help();
121         }
122         else if ( s == "-q" ) {
123             silent = true;
124             verbosity = 0;
125         }
126         else if ( s == "-n" ) {
127             report = true;
128         }
129         else if ( s == "-g" || s == "-u" || s == "-p" || s == "-a" ||
130                   s == "-s" || s == "-d" || s == "-S" )
131         {
132             if ( ac == 1 )
133                 error( s + " specified with no argument." );
134 
135             if ( s == "-g" ) {
136                 AOXGROUP = *av++;
137             }
138             else if ( s == "-u" ) {
139                 AOXUSER = *av++;
140                 dbuser = new EString( AOXUSER );
141             }
142             else if ( s == "-p" ) {
143                 PGUSER = *av++;
144             }
145             else if ( s == "-a" ) {
146                 dbaddress = new EString( *av++ );
147             }
148             else if ( s == "-s" ) {
149                 dbsocket = new EString( *av++ );
150                 Allocator::addEternal( dbsocket, "DBSOCKET" );
151             }
152             else if ( s == "-d" ) {
153                 dbname = new EString( *av++ );
154             }
155             else if ( s == "-S" ) {
156                 dbschema = new EString( *av++ );
157             }
158 
159             ac--;
160         }
161         else if ( s == "-t" ) {
162             if ( ac == 1 )
163                 error( s + " specified with no argument." );
164             EString p( *av++ );
165             bool ok;
166             dbport = p.number( &ok );
167             if ( !ok )
168                 error( "Invalid port number " + p );
169             ac--;
170         }
171         else if ( s == "-P" ) {
172             askPass = true;
173         }
174         else if ( s == "-v" ) {
175             verbosity++;
176         }
177         else {
178             error( "Unrecognised argument: " + s.quoted() );
179         }
180     }
181 
182     Allocator::addEternal( new StderrLogger( "installer", verbosity ),
183                            "log object" );
184 
185     if ( verbosity )
186         printf( "Archiveopteryx installer version %s\n",
187                 Configuration::compiledIn( Configuration::Version ) );
188 
189     if ( getuid() != 0 )
190         error( "Please run the installer as root." );
191 
192     if ( verbosity ) {
193         EString d( Configuration::compiledIn( Configuration::ConfigDir ) );
194         printf( "Will read these configuration files:\n"
195                 "    %s/archiveopteryx.conf\n"
196                 "    %s/aoxsuper.conf\n",
197                 d.cstr(), d.cstr() );
198     }
199 
200     Configuration::setup( "archiveopteryx.conf" );
201     EString super( Configuration::compiledIn( Configuration::ConfigDir ) );
202     super.append( "/aoxsuper.conf" );
203     Configuration::read( super, true );
204 
205     configure();
206 
207     findPostgres();
208 
209     if ( report )
210         printf( "Reporting what the installer needs to do.\n" );
211 
212     aoxGroup();
213     aoxUser();
214 
215     if ( postgres != 0 )
216         seteuid( postgres );
217     EventLoop::setup();
218     database();
219 
220     if ( d )
221         Allocator::addEternal( d, "dispatcher" );
222     EventLoop::global()->start();
223 }
224 
225 
help()226 void help()
227 {
228     fprintf(
229         stderr,
230         "  Archiveopteryx installer\n\n"
231         "  Synopsis:\n\n"
232         "    installer [-n] [-q]\n"
233         "    installer [-g group] [-u user] [-p postgres] [-s socket]\n"
234         "              [-a address] [-t port] [-d dbname] [-S schema]\n\n"
235         "  This program does the following:\n\n"
236         "    - Creates a Unix group named aox, and a user named aox.\n"
237         "    - Creates Postgres users named aoxsuper and aox.\n"
238         "    - Creates a database named archiveopteryx, owned by aoxsuper.\n"
239         "    - Loads the database schema and grants limited privileges "
240         "to user aox.\n"
241         "    - Generates an initial configuration file.\n"
242         "    - Adjusts ownership and permissions if necessary.\n\n"
243         "  Options:\n\n"
244         "  The -q flag suppresses all normal output.\n\n"
245         "  The -n flag causes the program to report what it would do,\n"
246         "  but not actually do anything.\n\n"
247         "  The \"-g group\" flag allows you to specify a Unix group\n"
248         "  other than the default of '%s'.\n\n"
249         "  The \"-u user\" flag allows you to specify a Unix username\n"
250         "  other than the default of '%s'.\n\n"
251         "  The \"-p postgres\" flag allows you to specify the name of\n"
252         "  the PostgreSQL superuser. The default is to try postgres and\n"
253         "  pgsql in turn.\n\n"
254         "  The \"-P\" flag instructs the installer to prompt for and\n"
255         "  read the Postgres superuser's password, and be prepared to\n"
256         "  use that for authentication (if necessary).\n\n"
257         "  The \"-s socket\" flag allows you to specify an alternate\n"
258         "  location for the Postgres server's named listening socket.\n\n"
259         "  The \"-a address\" flag allows you to specify a different\n"
260         "  address for the Postgres server. The default is '%s'.\n\n"
261         "  The \"-t port\" flag allows you to specify a different port\n"
262         "  for the Postgres server. The default is 5432.\n\n"
263         "  The \"-d dbname\" flag allows you to specify a database name to\n"
264         "  use. The default is '%s'.\n\n"
265         "  The \"-S schema\" flag allows you to specify a schema in the\n"
266         "  database where objects are installed. The default is to assume\n"
267         "  that objects live in the public schema.\n\n"
268         "  The defaults are set at build time in the Jamsettings file.\n\n",
269         AOXGROUP, AOXUSER, DBADDRESS, DBNAME
270     );
271     exit( 0 );
272 }
273 
274 
error(EString m)275 void error( EString m )
276 {
277     fprintf( stderr, "%s\n", m.cstr() );
278     exit( -1 );
279 }
280 
281 
pgErr(const Query * q)282 EString pgErr( const Query * q )
283 {
284     EString p( "PostgreSQL error: " );
285     p.append( q->error() );
286     return p;
287 }
288 
289 
exists(const EString & f)290 bool exists( const EString & f )
291 {
292     struct stat st;
293     return stat( f.cstr(), &st ) == 0;
294 }
295 
296 
findPostgres()297 void findPostgres()
298 {
299     EString port( fn( dbport ) );
300 
301     if ( !dbsocket && *dbaddress == "127.0.0.1" )
302         dbsocket = new EString( "/tmp/.s.PGSQL." + port );
303 
304     if ( dbsocket ) {
305         findPgUser();
306         if ( checkSocket( dbsocket ) == false )
307             badSocket( dbsocket );
308         db = dbsocket;
309     }
310     else {
311         if ( !*PGUSER )
312             PGUSER = "postgres";
313         struct passwd * p = getpwnam( PGUSER );
314         if ( p )
315             postgres = p->pw_uid;
316         db = dbaddress;
317     }
318     Allocator::addEternal( db, "DB" );
319 
320     if ( askPass )
321         readPassword();
322     else
323         readPgPass();
324 
325     if ( !silent )
326         printf( "Connecting to Postgres server %s as%suser %s.\n",
327                 db->cstr(), ( postgres != 0 ? " Unix " : " " ),
328                 PGUSER );
329 }
330 
331 
badSocket(EString * sock)332 void badSocket( EString * sock )
333 {
334     fprintf( stderr, "Error: Couldn't find the Postgres listening "
335              "socket at '%s'.\n", sock->cstr() );
336 
337     if ( exists( "/etc/debian_version" ) &&
338          exists( "/var/run/postgresql/.s.PGSQL.5432" ) )
339     {
340         fprintf( stderr, "(On Debian, perhaps it should be "
341                  "/var/run/postgresql/.s.PGSQL.5432 instead.)\n" );
342     }
343 
344     fprintf( stderr, "Please rerun the installer with "
345              "\"-s /path/to/socket.file\".\n" );
346     exit( -1 );
347 }
348 
349 
checkSocket(EString * sock)350 bool checkSocket( EString * sock )
351 {
352     if ( !sock->startsWith( "/" ) )
353         return false;
354 
355     struct stat st;
356     int n = stat( sock->cstr(), &st );
357 
358     if ( n < 0 ) {
359         return false;
360     }
361     else if ( S_ISSOCK( st.st_mode ) ) {
362         // This is the normal case.
363     }
364     else if ( S_ISDIR( st.st_mode ) ) {
365         // Postgres users are used to specifying a directory and port
366         // number, and letting psql turn that into a socket path. We
367         // try to cooperate.
368 
369         EString s( "/.s.PGSQL." + fn( dbport ) );
370         sock->append( s );
371 
372         if ( !( stat( sock->cstr(), &st ) == 0 &&
373                 S_ISSOCK( st.st_mode ) ) )
374             return false;
375 
376         fprintf( stderr, "Using '%s' as the server socket.\n",
377                  sock->cstr() );
378     }
379     else {
380         return false;
381     }
382 
383     // If we were run with "-s /foo/bar/.s.PGSQL.6666", make sure we can
384     // translate that into "psql -h /foo/bar -p 6666".
385 
386     if ( !sock->endsWith( "/.s.PGSQL." + fn( dbport ) ) ) {
387         bool ok = false;
388         EString s = *sock;
389 
390         uint i = sock->length()-1;
391         while ( i > 0 && (*sock)[i] != '/' )
392             i--;
393         if ( i > 0 && (*sock)[i] == '/' ) {
394             s = sock->mid( i+1 );
395             if ( s.startsWith( ".s.PGSQL." ) ) {
396                 EString port( s.mid( 9 ) );
397                 dbport = port.number( &ok );
398             }
399         }
400 
401         if ( !ok )
402             error( "Malformed socket name: " + s.quoted() );
403     }
404 
405     return true;
406 }
407 
408 
readPassword()409 void readPassword()
410 {
411     char * s;
412     char passwd[128];
413     struct termios term;
414     struct termios newt;
415 
416     if ( tcgetattr( 0, &term ) < 0 )
417         error( "Couldn't get terminal attributes (-" +
418                fn( errno ) + ")." );
419     newt = term;
420     newt.c_lflag |= ECHONL;
421     newt.c_lflag &= ~(ECHO|ISIG);
422     if ( tcsetattr( 0, TCSANOW, &newt ) < 0 )
423         error( "Couldn't set terminal attributes (-" +
424                fn( errno ) + ")." );
425     printf( "Password: " );
426     s = fgets( passwd, 128, stdin );
427     tcsetattr( 0, TCSANOW, &term );
428     if ( !s )
429         error( "Couldn't read password" );
430     dbpgpass = new EString( passwd );
431     dbpgpass->truncate( dbpgpass->length()-1 );
432     Allocator::addEternal( dbpgpass, "DBPGPASS" );
433 }
434 
435 
splitFields(const EString & s)436 EStringList * splitFields( const EString & s )
437 {
438     EStringList * l = new EStringList;
439 
440     uint i = 0;
441     EString word;
442     while ( i < s.length() ) {
443         char c = s[i++];
444 
445         if ( c == ':' || c == '\n' ) {
446             l->append( new EString( word ) );
447             word.truncate( 0 );
448         }
449         else {
450             if ( c == '\\' )
451                 c = s[i++];
452             word.append( c );
453         }
454     }
455 
456     return l;
457 }
458 
459 
readPgPass()460 void readPgPass()
461 {
462     const char * pgpass = getenv( "PGPASSFILE" );
463     if ( !pgpass )
464         return;
465 
466     struct stat st;
467     if ( stat( pgpass, &st ) < 0 || !S_ISREG( st.st_mode ) ||
468          ( st.st_mode & (S_IRWXG|S_IRWXO) ) != 0 )
469         return;
470 
471     File f( pgpass, File::Read );
472     if ( !f.valid() )
473         return;
474 
475     EStringList::Iterator line( f.lines() );
476     while ( line ) {
477         EStringList * fields = splitFields( *line );
478         if ( fields->count() != 5 )
479             return;
480 
481         EString host( *fields->shift() );
482         EString port( *fields->shift() );
483         EString database( *fields->shift() );
484         EString username( *fields->shift() );
485         EString password( *fields->shift() );
486 
487         if ( ( host == "*" ||
488                host == *db ||
489                ( host == "localhost" &&
490                  ( *db == "127.0.0.1" || db->startsWith( "/" ) ) ) ) &&
491              ( port == "*" || port == fn( dbport ) ) &&
492              ( database == "*" || database == "template1" ) &&
493              ( username == "*" || username == PGUSER ) )
494         {
495             dbpgpass = new EString( password );
496             break;
497         }
498 
499         ++line;
500     }
501 
502     if ( dbpgpass ) {
503         Allocator::addEternal( dbpgpass, "DBPGPASS" );
504         fprintf( stderr, "Using password from PGPASSFILE='%s'\n",
505                  pgpass );
506     }
507 }
508 
509 
findPgUser()510 void findPgUser()
511 {
512     struct passwd * p = 0;
513 
514     if ( *PGUSER ) {
515         p = getpwnam( PGUSER );
516         if ( !p )
517             error( "PostgreSQL superuser " + EString( PGUSER ).quoted() +
518                    " does not exist (rerun with -p username)." );
519     }
520 
521     if ( !p ) {
522         PGUSER = "postgres";
523         p = getpwnam( PGUSER );
524     }
525     if ( !p ) {
526         PGUSER = "pgsql";
527         p = getpwnam( PGUSER );
528     }
529     if ( !p ) {
530         error( "PostgreSQL superuser unknown (PGUSER not set, and neither "
531                "\"postgres\" nor \"pgsql\" worked). Please re-run the "
532                "installer with \"-p username\"." );
533     }
534 
535     postgres = p->pw_uid;
536 
537     EString path( getenv( "PATH" ) );
538     path.append( ":" + EString( p->pw_dir ) + "/bin" );
539     path.append( ":/usr/local/pgsql/bin" );
540     setenv( "PATH", path.cstr(), 1 );
541 }
542 
543 
configure()544 void configure()
545 {
546     Entropy::setup();
547 
548     if ( !dbname ) {
549         EString t( DBNAME );
550 
551         if ( Configuration::present( Configuration::DbName ) ) {
552             t = Configuration::text( Configuration::DbName );
553             if ( verbosity )
554                 printf( "Using db-name from the configuration: %s\n",
555                         dbname->cstr() );
556         }
557 
558         dbname = new EString( t );
559     }
560     Allocator::addEternal( dbname, "DBNAME" );
561 
562     if ( !dbschema ) {
563         EString t( DBSCHEMA );
564 
565         if ( Configuration::present( Configuration::DbSchema ) ) {
566             t = Configuration::text( Configuration::DbSchema );
567             if ( verbosity )
568                 printf( "Using db-schema from the configuration: %s\n",
569                         dbschema->cstr() );
570         }
571 
572         dbschema = new EString( t );
573     }
574     Allocator::addEternal( dbschema, "DBSCHEMA" );
575 
576     if ( *dbschema != "public" )
577         ::privateSchema = true;
578 
579     if ( !dbaddress ) {
580         EString t( DBADDRESS );
581 
582         if ( Configuration::present( Configuration::DbAddress ) ) {
583             t = Configuration::text( Configuration::DbAddress );
584             if ( verbosity )
585                 printf( "Using db-address from the configuration: %s\n",
586                         dbaddress->cstr() );
587         }
588 
589         dbaddress = new EString( t );
590     }
591     Allocator::addEternal( dbaddress, "DBADDRESS" );
592 
593     if ( dbport == 0 ) {
594         if ( Configuration::present( Configuration::DbPort ) ) {
595             dbport = Configuration::scalar( Configuration::DbPort );
596             if ( verbosity )
597                 printf( "Using db-port from the configuration: %d\n", dbport );
598         }
599         else {
600             dbport = 5432;
601         }
602     }
603 
604     if ( !dbuser ) {
605         EString t( AOXUSER );
606 
607         if ( Configuration::present( Configuration::DbUser ) ) {
608             t = Configuration::text( Configuration::DbUser );
609             if ( verbosity )
610                 printf( "Using db-user from the configuration: %s\n",
611                         dbuser->cstr() );
612         }
613 
614         dbuser = new EString( t );
615     }
616     Allocator::addEternal( dbuser, "AOXUSER" );
617 
618     dbpass = new EString( DBPASS );
619     if ( Configuration::present( Configuration::DbPassword ) ) {
620         *dbpass = Configuration::text( Configuration::DbPassword );
621         if ( verbosity )
622             printf( "Using db-password from the configuration\n" );
623     }
624     else if ( dbpass->isEmpty() ) {
625         EString p( "(database user password here)" );
626         if ( !report ) {
627             p = MD5::hash( Entropy::asString( 16 ) ).hex();
628             generatedPass = true;
629         }
630         dbpass->append( p );
631     }
632     Allocator::addEternal( dbpass, "DBPASS" );
633 
634     dbowner = new EString( DBOWNER );
635     if ( Configuration::present( Configuration::DbOwner ) ) {
636         *dbowner = Configuration::text( Configuration::DbOwner );
637         if ( verbosity )
638             printf( "Using db-owner from the configuration: %s\n",
639                     dbowner->cstr() );
640     }
641     Allocator::addEternal( dbowner, "DBOWNER" );
642 
643     dbownerpass = new EString( DBOWNERPASS );
644     if ( Configuration::present( Configuration::DbOwnerPassword ) ) {
645         *dbownerpass = Configuration::text( Configuration::DbOwnerPassword );
646         if ( verbosity )
647             printf( "Using db-owner-password from the configuration\n" );
648     }
649     else if ( dbownerpass->isEmpty() ) {
650         EString p( "(database owner password here)" );
651         if ( !report ) {
652             p = MD5::hash( Entropy::asString( 16 ) ).hex();
653             generatedOwnerPass = true;
654         }
655         dbownerpass->append( p );
656     }
657     Allocator::addEternal( dbownerpass, "DBOWNERPASS" );
658 }
659 
660 
aoxGroup()661 void aoxGroup()
662 {
663     struct group * g = getgrnam( AOXGROUP );
664     if ( g )
665         return;
666 
667     if ( report ) {
668         todo++;
669         printf( " - Create a group named '%s' (e.g. \"groupadd %s\").\n",
670                 AOXGROUP, AOXGROUP );
671         return;
672     }
673 
674     EString cmd;
675     if ( exists( "/usr/sbin/groupadd" ) ) {
676         cmd.append( "/usr/sbin/groupadd " );
677         cmd.append( AOXGROUP );
678     }
679     else if ( exists( "/usr/sbin/pw" ) ) {
680         cmd.append( "/usr/sbin/pw groupadd " );
681         cmd.append( AOXGROUP );
682     }
683 
684     int status = 0;
685     if ( !cmd.isEmpty() ) {
686         if ( !silent )
687             printf( "Creating the '%s' group.\n", AOXGROUP );
688         status = system( cmd.cstr() );
689     }
690 
691     if ( cmd.isEmpty() || WEXITSTATUS( status ) != 0 ||
692          getgrnam( AOXGROUP ) == 0 )
693     {
694         EString s;
695         if ( cmd.isEmpty() )
696             s.append( "Don't know how to create group " );
697         else
698             s.append( "Couldn't create group " );
699         s.append( "'" );
700         s.append( AOXGROUP );
701         s.append( "'. " );
702         s.append( "Please create it by hand and re-run the installer.\n" );
703         if ( !cmd.isEmpty() )
704             s.append( "The command which failed was " + cmd.quoted() );
705         error( s );
706     }
707 }
708 
709 
aoxUser()710 void aoxUser()
711 {
712     struct passwd * p = getpwnam( AOXUSER );
713     if ( p )
714         return;
715 
716     if ( report ) {
717         todo++;
718         printf( " - Create a user named '%s' in the '%s' group "
719                 "(e.g. \"useradd -g %s %s\").\n",
720                 AOXUSER, AOXGROUP, AOXGROUP, AOXUSER );
721         return;
722     }
723 
724     EString cmd;
725     if ( exists( "/usr/sbin/useradd" ) ) {
726         cmd.append( "/usr/sbin/useradd -g " );
727         cmd.append( AOXGROUP );
728         cmd.append( " " );
729         cmd.append( AOXUSER );
730     }
731     else if ( exists( "/usr/sbin/pw" ) ) {
732         cmd.append( "/usr/sbin/pw useradd " );
733         cmd.append( AOXUSER );
734         cmd.append( " -g " );
735         cmd.append( AOXGROUP );
736     }
737 
738     int status = 0;
739     if ( !cmd.isEmpty() ) {
740         if ( !silent )
741             printf( "Creating the '%s' user.\n", AOXUSER );
742         status = system( cmd.cstr() );
743     }
744 
745     if ( cmd.isEmpty() || WEXITSTATUS( status ) != 0 ||
746          getpwnam( AOXUSER ) == 0 )
747     {
748         EString s;
749         if ( cmd.isEmpty() )
750             s.append( "Don't know how to create user " );
751         else
752             s.append( "Couldn't create user " );
753         s.append( "'" );
754         s.append( AOXUSER );
755         s.append( "'. " );
756         s.append( "Please create it by hand and re-run the installer.\n" );
757         s.append( "The new user does not need a valid login shell or "
758                   "password.\n" );
759         if ( !cmd.isEmpty() )
760             s.append( "The command which failed was " + cmd.quoted() );
761         error( s );
762     }
763 }
764 
765 
766 enum DbState {
767     CheckVersion, CheckEncoding,
768     CreateUser, CreateSuperuser, CreateDatabase, CreateLang,
769     CreateExtensions,
770     CreateNamespace, CheckOwnership, GrantUsage, SplitPrivileges,
771     CreateSchema, UpgradeSchema, GrantPrivileges,
772     Done
773 };
774 
775 
776 class Dispatcher
777     : public EventHandler
778 {
779 public:
780     DbState state;
781     Query * q;
782     Query * u;
783     Query * w;
784     Query * ssa;
785     Query * ssp;
786     Granter * g;
787     Transaction * t;
788     bool databaseExists;
789     bool namespaceExists;
790     bool mailstoreExists;
791     bool failed;
792     EString owner;
793 
Dispatcher()794     Dispatcher()
795         : state( CheckVersion ),
796           q( 0 ), u( 0 ), w( 0 ), ssa( 0 ), ssp( 0 ), g( 0 ), t( 0 ),
797           databaseExists( false ), namespaceExists( false ),
798           mailstoreExists( false ), failed( false )
799     {}
800 
execute()801     void execute()
802     {
803         database();
804     }
805 
error(const EString & s)806     void error( const EString &s )
807     {
808         d->failed = true;
809         fprintf( stderr, "%s\n", s.cstr() );
810     }
811 
nextState()812     void nextState()
813     {
814         d->q = d->u = d->w = 0;
815         state = (DbState)(state + 1);
816     }
817 };
818 
819 
connectToDb(const EString & dbname)820 void connectToDb( const EString & dbname )
821 {
822     Configuration::setup( "" );
823     Configuration::add( "db-max-handles = 1" );
824     Configuration::add( "db-name = " + dbname.quoted() );
825     Configuration::add( "db-schema = " + dbschema->quoted() );
826     Configuration::add( "db-user = " + dbuser->quoted() );
827     Configuration::add( "db-address = " + db->quoted() );
828     if ( !db->startsWith( "/" ) )
829         Configuration::add( "db-port = " + fn( dbport ) );
830 
831     EString pass;
832     if ( dbpgpass )
833         pass = *dbpgpass;
834 
835     Database::setup( 1, PGUSER, pass );
836 }
837 
838 
database()839 void database()
840 {
841     if ( !d ) {
842         connectToDb( "template1" );
843         d = new Dispatcher;
844     }
845 
846     DbState last;
847 
848     do {
849         last = d->state;
850         switch ( d->state ) {
851         case CheckVersion:
852             checkVersion();
853             break;
854 
855         case CheckEncoding:
856             checkEncoding();
857             break;
858 
859         case CreateUser:
860             createUser();
861             break;
862 
863         case CreateSuperuser:
864             createSuperuser();
865             break;
866 
867         case CreateDatabase:
868             createDatabase();
869             break;
870 
871         case CreateLang:
872             createLang();
873             break;
874 
875         case CreateExtensions:
876             createExtensions();
877             break;
878 
879         case CreateNamespace:
880             createNamespace();
881             break;
882 
883         case CheckOwnership:
884             checkOwnership();
885             break;
886 
887         case GrantUsage:
888             grantUsage();
889             break;
890 
891         case SplitPrivileges:
892             splitPrivileges();
893             break;
894 
895         case CreateSchema:
896             createSchema();
897             break;
898 
899         case UpgradeSchema:
900             upgradeSchema();
901             break;
902 
903         case GrantPrivileges:
904             grantPrivileges();
905             break;
906 
907         case Done:
908             break;
909         }
910 
911         if ( d->failed ) {
912             EventLoop::shutdown();
913             return;
914         }
915     }
916     while ( last != d->state && d->state != Done );
917 
918     if ( d->state == Done )
919         configFile();
920 }
921 
922 
checkVersion()923 void checkVersion()
924 {
925     // We could use Postgres::version() instead of issuing a query here,
926     // but it's not worth it. We have to check that we can issue queries
927     // anyway.
928 
929     if ( !d->q ) {
930         d->q = new Query( "show server_version_num", d );
931         d->q->execute();
932     }
933 
934     if ( !d->q->done() )
935         return;
936 
937     Row * r = d->q->nextRow();
938     if ( d->q->failed() || !r ) {
939         d->error( "Couldn't check Postgres server version. " + pgErr( d->q ) );
940         return;
941     }
942 
943     EString v = r->getEString( "server_version_num" );
944     bool ok = true;
945     uint version = v.number( &ok );
946     if ( !ok || version < 90100 ) {
947         d->error( "Archiveopteryx requires PostgreSQL 9.1.0 or higher "
948                   "(found only " + v + ")." );
949         return;
950     }
951 
952     d->nextState();
953 }
954 
955 
checkEncoding()956 void checkEncoding()
957 {
958     if ( !d->q ) {
959         d->owner = *dbowner;
960         d->q = new Query( "select usename::text, "
961                           "pg_encoding_to_char(encoding)::text as encoding "
962                           "from pg_database d join pg_user u "
963                           "on (d.datdba=u.usesysid) where datname=$1", d );
964         d->q->bind( 1, *dbname );
965         d->q->execute();
966     }
967 
968     if ( !d->q->done() )
969         return;
970 
971     if ( d->q->failed() ) {
972         d->error( "Couldn't check encoding for database " +
973                   dbname->quoted( '\'' ) + ". " + pgErr( d->q ) );
974         return;
975     }
976 
977     Row * r = d->q->nextRow();
978     if ( r ) {
979         d->databaseExists = true;
980 
981         bool warning = false;
982         d->owner = r->getEString( "usename" );
983         EString encoding( r->getEString( "encoding" ) );
984 
985         if ( encoding != "UNICODE" && encoding != "UTF8" ) {
986             // If someone is using SQL_ASCII, it's probably... us.
987             if ( encoding == "SQL_ASCII" )
988                 warning = true;
989 
990             fprintf( stderr,
991                      " - Database %s exists, but it has encoding "
992                      "%s instead of UTF8/UNICODE.\n"
993                      "   (That will need to be fixed by hand.)\n",
994                      dbname->quoted().cstr(), encoding.cstr() );
995 
996             if ( !warning ) {
997                 d->failed = true;
998                 return;
999             }
1000         }
1001     }
1002 
1003     d->nextState();
1004 }
1005 
1006 
createUser()1007 void createUser()
1008 {
1009     if ( !d->q ) {
1010         d->q = new Query( "select usename::text from pg_catalog.pg_user "
1011                           "where usename=$1", d );
1012         d->q->bind( 1, *dbuser );
1013         d->q->execute();
1014     }
1015 
1016     if ( !d->u ) {
1017         if ( !d->q->done() )
1018             return;
1019 
1020         if ( d->q->failed() ) {
1021             d->error( "Couldn't check user " + *dbuser + ". " + pgErr( d->q ) );
1022             return;
1023         }
1024 
1025         Row * r = d->q->nextRow();
1026         if ( !r ) {
1027             EString create( "create user " + *dbuser + " with encrypted "
1028                            "password " + dbpass->quoted( '\'' ) );
1029 
1030             if ( report ) {
1031                 todo++;
1032                 printf( " - Create a PostgreSQL user named '%s'.\n"
1033                         "   As user %s, run:\n\n"
1034                         "%s -d template1 -qc \"%s\"\n\n",
1035                         dbuser->cstr(), PGUSER, PSQL, create.cstr() );
1036             }
1037             else {
1038                 if ( !silent )
1039                     printf( "Creating the '%s' PostgreSQL user.\n",
1040                             dbuser->cstr() );
1041                 d->u = new Query( create, d );
1042                 d->u->execute();
1043             }
1044         }
1045         else {
1046             if ( generatedPass )
1047                 *dbpass = "(database user password here)";
1048         }
1049     }
1050 
1051     if ( d->u ) {
1052         if ( !d->u->done() )
1053             return;
1054 
1055         if ( d->u->failed() ) {
1056             d->error( "Couldn't create database user " + dbuser->quoted() +
1057                       " (" + pgErr( d->u ) + ").\nPlease create it by hand "
1058                       "and re-run the installer." );
1059             return;
1060         }
1061     }
1062 
1063     d->nextState();
1064 }
1065 
1066 
createSuperuser()1067 void createSuperuser()
1068 {
1069     if ( !d->q ) {
1070         d->q = new Query( "select usename::text from pg_catalog.pg_user "
1071                           "where usename=$1", d );
1072         d->q->bind( 1, *dbowner );
1073         d->q->execute();
1074     }
1075 
1076     if ( !d->u ) {
1077         if ( !d->q->done() )
1078             return;
1079 
1080         if ( d->q->failed() ) {
1081             d->error( "Couldn't check user " + *dbowner + ". " +
1082                       pgErr( d->q ) );
1083             return;
1084         }
1085 
1086         Row * r = d->q->nextRow();
1087         if ( !r ) {
1088             EString create( "create user " + *dbowner + " with encrypted "
1089                            "password " + dbownerpass->quoted( '\'' ) );
1090 
1091             if ( report ) {
1092                 todo++;
1093                 printf( " - Create a PostgreSQL user named '%s'.\n"
1094                         "   As user %s, run:\n\n"
1095                         "%s -d template1 -qc \"%s\"\n\n",
1096                         dbowner->cstr(), PGUSER, PSQL, create.cstr() );
1097             }
1098             else {
1099                 if ( !silent )
1100                     printf( "Creating the '%s' PostgreSQL user.\n",
1101                             dbowner->cstr() );
1102                 d->u = new Query( create, d );
1103                 d->u->execute();
1104             }
1105         }
1106         else {
1107             if ( generatedOwnerPass )
1108                 *dbownerpass = "(database owner password here)";
1109         }
1110     }
1111 
1112     if ( d->u ) {
1113         if ( !d->u->done() )
1114             return;
1115 
1116         if ( d->u->failed() ) {
1117             d->error( "Couldn't create database user " + dbuser->quoted() +
1118                       " (" + pgErr( d->u ) + ").\nPlease create it by hand "
1119                       "and re-run the installer." );
1120             return;
1121         }
1122     }
1123 
1124     d->nextState();
1125 }
1126 
1127 
1128 // If the database does not exist (the common case), we create it, add
1129 // plpgsql, create a namespace (if one is specified), create database
1130 // objects and grant privileges. If the database DOES exist, we don't
1131 // need to create it, but we check everything else.
1132 
createDatabase()1133 void createDatabase()
1134 {
1135     if ( d->databaseExists ) {
1136         d->nextState();
1137         return;
1138     }
1139 
1140     if ( !d->u ) {
1141         EString create( "create database " + *dbname + " with owner " +
1142                        *dbowner + " encoding 'UNICODE'" );
1143         if ( report ) {
1144             todo++;
1145             printf( " - Create a database named '%s'.\n"
1146                     "   As user %s, run:\n\n"
1147                     "%s -d template1 -qc \"%s\"\n\n",
1148                     dbname->cstr(), PGUSER, PSQL, create.cstr() );
1149         }
1150         else {
1151             if ( !silent )
1152                 printf( "Creating the '%s' database.\n", dbname->cstr() );
1153             d->u = new Query( create, d );
1154             d->u->execute();
1155         }
1156     }
1157 
1158     if ( d->u ) {
1159         if ( !d->u->done() )
1160             return;
1161 
1162         if ( d->u->failed() ) {
1163             d->error( "Couldn't create database " + dbname->quoted() +
1164                       "(" + pgErr( d->u ) + ").\nPlease create it by "
1165                       "hand and re-run the installer." );
1166             return;
1167         }
1168     }
1169 
1170     d->nextState();
1171 }
1172 
1173 
1174 // We must connect to the database for the next few tests, but we can do
1175 // so only if it existed before, or we just created it. Otherwise we'll
1176 // just report what we would have done and carry on.
1177 
createLang()1178 void createLang()
1179 {
1180     if ( !d->databaseExists && report ) {
1181         todo++;
1182         printf( " - Add PL/PgSQL to the '%s' database.\n"
1183                 "   As user %s, run:\n\n"
1184                 "createlang plpgsql %s\n\n",
1185                 dbname->cstr(), PGUSER, dbname->cstr() );
1186         d->nextState();
1187         return;
1188     }
1189 
1190     if ( !d->q ) {
1191         Database::disconnect();
1192         connectToDb( *dbname );
1193 
1194         d->q = new Query( "select lanname::text from pg_catalog.pg_language "
1195                           "where lanname='plpgsql'", d );
1196         d->q->execute();
1197     }
1198 
1199     if ( !d->u ) {
1200         if ( !d->q->done() )
1201             return;
1202 
1203         if ( d->q->failed() ) {
1204             d->error( "Couldn't check for plpgsql. " + pgErr( d->q ) );
1205             return;
1206         }
1207 
1208         Row * r = d->q->nextRow();
1209         if ( !r ) {
1210             EString create( "create language plpgsql" );
1211 
1212             if ( report ) {
1213                 todo++;
1214                 printf( " - Add PL/PgSQL to the '%s' database.\n"
1215                         "   As user %s, run:\n\n"
1216                         "createlang plpgsql %s\n\n",
1217                         dbname->cstr(), PGUSER, dbname->cstr() );
1218             }
1219             else {
1220                 if ( !silent )
1221                     printf( "Adding PL/PgSQL to the '%s' database.\n",
1222                             dbname->cstr() );
1223                 d->u = new Query( create, d );
1224                 d->u->execute();
1225             }
1226         }
1227     }
1228 
1229     if ( d->u ) {
1230         if ( !d->u->done() )
1231             return;
1232 
1233         if ( d->u->failed() ) {
1234             d->error( "Couldn't add PL/PgSQL to the " + dbname->quoted() +
1235                       "database (" + pgErr( d->u ) + ").\nPlease do it by "
1236                       "hand and re-run the installer." );
1237             return;
1238         }
1239     }
1240 
1241     d->nextState();
1242 }
1243 
1244 
1245 // We need the citext extension.
1246 
createExtensions()1247 void createExtensions()
1248 {
1249     if ( !d->databaseExists && report ) {
1250         todo++;
1251         printf( " - Add the citext extension to the '%s' database.\n"
1252                 "   As user %s, run:\n\n"
1253                 "psql %s -c 'create extension citext'\n\n",
1254                 dbname->cstr(), PGUSER, dbname->cstr() );
1255         d->nextState();
1256         return;
1257     }
1258 
1259     if ( !d->q ) {
1260         Database::disconnect();
1261         connectToDb( *dbname );
1262 
1263         d->q = new Query( "select extname::text from pg_catalog.pg_extension "
1264                           "where extname='citext'", d );
1265         d->q->execute();
1266     }
1267 
1268     if ( !d->u ) {
1269         if ( !d->q->done() )
1270             return;
1271 
1272         if ( d->q->failed() ) {
1273             d->error( "Couldn't check for citext. " + pgErr( d->q ) );
1274             return;
1275         }
1276 
1277         Row * r = d->q->nextRow();
1278         if ( !r ) {
1279             EString create( "create extension citext" );
1280 
1281             if ( report ) {
1282                 todo++;
1283                 printf( " - Add citext to the '%s' database.\n"
1284                         "   As user %s, run:\n\n"
1285                         "psql %s -c 'create extension citext'\n\n",
1286                         dbname->cstr(), PGUSER, dbname->cstr() );
1287             }
1288             else {
1289                 if ( !silent )
1290                     printf( "Adding citext to the '%s' database.\n",
1291                             dbname->cstr() );
1292                 d->u = new Query( create, d );
1293                 d->u->execute();
1294             }
1295         }
1296     }
1297 
1298     if ( d->u ) {
1299         if ( !d->u->done() )
1300             return;
1301 
1302         if ( d->u->failed() ) {
1303             d->error( "Couldn't add citext to the " + dbname->quoted() +
1304                       "database (" + pgErr( d->u ) + ").\nPlease do it by "
1305                       "hand and re-run the installer." );
1306             return;
1307         }
1308     }
1309 
1310     d->nextState();
1311 }
1312 
1313 
1314 // If the user specified a schema with -S, we need to check if it
1315 // exists and create it if it doesn't.
1316 //
1317 // We call our arrangement of database objects a schema (cf. schema.pg),
1318 // but now we're adding support for Postgres schemata; so there's some
1319 // confusion between the two terms here. I try to refer to the latter
1320 // as namespaces in the code, but commands still refer to "schema".
1321 
createNamespace()1322 void createNamespace()
1323 {
1324     if ( !privateSchema ) {
1325         d->namespaceExists = true;
1326         d->nextState();
1327         return;
1328     }
1329 
1330     EString create( "create schema " + *dbschema +
1331                    " authorization " + *dbowner );
1332 
1333     if ( !d->databaseExists && report ) {
1334         todo++;
1335         printf( " - Create a schema named '%s'.\n"
1336                 "   As user %s, run:\n\n"
1337                 "%s -d %s -qc \"%s\"\n\n",
1338                 dbschema->cstr(), PGUSER, PSQL, dbname->cstr(), create.cstr() );
1339         d->nextState();
1340         return;
1341     }
1342 
1343     if ( !d->q ) {
1344         d->q = new Query( "select nspname::text from pg_catalog.pg_namespace "
1345                           "where nspname=$1", d );
1346         d->q->bind( 1, *dbschema );
1347         d->q->execute();
1348     }
1349 
1350     if ( !d->u ) {
1351         if ( !d->q->done() )
1352             return;
1353 
1354         if ( d->q->failed() ) {
1355             d->error( "Couldn't check schema " + *dbschema + ". " +
1356                       pgErr( d->q ) );
1357             return;
1358         }
1359 
1360         Row * r = d->q->nextRow();
1361         if ( !r ) {
1362             if ( report ) {
1363                 todo++;
1364                 printf( " - Create a schema named '%s'.\n"
1365                         "   As user %s, run:\n\n"
1366                         "%s -d template1 -qc \"%s\"\n\n",
1367                         dbschema->cstr(), PGUSER, PSQL, create.cstr() );
1368             }
1369             else {
1370                 if ( !silent )
1371                     printf( "Creating the '%s' schema.\n", dbschema->cstr() );
1372                 d->u = new Query( create, d );
1373                 d->u->execute();
1374             }
1375         }
1376         else {
1377             d->namespaceExists = true;
1378         }
1379     }
1380 
1381     if ( d->u ) {
1382         if ( !d->u->done() )
1383             return;
1384 
1385         if ( d->u->failed() ) {
1386             d->error( "Couldn't create schema " + dbschema->quoted() + " "
1387                       "in database " + dbname->quoted() + "(" +
1388                       pgErr( d->u ) + ").\nPlease create it by hand and "
1389                       "re-run the installer." );
1390             return;
1391         }
1392     }
1393 
1394     d->nextState();
1395 }
1396 
1397 
1398 // Before we create database objects, we check ownership: if a schema
1399 // was specified, the dbowner should be its owner; if not, it should
1400 // own the database we're installing into.
1401 
checkOwnership()1402 void checkOwnership()
1403 {
1404     // If we just created either database or schema, the owner is
1405     // already set correctly, and we don't need to do anything.
1406 
1407     if ( !( d->databaseExists && d->namespaceExists ) ) {
1408         d->nextState();
1409         return;
1410     }
1411 
1412     // If a schema is specified, check its owner and decide what to do.
1413     // We could do the same for the database if a schema is not given,
1414     // but checkEncoding() already set d->owner, which we can use.
1415 
1416     if ( !d->q && !d->u ) {
1417         if ( privateSchema ) {
1418             d->q = new Query( "select usename::text "
1419                               "from pg_namespace n join pg_user u on "
1420                               "(n.nspowner=u.usesysid) where nspname=$1", d );
1421             d->q->bind( 1, *dbschema );
1422             d->q->execute();
1423         }
1424         else if ( d->owner != *dbowner ) {
1425             EString alter( "alter database " + *dbname +
1426                           " owner to " + *dbowner );
1427 
1428             if ( report ) {
1429                 todo++;
1430                 printf( " - Alter owner of database '%s' from '%s' to '%s'.\n"
1431                         "   As user %s, run:\n\n"
1432                         "%s -d template1 -qc \"%s\"\n\n",
1433                         dbname->cstr(), d->owner.cstr(), dbowner->cstr(),
1434                         PGUSER, PSQL, alter.cstr() );
1435             }
1436             else {
1437                 if ( !silent )
1438                     printf( "Altering ownership of database '%s' to '%s'.\n",
1439                             dbname->cstr(), dbowner->cstr() );
1440                 d->u = new Query( alter, d );
1441                 d->u->execute();
1442             }
1443         }
1444     }
1445 
1446     if ( d->q && !d->u ) {
1447         if ( !d->q->done() )
1448             return;
1449 
1450         Row * r = d->q->nextRow();
1451         if ( d->q->failed() || !r ) {
1452             d->error( "Couldn't check ownership of schema " + *dbschema + ". " +
1453                       pgErr( d->q ) );
1454             return;
1455         }
1456 
1457         EString owner( r->getEString( "usename" ) );
1458         if ( owner != *dbowner ) {
1459             EString alter( "alter schema " + *dbschema +
1460                           " owner to " + *dbowner );
1461 
1462             if ( report ) {
1463                 todo++;
1464                 printf( " - Alter owner of schema '%s' from '%s' to '%s'.\n"
1465                         "   As user %s, run:\n\n"
1466                         "%s -d %s -qc \"%s\"\n\n",
1467                         dbschema->cstr(), owner.cstr(), dbowner->cstr(),
1468                         PGUSER, PSQL, dbname->cstr(), alter.cstr() );
1469             }
1470             else {
1471                 if ( !silent )
1472                     printf( "Altering ownership of schema '%s' to '%s'.\n",
1473                             dbschema->cstr(), dbowner->cstr() );
1474                 d->u = new Query( alter, d );
1475                 d->u->execute();
1476             }
1477         }
1478     }
1479 
1480     if ( d->u ) {
1481         if ( !d->u->done() )
1482             return;
1483 
1484         if ( d->u->failed() ) {
1485             EString s( "Couldn't alter owner of " );
1486             if ( privateSchema )
1487                 s.append( "schema " + dbschema->quoted( '\'' ) );
1488             else
1489                 s.append( "database " + dbname->quoted( '\'' ) );
1490             s.append( " to " + dbowner->quoted( '\'' ) );
1491             s.append( " (" );
1492             s.append( pgErr( d->u ) );
1493             s.append( ").\n" );
1494             d->error( s + "Please set the owner by hand and re-run the "
1495                       "installer." );
1496             return;
1497         }
1498     }
1499 
1500     d->nextState();
1501 }
1502 
1503 
grantUsage()1504 void grantUsage()
1505 {
1506     if ( !privateSchema ) {
1507         d->nextState();
1508         return;
1509     }
1510 
1511     EString grant( "grant usage on schema " + *dbschema + " to " + *dbuser );
1512 
1513     if ( !d->q ) {
1514         d->q = new Query(
1515             "select has_schema_privilege($1,nspname,'usage') as has_usage "
1516             "from pg_catalog.pg_namespace where nspname=$2", d
1517         );
1518         d->q->bind( 1, *dbuser );
1519         d->q->bind( 2, *dbschema );
1520         d->q->execute();
1521     }
1522 
1523     if ( !d->u ) {
1524         if ( !d->q->done() )
1525             return;
1526 
1527         if ( d->q->failed() ) {
1528             d->error( "Couldn't check usage privileges for schema " +
1529                       dbschema->quoted( '\'' ) + ". " + pgErr( d->q ) );
1530             return;
1531         }
1532 
1533         Row * r = d->q->nextRow();
1534         if ( !r || !r->getBoolean( "has_usage" ) ) {
1535             if ( report ) {
1536                 todo++;
1537                 printf( " - Grant usage on schema '%s' to user '%s'.\n"
1538                         "   As user %s, run:\n\n"
1539                         "%s -d %s -qc \"%s\"\n\n",
1540                         dbschema->cstr(), dbuser->cstr(), PGUSER, PSQL,
1541                         dbname->cstr(), grant.cstr() );
1542             }
1543             else {
1544                 d->u = new Query( grant, d );
1545                 d->u->execute();
1546             }
1547         }
1548     }
1549 
1550     if ( d->u ) {
1551         if ( !d->u->done() )
1552             return;
1553 
1554         if ( d->u->failed() ) {
1555             d->error( "Couldn't grant usage on schema " +
1556                       dbschema->quoted( '\'' ) + " to user " +
1557                       dbuser->quoted( '\'' ) + " (" + pgErr( d->u ) +
1558                       ").\nPlease grant it by hand and re-run the "
1559                       "installer." );
1560             return;
1561         }
1562     }
1563 
1564     d->nextState();
1565 }
1566 
1567 
1568 // Archiveopteryx 2.10 introduced the privilege-separation scheme still
1569 // in use, where aoxsuper owns the database objects and has all rights,
1570 // while servers connects as user aox, which has only selected rights.
1571 // In earlier versions, the oryx user owned everything. This function
1572 // is responsible for doing the one-time conversion to the new scheme.
1573 
splitPrivileges()1574 void splitPrivileges()
1575 {
1576     if ( !( d->databaseExists && d->namespaceExists ) ) {
1577         d->nextState();
1578         return;
1579     }
1580 
1581     if ( !d->q ) {
1582         d->q = new Query( "select tableowner::text from pg_catalog.pg_tables "
1583                           "where tablename=$1 and schemaname=$2", d );
1584         d->q->bind( 1, "messages" );
1585         d->q->bind( 2, *dbschema );
1586         d->q->execute();
1587     }
1588 
1589     if ( !d->u ) {
1590         if ( !d->q->done() )
1591             return;
1592 
1593         if ( d->q->failed() ) {
1594             d->error( "Couldn't check ownership of messages table. " +
1595                       pgErr( d->q ) );
1596             return;
1597         }
1598 
1599         EString owner( *dbowner );
1600         Row * r = d->q->nextRow();
1601         if ( r )
1602             owner = r->getEString( "tableowner" );
1603 
1604         // If the messages table is owned by the user that the servers
1605         // connect as, that's bad. But we have to be careful, because
1606         // people may have dbuser and dbowner set to the same user.
1607 
1608         if ( owner == *dbuser && *dbuser != *dbowner ) {
1609             if ( report ) {
1610                 todo++;
1611                 printf( " - Alter the owner of all database objects "
1612                         "to '%s'.\n\n", dbowner->cstr() );
1613             }
1614             else {
1615                 d->q = new Query(
1616                     "create function exec(text) returns int "
1617                     "language 'plpgsql' as "
1618                     "$$begin execute $1;return 0;end;$$", d
1619                 );
1620                 d->q->execute();
1621 
1622                 d->u = new Query(
1623                     "select "
1624                     "exec('ALTER TABLE '||c.relname||' OWNER TO '||$1) "
1625                     "from pg_catalog.pg_class c join "
1626                     "pg_catalog.pg_namespace n on (n.oid=c.relnamespace) "
1627                     "where n.nspname='public' and c.relkind='r' and "
1628                     "pg_catalog.pg_table_is_visible(c.oid)", d
1629                 );
1630                 d->u->bind( 1, *dbowner );
1631                 d->u->execute();
1632 
1633                 // We have at least one unlinked sequence (bodypart_ids)
1634                 // whose ownership would not have been altered by the
1635                 // query above.
1636 
1637                 d->w = new Query(
1638                     "select "
1639                     "exec('ALTER TABLE '||c.relname||' OWNER TO '||$1) "
1640                     "from pg_catalog.pg_class c join "
1641                     "pg_catalog.pg_namespace n on (n.oid=c.relnamespace) "
1642                     "where n.nspname='public' and c.relkind='S' and "
1643                     "pg_catalog.pg_table_is_visible(c.oid)", d
1644                 );
1645                 d->w->bind( 1, *dbowner );
1646                 d->w->execute();
1647 
1648                 d->q = new Query( "drop function exec(text)", d );
1649                 d->q->execute();
1650             }
1651         }
1652         else if ( owner != *dbowner ) {
1653             d->error( "The messages table is not owned by user " +
1654                       dbuser->quoted( '\'' ) + " or by user " +
1655                       dbowner->quoted( '\'' ) + ".\n"
1656                       "This configuration is unsupported. Please contact "
1657                       "info@aox.org for help." );
1658             return;
1659         }
1660     }
1661 
1662     if ( d->u ) {
1663         if ( !d->u->done() || !d->w->done() || !d->q->done() )
1664             return;
1665 
1666         Query * q = 0;
1667         if ( d->u->failed() )
1668             q = d->u;
1669         else if ( d->w->failed() )
1670             q = d->w;
1671         else if ( d->q->failed() )
1672             q = d->q;
1673 
1674         if ( q ) {
1675             d->error( "Couldn't alter ownership of objects in the database " +
1676                       dbname->quoted( '\'' ) + ". " + pgErr( q ) );
1677             return;
1678         }
1679     }
1680 
1681     d->nextState();
1682 }
1683 
1684 
1685 // At this point, we know that the aox/aoxsuper users exist, that the
1686 // aox database exists, that any given schema exists, that PL/PgSQL is
1687 // available, and that the database/schema have the right ownership.
1688 
createSchema()1689 void createSchema()
1690 {
1691     // This is what we need to feed to psql to create the schema.
1692 
1693     EString cmd( "\\set ON_ERROR_STOP\n"
1694                 "SET SESSION AUTHORIZATION " + *dbowner + ";\n"
1695                 "SET client_min_messages TO 'ERROR';\n" );
1696 
1697     if ( privateSchema )
1698         cmd.append( "SET search_path TO " +
1699                     dbschema->quoted( '\'' ) + ";\n" );
1700 
1701     cmd.append( "\\i " LIBDIR "/schema.pg\n"
1702                 "\\i " LIBDIR "/flag-names\n"
1703                 "\\i " LIBDIR "/field-names\n"
1704                 "\\i " LIBDIR "/downgrades\n" );
1705 
1706     // And this function decides whether we need to invoke psql at all,
1707     // based on whether we can find the "mailstore" table.
1708 
1709     if ( report && !( d->databaseExists && d->namespaceExists ) ) {
1710         todo++;
1711         printf( " - Load the database schema.\n   "
1712                 "As user %s, run:\n\n"
1713                 "%s %s -f - <<PSQL;\n%sPSQL\n\n",
1714                 PGUSER, PSQL, dbname->cstr(), cmd.cstr() );
1715         d->nextState();
1716         return;
1717     }
1718 
1719     if ( !d->q ) {
1720         d->ssa = new Query( "set session authorization " + *dbowner, d );
1721         d->ssa->execute();
1722 
1723         if ( privateSchema ) {
1724             d->ssp = new Query( "set search_path to " +
1725                                 dbschema->quoted( '\'' ), d );
1726             d->ssp->execute();
1727         }
1728 
1729         d->q = new Query( "select tablename::text from pg_catalog.pg_tables "
1730                           "where tablename=$1 and schemaname=$2", d );
1731         d->q->bind( 1, "mailstore" );
1732         d->q->bind( 2, *dbschema );
1733         d->q->execute();
1734     }
1735 
1736     if ( !d->u ) {
1737         if ( !d->ssa->done() || ( d->ssp && !d->ssp->done() ) ||
1738              !d->q->done() )
1739             return;
1740 
1741         EString s;
1742         Query * q = 0;
1743 
1744         if ( d->ssa->failed() ) {
1745             q = d->ssa;
1746             s.append( "authenticate as user " );
1747             s.append( dbowner->quoted( '\'' ) );
1748         }
1749         else if ( d->ssp && d->ssp->failed() ) {
1750             q = d->ssp;
1751             s.append( "set search_path to " );
1752             s.append( dbschema->quoted( '\'' ) );
1753         }
1754         else if ( d->q->failed() ) {
1755             q = d->q;
1756             s.append( "query database " );
1757             s.append( dbname->quoted( '\'' ) );
1758         }
1759 
1760         if ( q ) {
1761             if ( report ) {
1762                 todo++;
1763                 printf( " - May need to load the database schema.\n"
1764                         "   (Couldn't %s to make sure it's needed. %s.)\n\n",
1765                         s.cstr(), pgErr( q ).cstr() );
1766             }
1767             else {
1768                 d->error( "Couldn't " + s + " to see if the schema needs "
1769                           "to be loaded. " + pgErr( q ) );
1770             }
1771             d->state = Done;
1772             return;
1773         }
1774 
1775         Row * r = d->q->nextRow();
1776         if ( !r ) {
1777             if ( report ) {
1778                 todo++;
1779                 printf( " - Load the database schema.\n   "
1780                         "As user %s, run:\n\n"
1781                         "%s %s -f - <<PSQL;\n%sPSQL\n\n",
1782                         PGUSER, PSQL, dbname->cstr(), cmd.cstr() );
1783             }
1784             else {
1785                 if ( !silent )
1786                     printf( "Loading the database schema.\n" );
1787                 if ( psql( cmd ) < 0 )
1788                     d->failed = true;
1789             }
1790         }
1791         else {
1792             d->mailstoreExists = true;
1793         }
1794     }
1795 
1796     d->nextState();
1797 }
1798 
1799 
1800 // If the schema already exists, we might need to upgrade it to the
1801 // latest version.
1802 
upgradeSchema()1803 void upgradeSchema()
1804 {
1805     if ( !d->mailstoreExists ) {
1806         d->nextState();
1807         return;
1808     }
1809 
1810     if ( !d->q ) {
1811         d->q = new Query( "select revision from mailstore", d );
1812         d->q->execute();
1813     }
1814 
1815     if ( !d->u ) {
1816         if ( !d->q->done() )
1817             return;
1818 
1819         // This query may fail even if the pg_class query for mailstore
1820         // above succeeded, because we (aoxsuper) may not have rights to
1821         // the schema or the mailstore table.
1822 
1823         Row * r = d->q->nextRow();
1824         if ( d->q->failed() || !r ) {
1825             if ( report ) {
1826                 todo++;
1827                 printf( " - May need to upgrade the database schema.\n"
1828                         "   (Couldn't query mailstore table to make sure "
1829                         "it's needed.)\n\n" );
1830             }
1831             else {
1832                 EString s( "Couldn't query database " );
1833                 s.append( dbname->quoted( '\'' ) );
1834                 s.append( " to see if the schema needs to be upgraded." );
1835                 if ( d->q->failed() ) {
1836                     s.append( " " );
1837                     s.append( pgErr( d->q ) );
1838                 }
1839                 d->error( s );
1840             }
1841             d->state = Done;
1842             return;
1843         }
1844 
1845         uint revision = r->getInt( "revision" );
1846 
1847         if ( revision > Database::currentRevision() ) {
1848             EString v( Configuration::compiledIn( Configuration::Version ) );
1849             fprintf( stderr, "The schema in database '%s' (revision #%d) "
1850                      "is newer than this version of Archiveopteryx (%s) "
1851                      "recognises (up to #%d).\n", dbname->cstr(), revision,
1852                      v.cstr(), Database::currentRevision() );
1853             d->failed = true;
1854             return;
1855         }
1856         else if ( revision < Database::currentRevision() ) {
1857             if ( report ) {
1858                 todo++;
1859                 printf( " - Upgrade the database schema.\n   "
1860                         "(Try \"aox upgrade schema -n\" to see "
1861                         "what would happen).\n\n" );
1862             }
1863             else {
1864                 if ( !silent )
1865                     printf( "Upgrading the database schema.\n" );
1866                 Schema * s = new Schema( d, true, true );
1867                 d->u = s->result();
1868                 s->execute();
1869             }
1870         }
1871     }
1872 
1873     if ( d->u ) {
1874         if ( !d->u->done() )
1875             return;
1876 
1877         if ( d->u->failed() ) {
1878             d->error( "Couldn't upgrade Archiveopteryx schema in database " +
1879                       dbname->quoted( '\'' ) + " (" + pgErr( d->u ) + ").\n"
1880                       "Please run \"aox upgrade schema -n\" by hand.\n" );
1881             return;
1882         }
1883     }
1884 
1885     d->nextState();
1886 }
1887 
1888 
1889 // Make sure the aox user has exactly those privileges it needs.
1890 
grantPrivileges()1891 void grantPrivileges()
1892 {
1893     if ( report ) {
1894         todo++;
1895         printf( " - Grant privileges to user '%s'.\n   "
1896                 "(Run \"aox grant privileges -n %s\" to see "
1897                 "what would happen).\n\n",
1898                 dbuser->cstr(), dbuser->cstr() );
1899         d->nextState();
1900         return;
1901     }
1902 
1903     if ( !d->t ) {
1904         if ( !silent )
1905             printf( "Granting database privileges.\n" );
1906         d->t = new Transaction( d );
1907         d->g = new Granter( *dbuser, d->t );
1908         d->g->execute();
1909     }
1910 
1911     d->t->commit();
1912 
1913     if ( !d->t->done() )
1914         return;
1915 
1916     if ( d->t->failed() ) {
1917         d->error( "Couldn't grant privileges to user " +
1918                   dbuser->quoted( '\'' ) + " (PostgreSQL error: " +
1919                   d->t->error() + ").\nPlease run \"aox grant "
1920                   "privileges -n\" by hand.\n" );
1921         return;
1922     }
1923 
1924     d->nextState();
1925 }
1926 
1927 
configFile()1928 void configFile()
1929 {
1930     setreuid( 0, 0 );
1931 
1932     EString p( *dbpass );
1933     if ( p.contains( " " ) )
1934         p = "'" + p + "'";
1935 
1936     EString cf( Configuration::configFile() );
1937     EString v( Configuration::compiledIn( Configuration::Version ) );
1938     EString intro(
1939         "# Archiveopteryx configuration. See archiveopteryx.conf(5) or\n"
1940         "# http://aox.org/conf/ for details and other variables.\n"
1941         "# Automatically generated while installing Archiveopteryx "
1942         + v + ".\n\n"
1943     );
1944 
1945     EString dbhost( "db-address = " + *dbaddress + "\n" );
1946     if ( dbaddress->startsWith( "/" ) )
1947         dbhost.append( "# " );
1948     dbhost.append( "db-port = " + fn( dbport ) + "\n" );
1949 
1950     EString name( "db-name = " + *dbname + "\n" );
1951 
1952     EString schema;
1953     if ( privateSchema ) {
1954         schema.append( "db-schema = " );
1955         schema.append( *dbschema );
1956         schema.append( "\n" );
1957     }
1958 
1959     EString cfg(
1960         dbhost + name + schema +
1961         "db-user = " + *dbuser + "\n"
1962         "db-password = " + p + "\n\n"
1963     );
1964 
1965     EString other(
1966         "# Uncomment the next line to log more (or set it to debug for even more).\n"
1967         "# log-level = info\n"
1968         "\n"
1969         "# Specify the hostname if Archiveopteryx gets it wrong at runtime.\n"
1970         "# (We suggest not using the name \"localhost\".)\n"
1971         "# hostname = fully.qualified.hostname\n"
1972         "\n"
1973         "# If soft-bounce is set, configuration problems will not cause mail\n"
1974         "# loss. Instead, the mail will be queued by the MTA. Uncomment the\n"
1975         "# following when you are confident that mail delivery works.\n"
1976         "# soft-bounce = disabled\n"
1977         "\n"
1978         "# Change the following to accept LMTP connections on an address\n"
1979         "# other than the default localhost.\n"
1980         "# lmtp-address = 192.0.2.1\n"
1981         "# lmtp-port = 2026\n"
1982         "\n"
1983         "# Uncomment the following to support subaddressing: foo+bar@example.org\n"
1984         "# use-subaddressing = true\n"
1985         "\n"
1986         "# Uncomment the following to keep a filesystem copy of all messages\n"
1987         "# that couldn't be parsed and delivered into the database.\n"
1988         "# message-copy = errors\n"
1989         "# message-copy-directory = /usr/local/archiveopteryx/messages\n"
1990         "\n"
1991         "# Uncomment the following ONLY if necessary for debugging.\n"
1992         "# security = off\n"
1993         "# use-tls = false\n"
1994         "\n"
1995         "# Uncomment the next line to use your own TLS certificate.\n"
1996         "# tls-certificate = /usr/local/archiveopteryx/...\n"
1997         "\n"
1998         "# Uncomment the following to reject all plaintext passwords.\n"
1999         "# allow-plaintext-passwords = never\n"
2000         "# Uncomment the following to require TLS to read or send mail.\n"
2001         "# allow-plaintext-access = never\n"
2002         "\n"
2003         "# There are almost a hundred other configuration variables.\n"
2004         "# The ones above are only what many people will want to change during\n"
2005         "# installation. There are full lists at http://aox.org/conf/ and in\n"
2006         "# the archiveopteryx.conf manual page.\n"
2007     );
2008 
2009     if ( exists( cf ) && generatedPass ) {
2010         fprintf( stderr, "Not overwriting existing %s!\n\n"
2011                  "%s should contain:\n\n%s\n", cf.cstr(), cf.cstr(),
2012                  cfg.cstr() );
2013     }
2014     else if ( !exists( cf ) ) {
2015         if ( report ) {
2016             todo++;
2017             printf( " - Generate a default configuration file.\n"
2018                     "   %s should contain:\n\n%s\n", cf.cstr(), cfg.cstr() );
2019         }
2020         else {
2021             File f( cf, File::Write, 0600 );
2022             if ( !f.valid() ) {
2023                 fprintf( stderr, "Could not open %s for writing.\n",
2024                          cf.cstr() );
2025                 fprintf( stderr, "%s should contain:\n\n%s\n\n",
2026                          cf.cstr(), cfg.cstr() );
2027                 exit( -1 );
2028             }
2029             else {
2030                 if ( !silent )
2031                     printf( "Generating default %s\n", cf.cstr() );
2032                 f.write( intro );
2033                 f.write( cfg );
2034                 f.write( other );
2035             }
2036         }
2037     }
2038 
2039     superConfig();
2040 }
2041 
2042 
superConfig()2043 void superConfig()
2044 {
2045     EString p( *dbownerpass );
2046     if ( p.contains( " " ) )
2047         p = "'" + p + "'";
2048 
2049     EString cf( Configuration::compiledIn( Configuration::ConfigDir ) );
2050     cf.append( "/aoxsuper.conf" );
2051 
2052     EString v( Configuration::compiledIn( Configuration::Version ) );
2053     EString intro(
2054         "# Archiveopteryx configuration. See aoxsuper.conf(5) "
2055         "for details.\n"
2056         "# Automatically generated while installing Archiveopteryx "
2057         + v + ".\n\n"
2058     );
2059     EString cfg(
2060         "# Security note: Anyone who can read this password can do\n"
2061         "# anything to the database, including delete all mail.\n"
2062         "db-owner = " + *dbowner + "\n"
2063         "db-owner-password = " + p + "\n"
2064     );
2065 
2066     if ( exists( cf ) && generatedOwnerPass ) {
2067         fprintf( stderr, "Not overwriting existing %s!\n\n"
2068                  "%s should contain:\n\n%s\n", cf.cstr(), cf.cstr(),
2069                  cfg.cstr() );
2070     }
2071     else if ( !exists( cf ) ) {
2072         if ( report ) {
2073             todo++;
2074             printf( " - Generate the privileged configuration file.\n"
2075                     "   %s should contain:\n\n%s\n", cf.cstr(), cfg.cstr() );
2076         }
2077         else {
2078             File f( cf, File::Write, 0400 );
2079             if ( !f.valid() ) {
2080                 fprintf( stderr, "Could not open %s for writing.\n\n",
2081                          cf.cstr() );
2082                 fprintf( stderr, "%s should contain:\n\n%s\n",
2083                          cf.cstr(), cfg.cstr() );
2084                 exit( -1 );
2085             }
2086             else {
2087                 if ( !silent )
2088                     printf( "Generating default %s\n", cf.cstr() );
2089                 f.write( intro );
2090                 f.write( cfg );
2091             }
2092         }
2093     }
2094 
2095     permissions();
2096 }
2097 
2098 
permissions()2099 void permissions()
2100 {
2101     int n = 0;
2102     struct stat st;
2103 
2104     struct passwd * p = getpwnam( AOXUSER );
2105     struct group * g = getgrnam( AOXGROUP );
2106 
2107     // This should never happen, but I'm feeling paranoid.
2108     if ( !report && !( p && g ) ) {
2109         fprintf( stderr, "getpwnam(AOXUSER)/getgrnam(AOXGROUP) failed "
2110                  "in non-reporting mode.\n" );
2111         exit( -1 );
2112     }
2113 
2114     EString cf( Configuration::configFile() );
2115 
2116     // If archiveopteryx.conf doesn't exist, or has the wrong ownership
2117     // or permissions:
2118 
2119     n = stat( cf.cstr(), &st );
2120     if ( n != 0 || !p || !g || st.st_uid != p->pw_uid ||
2121          (gid_t)st.st_gid != (gid_t)g->gr_gid ||
2122          ( st.st_mode & 0777 ) != 0600 )
2123     {
2124         if ( report ) {
2125             todo++;
2126             printf( " - Set permissions and ownership on %s.\n\n"
2127                     "chmod 0600 %s\n"
2128                     "chown %s:%s %s\n\n",
2129                     cf.cstr(), cf.cstr(), AOXUSER, AOXGROUP, cf.cstr() );
2130         }
2131         else {
2132             if ( !silent )
2133                 printf( "Setting ownership and permissions on %s\n",
2134                         cf.cstr() );
2135 
2136             if ( chmod( cf.cstr(), 0600 ) < 0 )
2137                 fprintf( stderr, "Could not \"chmod 0600 %s\" (-%d).\n",
2138                          cf.cstr(), errno );
2139 
2140             if ( chown( cf.cstr(), p->pw_uid, g->gr_gid ) < 0 )
2141                 fprintf( stderr, "Could not \"chown %s:%s %s\" (-%d).\n",
2142                          AOXUSER, AOXGROUP, cf.cstr(), errno );
2143         }
2144     }
2145 
2146     EString scf( Configuration::compiledIn( Configuration::ConfigDir ) );
2147     scf.append( "/aoxsuper.conf" );
2148 
2149     // If aoxsuper.conf doesn't exist, or has the wrong ownership or
2150     // permissions:
2151 
2152     n = stat( scf.cstr(), &st );
2153     if ( n != 0 || st.st_uid != 0 || (gid_t)st.st_gid != (gid_t)0 ||
2154          ( st.st_mode & 0777 ) != 0400 )
2155     {
2156         if ( report ) {
2157             todo++;
2158             printf( " - Set permissions and ownership on %s.\n\n"
2159                     "chmod 0400 %s\n"
2160                     "chown root:root %s\n\n",
2161                     scf.cstr(), scf.cstr(), scf.cstr() );
2162         }
2163         else {
2164             if ( !silent )
2165                 printf( "Setting ownership and permissions on %s\n",
2166                         scf.cstr() );
2167 
2168             if ( chmod( scf.cstr(), 0400 ) < 0 )
2169                 fprintf( stderr, "Could not \"chmod 0400 %s\" (-%d).\n",
2170                          scf.cstr(), errno );
2171 
2172             if ( chown( scf.cstr(), 0, 0 ) < 0 )
2173                 fprintf( stderr, "Could not \"chown root:root %s\" (-%d).\n",
2174                          scf.cstr(), errno );
2175         }
2176     }
2177 
2178     EString mcd( Configuration::text( Configuration::MessageCopyDir ) );
2179 
2180     // If the message-copy-directory exists and has the wrong ownership
2181     // or permissions:
2182     if ( stat( mcd.cstr(), &st ) == 0 &&
2183          ( !( p && g ) ||
2184            ( st.st_uid != p->pw_uid ||
2185              (gid_t)st.st_gid != (gid_t)g->gr_gid ||
2186              ( st.st_mode & S_IRWXU ) != S_IRWXU ) ) )
2187     {
2188         if ( report ) {
2189             todo++;
2190             printf( " - Set permissions and ownership on %s.\n\n"
2191                     "chmod 0700 %s\n"
2192                     "chown %s:%s %s\n\n",
2193                     mcd.cstr(), mcd.cstr(), AOXUSER, AOXGROUP,
2194                     mcd.cstr() );
2195         }
2196         else {
2197             if ( !silent )
2198                 printf( "Setting ownership and permissions on %s\n",
2199                         mcd.cstr() );
2200 
2201             if ( chmod( mcd.cstr(), 0700 ) < 0 )
2202                 fprintf( stderr, "Could not \"chmod 0700 %s\" (-%d).\n",
2203                          mcd.cstr(), errno );
2204 
2205             if ( chown( mcd.cstr(), p->pw_uid, g->gr_gid ) < 0 )
2206                 fprintf( stderr, "Could not \"chown %s:%s %s\" (-%d).\n",
2207                          AOXUSER, AOXGROUP, mcd.cstr(), errno );
2208         }
2209     }
2210 
2211     EString jd( Configuration::text( Configuration::JailDir ) );
2212 
2213     // If the jail directory exists and has the wrong ownership or
2214     // permissions (i.e. we own it or have any rights to it):
2215     if ( stat( jd.cstr(), &st ) == 0 &&
2216          ( ( st.st_uid != 0 &&
2217              !( p && st.st_uid != p->pw_uid ) ) ||
2218            ( st.st_gid != 0 &&
2219              !( g && (gid_t)st.st_gid != (gid_t)g->gr_gid ) ) ||
2220            ( st.st_mode & S_IRWXO ) != 1 ) )
2221     {
2222         if ( report ) {
2223             todo++;
2224             printf( " - Set permissions and ownership on %s.\n\n"
2225                     "chmod 0701 %s\n"
2226                     "chown root:root %s\n\n",
2227                     jd.cstr(), jd.cstr(), jd.cstr() );
2228         }
2229         else {
2230             if ( !silent )
2231                 printf( "Setting ownership and permissions on %s\n",
2232                         jd.cstr() );
2233 
2234             if ( chmod( jd.cstr(), 0701 ) < 0 )
2235                 fprintf( stderr, "Could not \"chmod 0701 %s\" (-%d).\n",
2236                          jd.cstr(), errno );
2237 
2238             if ( chown( jd.cstr(), 0, 0 ) < 0 )
2239                 fprintf( stderr, "Could not \"chown root:root %s\" (%d).\n",
2240                          jd.cstr(), errno );
2241         }
2242     }
2243 
2244     if ( report && todo == 0 )
2245         printf( "(Nothing.)\n" );
2246     else if ( !silent )
2247         printf( "Done.\n" );
2248 
2249     EventLoop::shutdown();
2250 }
2251 
2252 
psql(const EString & cmd)2253 int psql( const EString &cmd )
2254 {
2255     int n;
2256     int fd[2];
2257     pid_t pid = -1;
2258 
2259     EString host( *dbaddress );
2260     EString port( fn( dbport ) );
2261 
2262     if ( dbsocket ) {
2263         EString s( ".s.PGSQL." + port );
2264         uint l = dbsocket->length() - s.length();
2265         host = dbsocket->mid( 0, l-1 );
2266     }
2267 
2268     n = pipe( fd );
2269     if ( n == 0 )
2270         pid = fork();
2271     if ( n == 0 && pid == 0 ) {
2272         if ( ( postgres != 0 && setreuid( postgres, postgres ) < 0 ) ||
2273              dup2( fd[0], 0 ) < 0 ||
2274              close( fd[1] ) < 0 ||
2275              close( fd[0] ) < 0 )
2276             exit( -1 );
2277         if ( silent )
2278             if ( close( 1 ) < 0 || open( "/dev/null", 0 ) != 1 )
2279                 exit( -1 );
2280         execlp( PSQL, PSQL, "-h", host.cstr(), "-p", port.cstr(),
2281                 "-U", PGUSER, dbname->cstr(), "-f", "-",
2282                 (const char *) 0 );
2283         exit( -1 );
2284     }
2285     else {
2286         int status = 0;
2287         if ( pid > 0 ) {
2288             int ignore = write( fd[1], cmd.cstr(), cmd.length() );
2289             ignore = ignore;
2290             (void)close( fd[1] );
2291             waitpid( pid, &status, 0 );
2292         }
2293         if ( pid < 0 || ( WIFEXITED( status ) &&
2294                           WEXITSTATUS( status ) != 0 ) )
2295         {
2296             fprintf( stderr, "Couldn't execute psql.\n" );
2297             if ( WEXITSTATUS( status ) == 255 )
2298                 fprintf( stderr, "(No psql in PATH=%s)\n", getenv( "PATH" ) );
2299             fprintf( stderr, "Please re-run the installer after "
2300                      "doing the following as user %s:\n\n"
2301                      "%s -h %s -p %s %s -f - <<PSQL;\n%sPSQL\n\n",
2302                      PGUSER, PSQL, host.cstr(), port.cstr(),
2303                      dbname->cstr(), cmd.cstr() );
2304             return -1;
2305         }
2306     }
2307 
2308     return 0;
2309 }
2310