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( "select version() as version", 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( "version" ).simplified().section( " ", 2 );
944     if ( v.isEmpty() )
945         v = r->getEString( "version" );
946     bool ok = true;
947     uint version = 10000 * v.section( ".", 1 ).number( &ok ) +
948                    100 * v.section( ".", 2 ).number( &ok ) +
949                    v.section( ".", 3 ).number( &ok );
950     if ( !ok || version < 90100 ) {
951         d->error( "Archiveopteryx requires PostgreSQL 9.1.0 or higher "
952                   "(found only " + v + ")." );
953         return;
954     }
955 
956     d->nextState();
957 }
958 
959 
checkEncoding()960 void checkEncoding()
961 {
962     if ( !d->q ) {
963         d->owner = *dbowner;
964         d->q = new Query( "select usename::text, "
965                           "pg_encoding_to_char(encoding)::text as encoding "
966                           "from pg_database d join pg_user u "
967                           "on (d.datdba=u.usesysid) where datname=$1", d );
968         d->q->bind( 1, *dbname );
969         d->q->execute();
970     }
971 
972     if ( !d->q->done() )
973         return;
974 
975     if ( d->q->failed() ) {
976         d->error( "Couldn't check encoding for database " +
977                   dbname->quoted( '\'' ) + ". " + pgErr( d->q ) );
978         return;
979     }
980 
981     Row * r = d->q->nextRow();
982     if ( r ) {
983         d->databaseExists = true;
984 
985         bool warning = false;
986         d->owner = r->getEString( "usename" );
987         EString encoding( r->getEString( "encoding" ) );
988 
989         if ( encoding != "UNICODE" && encoding != "UTF8" ) {
990             // If someone is using SQL_ASCII, it's probably... us.
991             if ( encoding == "SQL_ASCII" )
992                 warning = true;
993 
994             fprintf( stderr,
995                      " - Database %s exists, but it has encoding "
996                      "%s instead of UTF8/UNICODE.\n"
997                      "   (That will need to be fixed by hand.)\n",
998                      dbname->quoted().cstr(), encoding.cstr() );
999 
1000             if ( !warning ) {
1001                 d->failed = true;
1002                 return;
1003             }
1004         }
1005     }
1006 
1007     d->nextState();
1008 }
1009 
1010 
createUser()1011 void createUser()
1012 {
1013     if ( !d->q ) {
1014         d->q = new Query( "select usename::text from pg_catalog.pg_user "
1015                           "where usename=$1", d );
1016         d->q->bind( 1, *dbuser );
1017         d->q->execute();
1018     }
1019 
1020     if ( !d->u ) {
1021         if ( !d->q->done() )
1022             return;
1023 
1024         if ( d->q->failed() ) {
1025             d->error( "Couldn't check user " + *dbuser + ". " + pgErr( d->q ) );
1026             return;
1027         }
1028 
1029         Row * r = d->q->nextRow();
1030         if ( !r ) {
1031             EString create( "create user " + *dbuser + " with encrypted "
1032                            "password " + dbpass->quoted( '\'' ) );
1033 
1034             if ( report ) {
1035                 todo++;
1036                 printf( " - Create a PostgreSQL user named '%s'.\n"
1037                         "   As user %s, run:\n\n"
1038                         "%s -d template1 -qc \"%s\"\n\n",
1039                         dbuser->cstr(), PGUSER, PSQL, create.cstr() );
1040             }
1041             else {
1042                 if ( !silent )
1043                     printf( "Creating the '%s' PostgreSQL user.\n",
1044                             dbuser->cstr() );
1045                 d->u = new Query( create, d );
1046                 d->u->execute();
1047             }
1048         }
1049         else {
1050             if ( generatedPass )
1051                 *dbpass = "(database user password here)";
1052         }
1053     }
1054 
1055     if ( d->u ) {
1056         if ( !d->u->done() )
1057             return;
1058 
1059         if ( d->u->failed() ) {
1060             d->error( "Couldn't create database user " + dbuser->quoted() +
1061                       " (" + pgErr( d->u ) + ").\nPlease create it by hand "
1062                       "and re-run the installer." );
1063             return;
1064         }
1065     }
1066 
1067     d->nextState();
1068 }
1069 
1070 
createSuperuser()1071 void createSuperuser()
1072 {
1073     if ( !d->q ) {
1074         d->q = new Query( "select usename::text from pg_catalog.pg_user "
1075                           "where usename=$1", d );
1076         d->q->bind( 1, *dbowner );
1077         d->q->execute();
1078     }
1079 
1080     if ( !d->u ) {
1081         if ( !d->q->done() )
1082             return;
1083 
1084         if ( d->q->failed() ) {
1085             d->error( "Couldn't check user " + *dbowner + ". " +
1086                       pgErr( d->q ) );
1087             return;
1088         }
1089 
1090         Row * r = d->q->nextRow();
1091         if ( !r ) {
1092             EString create( "create user " + *dbowner + " with encrypted "
1093                            "password " + dbownerpass->quoted( '\'' ) );
1094 
1095             if ( report ) {
1096                 todo++;
1097                 printf( " - Create a PostgreSQL user named '%s'.\n"
1098                         "   As user %s, run:\n\n"
1099                         "%s -d template1 -qc \"%s\"\n\n",
1100                         dbowner->cstr(), PGUSER, PSQL, create.cstr() );
1101             }
1102             else {
1103                 if ( !silent )
1104                     printf( "Creating the '%s' PostgreSQL user.\n",
1105                             dbowner->cstr() );
1106                 d->u = new Query( create, d );
1107                 d->u->execute();
1108             }
1109         }
1110         else {
1111             if ( generatedOwnerPass )
1112                 *dbownerpass = "(database owner password here)";
1113         }
1114     }
1115 
1116     if ( d->u ) {
1117         if ( !d->u->done() )
1118             return;
1119 
1120         if ( d->u->failed() ) {
1121             d->error( "Couldn't create database user " + dbuser->quoted() +
1122                       " (" + pgErr( d->u ) + ").\nPlease create it by hand "
1123                       "and re-run the installer." );
1124             return;
1125         }
1126     }
1127 
1128     d->nextState();
1129 }
1130 
1131 
1132 // If the database does not exist (the common case), we create it, add
1133 // plpgsql, create a namespace (if one is specified), create database
1134 // objects and grant privileges. If the database DOES exist, we don't
1135 // need to create it, but we check everything else.
1136 
createDatabase()1137 void createDatabase()
1138 {
1139     if ( d->databaseExists ) {
1140         d->nextState();
1141         return;
1142     }
1143 
1144     if ( !d->u ) {
1145         EString create( "create database " + *dbname + " with owner " +
1146                        *dbowner + " encoding 'UNICODE'" );
1147         if ( report ) {
1148             todo++;
1149             printf( " - Create a database named '%s'.\n"
1150                     "   As user %s, run:\n\n"
1151                     "%s -d template1 -qc \"%s\"\n\n",
1152                     dbname->cstr(), PGUSER, PSQL, create.cstr() );
1153         }
1154         else {
1155             if ( !silent )
1156                 printf( "Creating the '%s' database.\n", dbname->cstr() );
1157             d->u = new Query( create, d );
1158             d->u->execute();
1159         }
1160     }
1161 
1162     if ( d->u ) {
1163         if ( !d->u->done() )
1164             return;
1165 
1166         if ( d->u->failed() ) {
1167             d->error( "Couldn't create database " + dbname->quoted() +
1168                       "(" + pgErr( d->u ) + ").\nPlease create it by "
1169                       "hand and re-run the installer." );
1170             return;
1171         }
1172     }
1173 
1174     d->nextState();
1175 }
1176 
1177 
1178 // We must connect to the database for the next few tests, but we can do
1179 // so only if it existed before, or we just created it. Otherwise we'll
1180 // just report what we would have done and carry on.
1181 
createLang()1182 void createLang()
1183 {
1184     if ( !d->databaseExists && report ) {
1185         todo++;
1186         printf( " - Add PL/PgSQL to the '%s' database.\n"
1187                 "   As user %s, run:\n\n"
1188                 "createlang plpgsql %s\n\n",
1189                 dbname->cstr(), PGUSER, dbname->cstr() );
1190         d->nextState();
1191         return;
1192     }
1193 
1194     if ( !d->q ) {
1195         Database::disconnect();
1196         connectToDb( *dbname );
1197 
1198         d->q = new Query( "select lanname::text from pg_catalog.pg_language "
1199                           "where lanname='plpgsql'", d );
1200         d->q->execute();
1201     }
1202 
1203     if ( !d->u ) {
1204         if ( !d->q->done() )
1205             return;
1206 
1207         if ( d->q->failed() ) {
1208             d->error( "Couldn't check for plpgsql. " + pgErr( d->q ) );
1209             return;
1210         }
1211 
1212         Row * r = d->q->nextRow();
1213         if ( !r ) {
1214             EString create( "create language plpgsql" );
1215 
1216             if ( report ) {
1217                 todo++;
1218                 printf( " - Add PL/PgSQL to the '%s' database.\n"
1219                         "   As user %s, run:\n\n"
1220                         "createlang plpgsql %s\n\n",
1221                         dbname->cstr(), PGUSER, dbname->cstr() );
1222             }
1223             else {
1224                 if ( !silent )
1225                     printf( "Adding PL/PgSQL to the '%s' database.\n",
1226                             dbname->cstr() );
1227                 d->u = new Query( create, d );
1228                 d->u->execute();
1229             }
1230         }
1231     }
1232 
1233     if ( d->u ) {
1234         if ( !d->u->done() )
1235             return;
1236 
1237         if ( d->u->failed() ) {
1238             d->error( "Couldn't add PL/PgSQL to the " + dbname->quoted() +
1239                       "database (" + pgErr( d->u ) + ").\nPlease do it by "
1240                       "hand and re-run the installer." );
1241             return;
1242         }
1243     }
1244 
1245     d->nextState();
1246 }
1247 
1248 
1249 // We need the citext extension.
1250 
createExtensions()1251 void createExtensions()
1252 {
1253     if ( !d->databaseExists && report ) {
1254         todo++;
1255         printf( " - Add the citext extension to the '%s' database.\n"
1256                 "   As user %s, run:\n\n"
1257                 "psql %s -c 'create extension citext'\n\n",
1258                 dbname->cstr(), PGUSER, dbname->cstr() );
1259         d->nextState();
1260         return;
1261     }
1262 
1263     if ( !d->q ) {
1264         Database::disconnect();
1265         connectToDb( *dbname );
1266 
1267         d->q = new Query( "select extname::text from pg_catalog.pg_extension "
1268                           "where extname='citext'", d );
1269         d->q->execute();
1270     }
1271 
1272     if ( !d->u ) {
1273         if ( !d->q->done() )
1274             return;
1275 
1276         if ( d->q->failed() ) {
1277             d->error( "Couldn't check for citext. " + pgErr( d->q ) );
1278             return;
1279         }
1280 
1281         Row * r = d->q->nextRow();
1282         if ( !r ) {
1283             EString create( "create extension citext" );
1284 
1285             if ( report ) {
1286                 todo++;
1287                 printf( " - Add citext to the '%s' database.\n"
1288                         "   As user %s, run:\n\n"
1289                         "psql %s -c 'create extension citext'\n\n",
1290                         dbname->cstr(), PGUSER, dbname->cstr() );
1291             }
1292             else {
1293                 if ( !silent )
1294                     printf( "Adding citext to the '%s' database.\n",
1295                             dbname->cstr() );
1296                 d->u = new Query( create, d );
1297                 d->u->execute();
1298             }
1299         }
1300     }
1301 
1302     if ( d->u ) {
1303         if ( !d->u->done() )
1304             return;
1305 
1306         if ( d->u->failed() ) {
1307             d->error( "Couldn't add citext to the " + dbname->quoted() +
1308                       "database (" + pgErr( d->u ) + ").\nPlease do it by "
1309                       "hand and re-run the installer." );
1310             return;
1311         }
1312     }
1313 
1314     d->nextState();
1315 }
1316 
1317 
1318 // If the user specified a schema with -S, we need to check if it
1319 // exists and create it if it doesn't.
1320 //
1321 // We call our arrangement of database objects a schema (cf. schema.pg),
1322 // but now we're adding support for Postgres schemata; so there's some
1323 // confusion between the two terms here. I try to refer to the latter
1324 // as namespaces in the code, but commands still refer to "schema".
1325 
createNamespace()1326 void createNamespace()
1327 {
1328     if ( !privateSchema ) {
1329         d->namespaceExists = true;
1330         d->nextState();
1331         return;
1332     }
1333 
1334     EString create( "create schema " + *dbschema +
1335                    " authorization " + *dbowner );
1336 
1337     if ( !d->databaseExists && report ) {
1338         todo++;
1339         printf( " - Create a schema named '%s'.\n"
1340                 "   As user %s, run:\n\n"
1341                 "%s -d %s -qc \"%s\"\n\n",
1342                 dbschema->cstr(), PGUSER, PSQL, dbname->cstr(), create.cstr() );
1343         d->nextState();
1344         return;
1345     }
1346 
1347     if ( !d->q ) {
1348         d->q = new Query( "select nspname::text from pg_catalog.pg_namespace "
1349                           "where nspname=$1", d );
1350         d->q->bind( 1, *dbschema );
1351         d->q->execute();
1352     }
1353 
1354     if ( !d->u ) {
1355         if ( !d->q->done() )
1356             return;
1357 
1358         if ( d->q->failed() ) {
1359             d->error( "Couldn't check schema " + *dbschema + ". " +
1360                       pgErr( d->q ) );
1361             return;
1362         }
1363 
1364         Row * r = d->q->nextRow();
1365         if ( !r ) {
1366             if ( report ) {
1367                 todo++;
1368                 printf( " - Create a schema named '%s'.\n"
1369                         "   As user %s, run:\n\n"
1370                         "%s -d template1 -qc \"%s\"\n\n",
1371                         dbschema->cstr(), PGUSER, PSQL, create.cstr() );
1372             }
1373             else {
1374                 if ( !silent )
1375                     printf( "Creating the '%s' schema.\n", dbschema->cstr() );
1376                 d->u = new Query( create, d );
1377                 d->u->execute();
1378             }
1379         }
1380         else {
1381             d->namespaceExists = true;
1382         }
1383     }
1384 
1385     if ( d->u ) {
1386         if ( !d->u->done() )
1387             return;
1388 
1389         if ( d->u->failed() ) {
1390             d->error( "Couldn't create schema " + dbschema->quoted() + " "
1391                       "in database " + dbname->quoted() + "(" +
1392                       pgErr( d->u ) + ").\nPlease create it by hand and "
1393                       "re-run the installer." );
1394             return;
1395         }
1396     }
1397 
1398     d->nextState();
1399 }
1400 
1401 
1402 // Before we create database objects, we check ownership: if a schema
1403 // was specified, the dbowner should be its owner; if not, it should
1404 // own the database we're installing into.
1405 
checkOwnership()1406 void checkOwnership()
1407 {
1408     // If we just created either database or schema, the owner is
1409     // already set correctly, and we don't need to do anything.
1410 
1411     if ( !( d->databaseExists && d->namespaceExists ) ) {
1412         d->nextState();
1413         return;
1414     }
1415 
1416     // If a schema is specified, check its owner and decide what to do.
1417     // We could do the same for the database if a schema is not given,
1418     // but checkEncoding() already set d->owner, which we can use.
1419 
1420     if ( !d->q && !d->u ) {
1421         if ( privateSchema ) {
1422             d->q = new Query( "select usename::text "
1423                               "from pg_namespace n join pg_user u on "
1424                               "(n.nspowner=u.usesysid) where nspname=$1", d );
1425             d->q->bind( 1, *dbschema );
1426             d->q->execute();
1427         }
1428         else if ( d->owner != *dbowner ) {
1429             EString alter( "alter database " + *dbname +
1430                           " owner to " + *dbowner );
1431 
1432             if ( report ) {
1433                 todo++;
1434                 printf( " - Alter owner of database '%s' from '%s' to '%s'.\n"
1435                         "   As user %s, run:\n\n"
1436                         "%s -d template1 -qc \"%s\"\n\n",
1437                         dbname->cstr(), d->owner.cstr(), dbowner->cstr(),
1438                         PGUSER, PSQL, alter.cstr() );
1439             }
1440             else {
1441                 if ( !silent )
1442                     printf( "Altering ownership of database '%s' to '%s'.\n",
1443                             dbname->cstr(), dbowner->cstr() );
1444                 d->u = new Query( alter, d );
1445                 d->u->execute();
1446             }
1447         }
1448     }
1449 
1450     if ( d->q && !d->u ) {
1451         if ( !d->q->done() )
1452             return;
1453 
1454         Row * r = d->q->nextRow();
1455         if ( d->q->failed() || !r ) {
1456             d->error( "Couldn't check ownership of schema " + *dbschema + ". " +
1457                       pgErr( d->q ) );
1458             return;
1459         }
1460 
1461         EString owner( r->getEString( "usename" ) );
1462         if ( owner != *dbowner ) {
1463             EString alter( "alter schema " + *dbschema +
1464                           " owner to " + *dbowner );
1465 
1466             if ( report ) {
1467                 todo++;
1468                 printf( " - Alter owner of schema '%s' from '%s' to '%s'.\n"
1469                         "   As user %s, run:\n\n"
1470                         "%s -d %s -qc \"%s\"\n\n",
1471                         dbschema->cstr(), owner.cstr(), dbowner->cstr(),
1472                         PGUSER, PSQL, dbname->cstr(), alter.cstr() );
1473             }
1474             else {
1475                 if ( !silent )
1476                     printf( "Altering ownership of schema '%s' to '%s'.\n",
1477                             dbschema->cstr(), dbowner->cstr() );
1478                 d->u = new Query( alter, d );
1479                 d->u->execute();
1480             }
1481         }
1482     }
1483 
1484     if ( d->u ) {
1485         if ( !d->u->done() )
1486             return;
1487 
1488         if ( d->u->failed() ) {
1489             EString s( "Couldn't alter owner of " );
1490             if ( privateSchema )
1491                 s.append( "schema " + dbschema->quoted( '\'' ) );
1492             else
1493                 s.append( "database " + dbname->quoted( '\'' ) );
1494             s.append( " to " + dbowner->quoted( '\'' ) );
1495             s.append( " (" );
1496             s.append( pgErr( d->u ) );
1497             s.append( ").\n" );
1498             d->error( s + "Please set the owner by hand and re-run the "
1499                       "installer." );
1500             return;
1501         }
1502     }
1503 
1504     d->nextState();
1505 }
1506 
1507 
grantUsage()1508 void grantUsage()
1509 {
1510     if ( !privateSchema ) {
1511         d->nextState();
1512         return;
1513     }
1514 
1515     EString grant( "grant usage on schema " + *dbschema + " to " + *dbuser );
1516 
1517     if ( !d->q ) {
1518         d->q = new Query(
1519             "select has_schema_privilege($1,nspname,'usage') as has_usage "
1520             "from pg_catalog.pg_namespace where nspname=$2", d
1521         );
1522         d->q->bind( 1, *dbuser );
1523         d->q->bind( 2, *dbschema );
1524         d->q->execute();
1525     }
1526 
1527     if ( !d->u ) {
1528         if ( !d->q->done() )
1529             return;
1530 
1531         if ( d->q->failed() ) {
1532             d->error( "Couldn't check usage privileges for schema " +
1533                       dbschema->quoted( '\'' ) + ". " + pgErr( d->q ) );
1534             return;
1535         }
1536 
1537         Row * r = d->q->nextRow();
1538         if ( !r || !r->getBoolean( "has_usage" ) ) {
1539             if ( report ) {
1540                 todo++;
1541                 printf( " - Grant usage on schema '%s' to user '%s'.\n"
1542                         "   As user %s, run:\n\n"
1543                         "%s -d %s -qc \"%s\"\n\n",
1544                         dbschema->cstr(), dbuser->cstr(), PGUSER, PSQL,
1545                         dbname->cstr(), grant.cstr() );
1546             }
1547             else {
1548                 d->u = new Query( grant, d );
1549                 d->u->execute();
1550             }
1551         }
1552     }
1553 
1554     if ( d->u ) {
1555         if ( !d->u->done() )
1556             return;
1557 
1558         if ( d->u->failed() ) {
1559             d->error( "Couldn't grant usage on schema " +
1560                       dbschema->quoted( '\'' ) + " to user " +
1561                       dbuser->quoted( '\'' ) + " (" + pgErr( d->u ) +
1562                       ").\nPlease grant it by hand and re-run the "
1563                       "installer." );
1564             return;
1565         }
1566     }
1567 
1568     d->nextState();
1569 }
1570 
1571 
1572 // Archiveopteryx 2.10 introduced the privilege-separation scheme still
1573 // in use, where aoxsuper owns the database objects and has all rights,
1574 // while servers connects as user aox, which has only selected rights.
1575 // In earlier versions, the oryx user owned everything. This function
1576 // is responsible for doing the one-time conversion to the new scheme.
1577 
splitPrivileges()1578 void splitPrivileges()
1579 {
1580     if ( !( d->databaseExists && d->namespaceExists ) ) {
1581         d->nextState();
1582         return;
1583     }
1584 
1585     if ( !d->q ) {
1586         d->q = new Query( "select tableowner::text from pg_catalog.pg_tables "
1587                           "where tablename=$1 and schemaname=$2", d );
1588         d->q->bind( 1, "messages" );
1589         d->q->bind( 2, *dbschema );
1590         d->q->execute();
1591     }
1592 
1593     if ( !d->u ) {
1594         if ( !d->q->done() )
1595             return;
1596 
1597         if ( d->q->failed() ) {
1598             d->error( "Couldn't check ownership of messages table. " +
1599                       pgErr( d->q ) );
1600             return;
1601         }
1602 
1603         EString owner( *dbowner );
1604         Row * r = d->q->nextRow();
1605         if ( r )
1606             owner = r->getEString( "tableowner" );
1607 
1608         // If the messages table is owned by the user that the servers
1609         // connect as, that's bad. But we have to be careful, because
1610         // people may have dbuser and dbowner set to the same user.
1611 
1612         if ( owner == *dbuser && *dbuser != *dbowner ) {
1613             if ( report ) {
1614                 todo++;
1615                 printf( " - Alter the owner of all database objects "
1616                         "to '%s'.\n\n", dbowner->cstr() );
1617             }
1618             else {
1619                 d->q = new Query(
1620                     "create function exec(text) returns int "
1621                     "language 'plpgsql' as "
1622                     "$$begin execute $1;return 0;end;$$", d
1623                 );
1624                 d->q->execute();
1625 
1626                 d->u = new Query(
1627                     "select "
1628                     "exec('ALTER TABLE '||c.relname||' OWNER TO '||$1) "
1629                     "from pg_catalog.pg_class c join "
1630                     "pg_catalog.pg_namespace n on (n.oid=c.relnamespace) "
1631                     "where n.nspname='public' and c.relkind='r' and "
1632                     "pg_catalog.pg_table_is_visible(c.oid)", d
1633                 );
1634                 d->u->bind( 1, *dbowner );
1635                 d->u->execute();
1636 
1637                 // We have at least one unlinked sequence (bodypart_ids)
1638                 // whose ownership would not have been altered by the
1639                 // query above.
1640 
1641                 d->w = new Query(
1642                     "select "
1643                     "exec('ALTER TABLE '||c.relname||' OWNER TO '||$1) "
1644                     "from pg_catalog.pg_class c join "
1645                     "pg_catalog.pg_namespace n on (n.oid=c.relnamespace) "
1646                     "where n.nspname='public' and c.relkind='S' and "
1647                     "pg_catalog.pg_table_is_visible(c.oid)", d
1648                 );
1649                 d->w->bind( 1, *dbowner );
1650                 d->w->execute();
1651 
1652                 d->q = new Query( "drop function exec(text)", d );
1653                 d->q->execute();
1654             }
1655         }
1656         else if ( owner != *dbowner ) {
1657             d->error( "The messages table is not owned by user " +
1658                       dbuser->quoted( '\'' ) + " or by user " +
1659                       dbowner->quoted( '\'' ) + ".\n"
1660                       "This configuration is unsupported. Please contact "
1661                       "info@aox.org for help." );
1662             return;
1663         }
1664     }
1665 
1666     if ( d->u ) {
1667         if ( !d->u->done() || !d->w->done() || !d->q->done() )
1668             return;
1669 
1670         Query * q = 0;
1671         if ( d->u->failed() )
1672             q = d->u;
1673         else if ( d->w->failed() )
1674             q = d->w;
1675         else if ( d->q->failed() )
1676             q = d->q;
1677 
1678         if ( q ) {
1679             d->error( "Couldn't alter ownership of objects in the database " +
1680                       dbname->quoted( '\'' ) + ". " + pgErr( q ) );
1681             return;
1682         }
1683     }
1684 
1685     d->nextState();
1686 }
1687 
1688 
1689 // At this point, we know that the aox/aoxsuper users exist, that the
1690 // aox database exists, that any given schema exists, that PL/PgSQL is
1691 // available, and that the database/schema have the right ownership.
1692 
createSchema()1693 void createSchema()
1694 {
1695     // This is what we need to feed to psql to create the schema.
1696 
1697     EString cmd( "\\set ON_ERROR_STOP\n"
1698                 "SET SESSION AUTHORIZATION " + *dbowner + ";\n"
1699                 "SET client_min_messages TO 'ERROR';\n" );
1700 
1701     if ( privateSchema )
1702         cmd.append( "SET search_path TO " +
1703                     dbschema->quoted( '\'' ) + ";\n" );
1704 
1705     cmd.append( "\\i " LIBDIR "/schema.pg\n"
1706                 "\\i " LIBDIR "/flag-names\n"
1707                 "\\i " LIBDIR "/field-names\n"
1708                 "\\i " LIBDIR "/downgrades\n" );
1709 
1710     // And this function decides whether we need to invoke psql at all,
1711     // based on whether we can find the "mailstore" table.
1712 
1713     if ( report && !( d->databaseExists && d->namespaceExists ) ) {
1714         todo++;
1715         printf( " - Load the database schema.\n   "
1716                 "As user %s, run:\n\n"
1717                 "%s %s -f - <<PSQL;\n%sPSQL\n\n",
1718                 PGUSER, PSQL, dbname->cstr(), cmd.cstr() );
1719         d->nextState();
1720         return;
1721     }
1722 
1723     if ( !d->q ) {
1724         d->ssa = new Query( "set session authorization " + *dbowner, d );
1725         d->ssa->execute();
1726 
1727         if ( privateSchema ) {
1728             d->ssp = new Query( "set search_path to " +
1729                                 dbschema->quoted( '\'' ), d );
1730             d->ssp->execute();
1731         }
1732 
1733         d->q = new Query( "select tablename::text from pg_catalog.pg_tables "
1734                           "where tablename=$1 and schemaname=$2", d );
1735         d->q->bind( 1, "mailstore" );
1736         d->q->bind( 2, *dbschema );
1737         d->q->execute();
1738     }
1739 
1740     if ( !d->u ) {
1741         if ( !d->ssa->done() || ( d->ssp && !d->ssp->done() ) ||
1742              !d->q->done() )
1743             return;
1744 
1745         EString s;
1746         Query * q = 0;
1747 
1748         if ( d->ssa->failed() ) {
1749             q = d->ssa;
1750             s.append( "authenticate as user " );
1751             s.append( dbowner->quoted( '\'' ) );
1752         }
1753         else if ( d->ssp && d->ssp->failed() ) {
1754             q = d->ssp;
1755             s.append( "set search_path to " );
1756             s.append( dbschema->quoted( '\'' ) );
1757         }
1758         else if ( d->q->failed() ) {
1759             q = d->q;
1760             s.append( "query database " );
1761             s.append( dbname->quoted( '\'' ) );
1762         }
1763 
1764         if ( q ) {
1765             if ( report ) {
1766                 todo++;
1767                 printf( " - May need to load the database schema.\n"
1768                         "   (Couldn't %s to make sure it's needed. %s.)\n\n",
1769                         s.cstr(), pgErr( q ).cstr() );
1770             }
1771             else {
1772                 d->error( "Couldn't " + s + " to see if the schema needs "
1773                           "to be loaded. " + pgErr( q ) );
1774             }
1775             d->state = Done;
1776             return;
1777         }
1778 
1779         Row * r = d->q->nextRow();
1780         if ( !r ) {
1781             if ( report ) {
1782                 todo++;
1783                 printf( " - Load the database schema.\n   "
1784                         "As user %s, run:\n\n"
1785                         "%s %s -f - <<PSQL;\n%sPSQL\n\n",
1786                         PGUSER, PSQL, dbname->cstr(), cmd.cstr() );
1787             }
1788             else {
1789                 if ( !silent )
1790                     printf( "Loading the database schema.\n" );
1791                 if ( psql( cmd ) < 0 )
1792                     d->failed = true;
1793             }
1794         }
1795         else {
1796             d->mailstoreExists = true;
1797         }
1798     }
1799 
1800     d->nextState();
1801 }
1802 
1803 
1804 // If the schema already exists, we might need to upgrade it to the
1805 // latest version.
1806 
upgradeSchema()1807 void upgradeSchema()
1808 {
1809     if ( !d->mailstoreExists ) {
1810         d->nextState();
1811         return;
1812     }
1813 
1814     if ( !d->q ) {
1815         d->q = new Query( "select revision from mailstore", d );
1816         d->q->execute();
1817     }
1818 
1819     if ( !d->u ) {
1820         if ( !d->q->done() )
1821             return;
1822 
1823         // This query may fail even if the pg_class query for mailstore
1824         // above succeeded, because we (aoxsuper) may not have rights to
1825         // the schema or the mailstore table.
1826 
1827         Row * r = d->q->nextRow();
1828         if ( d->q->failed() || !r ) {
1829             if ( report ) {
1830                 todo++;
1831                 printf( " - May need to upgrade the database schema.\n"
1832                         "   (Couldn't query mailstore table to make sure "
1833                         "it's needed.)\n\n" );
1834             }
1835             else {
1836                 EString s( "Couldn't query database " );
1837                 s.append( dbname->quoted( '\'' ) );
1838                 s.append( " to see if the schema needs to be upgraded." );
1839                 if ( d->q->failed() ) {
1840                     s.append( " " );
1841                     s.append( pgErr( d->q ) );
1842                 }
1843                 d->error( s );
1844             }
1845             d->state = Done;
1846             return;
1847         }
1848 
1849         uint revision = r->getInt( "revision" );
1850 
1851         if ( revision > Database::currentRevision() ) {
1852             EString v( Configuration::compiledIn( Configuration::Version ) );
1853             fprintf( stderr, "The schema in database '%s' (revision #%d) "
1854                      "is newer than this version of Archiveopteryx (%s) "
1855                      "recognises (up to #%d).\n", dbname->cstr(), revision,
1856                      v.cstr(), Database::currentRevision() );
1857             d->failed = true;
1858             return;
1859         }
1860         else if ( revision < Database::currentRevision() ) {
1861             if ( report ) {
1862                 todo++;
1863                 printf( " - Upgrade the database schema.\n   "
1864                         "(Try \"aox upgrade schema -n\" to see "
1865                         "what would happen).\n\n" );
1866             }
1867             else {
1868                 if ( !silent )
1869                     printf( "Upgrading the database schema.\n" );
1870                 Schema * s = new Schema( d, true, true );
1871                 d->u = s->result();
1872                 s->execute();
1873             }
1874         }
1875     }
1876 
1877     if ( d->u ) {
1878         if ( !d->u->done() )
1879             return;
1880 
1881         if ( d->u->failed() ) {
1882             d->error( "Couldn't upgrade Archiveopteryx schema in database " +
1883                       dbname->quoted( '\'' ) + " (" + pgErr( d->u ) + ").\n"
1884                       "Please run \"aox upgrade schema -n\" by hand.\n" );
1885             return;
1886         }
1887     }
1888 
1889     d->nextState();
1890 }
1891 
1892 
1893 // Make sure the aox user has exactly those privileges it needs.
1894 
grantPrivileges()1895 void grantPrivileges()
1896 {
1897     if ( report ) {
1898         todo++;
1899         printf( " - Grant privileges to user '%s'.\n   "
1900                 "(Run \"aox grant privileges -n %s\" to see "
1901                 "what would happen).\n\n",
1902                 dbuser->cstr(), dbuser->cstr() );
1903         d->nextState();
1904         return;
1905     }
1906 
1907     if ( !d->t ) {
1908         if ( !silent )
1909             printf( "Granting database privileges.\n" );
1910         d->t = new Transaction( d );
1911         d->g = new Granter( *dbuser, d->t );
1912         d->g->execute();
1913     }
1914 
1915     d->t->commit();
1916 
1917     if ( !d->t->done() )
1918         return;
1919 
1920     if ( d->t->failed() ) {
1921         d->error( "Couldn't grant privileges to user " +
1922                   dbuser->quoted( '\'' ) + " (PostgreSQL error: " +
1923                   d->t->error() + ").\nPlease run \"aox grant "
1924                   "privileges -n\" by hand.\n" );
1925         return;
1926     }
1927 
1928     d->nextState();
1929 }
1930 
1931 
configFile()1932 void configFile()
1933 {
1934     setreuid( 0, 0 );
1935 
1936     EString p( *dbpass );
1937     if ( p.contains( " " ) )
1938         p = "'" + p + "'";
1939 
1940     EString cf( Configuration::configFile() );
1941     EString v( Configuration::compiledIn( Configuration::Version ) );
1942     EString intro(
1943         "# Archiveopteryx configuration. See archiveopteryx.conf(5) or\n"
1944         "# http://aox.org/conf/ for details and other variables.\n"
1945         "# Automatically generated while installing Archiveopteryx "
1946         + v + ".\n\n"
1947     );
1948 
1949     EString dbhost( "db-address = " + *dbaddress + "\n" );
1950     if ( dbaddress->startsWith( "/" ) )
1951         dbhost.append( "# " );
1952     dbhost.append( "db-port = " + fn( dbport ) + "\n" );
1953 
1954     EString name( "db-name = " + *dbname + "\n" );
1955 
1956     EString schema;
1957     if ( privateSchema ) {
1958         schema.append( "db-schema = " );
1959         schema.append( *dbschema );
1960         schema.append( "\n" );
1961     }
1962 
1963     EString cfg(
1964         dbhost + name + schema +
1965         "db-user = " + *dbuser + "\n"
1966         "db-password = " + p + "\n\n"
1967     );
1968 
1969     EString other(
1970         "# Uncomment the next line to log more (or set it to debug for even more).\n"
1971         "# log-level = info\n"
1972         "\n"
1973         "# Specify the hostname if Archiveopteryx gets it wrong at runtime.\n"
1974         "# (We suggest not using the name \"localhost\".)\n"
1975         "# hostname = fully.qualified.hostname\n"
1976         "\n"
1977         "# If soft-bounce is set, configuration problems will not cause mail\n"
1978         "# loss. Instead, the mail will be queued by the MTA. Uncomment the\n"
1979         "# following when you are confident that mail delivery works.\n"
1980         "# soft-bounce = disabled\n"
1981         "\n"
1982         "# Change the following to accept LMTP connections on an address\n"
1983         "# other than the default localhost.\n"
1984         "# lmtp-address = 192.0.2.1\n"
1985         "# lmtp-port = 2026\n"
1986         "\n"
1987         "# Uncomment the following to support subaddressing: foo+bar@example.org\n"
1988         "# use-subaddressing = true\n"
1989         "\n"
1990         "# Uncomment the following to keep a filesystem copy of all messages\n"
1991         "# that couldn't be parsed and delivered into the database.\n"
1992         "# message-copy = errors\n"
1993         "# message-copy-directory = /usr/local/archiveopteryx/messages\n"
1994         "\n"
1995         "# Uncomment the following ONLY if necessary for debugging.\n"
1996         "# security = off\n"
1997         "# use-tls = false\n"
1998         "\n"
1999         "# Uncomment the next line to use your own TLS certificate.\n"
2000         "# tls-certificate = /usr/local/archiveopteryx/...\n"
2001         "\n"
2002         "# Uncomment the following to reject all plaintext passwords.\n"
2003         "# allow-plaintext-passwords = never\n"
2004         "# Uncomment the following to require TLS to read or send mail.\n"
2005         "# allow-plaintext-access = never\n"
2006         "\n"
2007         "# There are almost a hundred other configuration variables.\n"
2008         "# The ones above are only what many people will want to change during\n"
2009         "# installation. There are full lists at http://aox.org/conf/ and in\n"
2010         "# the archiveopteryx.conf manual page.\n"
2011     );
2012 
2013     if ( exists( cf ) && generatedPass ) {
2014         fprintf( stderr, "Not overwriting existing %s!\n\n"
2015                  "%s should contain:\n\n%s\n", cf.cstr(), cf.cstr(),
2016                  cfg.cstr() );
2017     }
2018     else if ( !exists( cf ) ) {
2019         if ( report ) {
2020             todo++;
2021             printf( " - Generate a default configuration file.\n"
2022                     "   %s should contain:\n\n%s\n", cf.cstr(), cfg.cstr() );
2023         }
2024         else {
2025             File f( cf, File::Write, 0600 );
2026             if ( !f.valid() ) {
2027                 fprintf( stderr, "Could not open %s for writing.\n",
2028                          cf.cstr() );
2029                 fprintf( stderr, "%s should contain:\n\n%s\n\n",
2030                          cf.cstr(), cfg.cstr() );
2031                 exit( -1 );
2032             }
2033             else {
2034                 if ( !silent )
2035                     printf( "Generating default %s\n", cf.cstr() );
2036                 f.write( intro );
2037                 f.write( cfg );
2038                 f.write( other );
2039             }
2040         }
2041     }
2042 
2043     superConfig();
2044 }
2045 
2046 
superConfig()2047 void superConfig()
2048 {
2049     EString p( *dbownerpass );
2050     if ( p.contains( " " ) )
2051         p = "'" + p + "'";
2052 
2053     EString cf( Configuration::compiledIn( Configuration::ConfigDir ) );
2054     cf.append( "/aoxsuper.conf" );
2055 
2056     EString v( Configuration::compiledIn( Configuration::Version ) );
2057     EString intro(
2058         "# Archiveopteryx configuration. See aoxsuper.conf(5) "
2059         "for details.\n"
2060         "# Automatically generated while installing Archiveopteryx "
2061         + v + ".\n\n"
2062     );
2063     EString cfg(
2064         "# Security note: Anyone who can read this password can do\n"
2065         "# anything to the database, including delete all mail.\n"
2066         "db-owner = " + *dbowner + "\n"
2067         "db-owner-password = " + p + "\n"
2068     );
2069 
2070     if ( exists( cf ) && generatedOwnerPass ) {
2071         fprintf( stderr, "Not overwriting existing %s!\n\n"
2072                  "%s should contain:\n\n%s\n", cf.cstr(), cf.cstr(),
2073                  cfg.cstr() );
2074     }
2075     else if ( !exists( cf ) ) {
2076         if ( report ) {
2077             todo++;
2078             printf( " - Generate the privileged configuration file.\n"
2079                     "   %s should contain:\n\n%s\n", cf.cstr(), cfg.cstr() );
2080         }
2081         else {
2082             File f( cf, File::Write, 0400 );
2083             if ( !f.valid() ) {
2084                 fprintf( stderr, "Could not open %s for writing.\n\n",
2085                          cf.cstr() );
2086                 fprintf( stderr, "%s should contain:\n\n%s\n",
2087                          cf.cstr(), cfg.cstr() );
2088                 exit( -1 );
2089             }
2090             else {
2091                 if ( !silent )
2092                     printf( "Generating default %s\n", cf.cstr() );
2093                 f.write( intro );
2094                 f.write( cfg );
2095             }
2096         }
2097     }
2098 
2099     permissions();
2100 }
2101 
2102 
permissions()2103 void permissions()
2104 {
2105     int n = 0;
2106     struct stat st;
2107 
2108     struct passwd * p = getpwnam( AOXUSER );
2109     struct group * g = getgrnam( AOXGROUP );
2110 
2111     // This should never happen, but I'm feeling paranoid.
2112     if ( !report && !( p && g ) ) {
2113         fprintf( stderr, "getpwnam(AOXUSER)/getgrnam(AOXGROUP) failed "
2114                  "in non-reporting mode.\n" );
2115         exit( -1 );
2116     }
2117 
2118     EString cf( Configuration::configFile() );
2119 
2120     // If archiveopteryx.conf doesn't exist, or has the wrong ownership
2121     // or permissions:
2122 
2123     n = stat( cf.cstr(), &st );
2124     if ( n != 0 || !p || !g || st.st_uid != p->pw_uid ||
2125          (gid_t)st.st_gid != (gid_t)g->gr_gid ||
2126          ( st.st_mode & 0777 ) != 0600 )
2127     {
2128         if ( report ) {
2129             todo++;
2130             printf( " - Set permissions and ownership on %s.\n\n"
2131                     "chmod 0600 %s\n"
2132                     "chown %s:%s %s\n\n",
2133                     cf.cstr(), cf.cstr(), AOXUSER, AOXGROUP, cf.cstr() );
2134         }
2135         else {
2136             if ( !silent )
2137                 printf( "Setting ownership and permissions on %s\n",
2138                         cf.cstr() );
2139 
2140             if ( chmod( cf.cstr(), 0600 ) < 0 )
2141                 fprintf( stderr, "Could not \"chmod 0600 %s\" (-%d).\n",
2142                          cf.cstr(), errno );
2143 
2144             if ( chown( cf.cstr(), p->pw_uid, g->gr_gid ) < 0 )
2145                 fprintf( stderr, "Could not \"chown %s:%s %s\" (-%d).\n",
2146                          AOXUSER, AOXGROUP, cf.cstr(), errno );
2147         }
2148     }
2149 
2150     EString scf( Configuration::compiledIn( Configuration::ConfigDir ) );
2151     scf.append( "/aoxsuper.conf" );
2152 
2153     // If aoxsuper.conf doesn't exist, or has the wrong ownership or
2154     // permissions:
2155 
2156     n = stat( scf.cstr(), &st );
2157     if ( n != 0 || st.st_uid != 0 || (gid_t)st.st_gid != (gid_t)0 ||
2158          ( st.st_mode & 0777 ) != 0400 )
2159     {
2160         if ( report ) {
2161             todo++;
2162             printf( " - Set permissions and ownership on %s.\n\n"
2163                     "chmod 0400 %s\n"
2164                     "chown root:root %s\n\n",
2165                     scf.cstr(), scf.cstr(), scf.cstr() );
2166         }
2167         else {
2168             if ( !silent )
2169                 printf( "Setting ownership and permissions on %s\n",
2170                         scf.cstr() );
2171 
2172             if ( chmod( scf.cstr(), 0400 ) < 0 )
2173                 fprintf( stderr, "Could not \"chmod 0400 %s\" (-%d).\n",
2174                          scf.cstr(), errno );
2175 
2176             if ( chown( scf.cstr(), 0, 0 ) < 0 )
2177                 fprintf( stderr, "Could not \"chown root:root %s\" (-%d).\n",
2178                          scf.cstr(), errno );
2179         }
2180     }
2181 
2182     EString mcd( Configuration::text( Configuration::MessageCopyDir ) );
2183 
2184     // If the message-copy-directory exists and has the wrong ownership
2185     // or permissions:
2186     if ( stat( mcd.cstr(), &st ) == 0 &&
2187          ( !( p && g ) ||
2188            ( st.st_uid != p->pw_uid ||
2189              (gid_t)st.st_gid != (gid_t)g->gr_gid ||
2190              ( st.st_mode & S_IRWXU ) != S_IRWXU ) ) )
2191     {
2192         if ( report ) {
2193             todo++;
2194             printf( " - Set permissions and ownership on %s.\n\n"
2195                     "chmod 0700 %s\n"
2196                     "chown %s:%s %s\n\n",
2197                     mcd.cstr(), mcd.cstr(), AOXUSER, AOXGROUP,
2198                     mcd.cstr() );
2199         }
2200         else {
2201             if ( !silent )
2202                 printf( "Setting ownership and permissions on %s\n",
2203                         mcd.cstr() );
2204 
2205             if ( chmod( mcd.cstr(), 0700 ) < 0 )
2206                 fprintf( stderr, "Could not \"chmod 0700 %s\" (-%d).\n",
2207                          mcd.cstr(), errno );
2208 
2209             if ( chown( mcd.cstr(), p->pw_uid, g->gr_gid ) < 0 )
2210                 fprintf( stderr, "Could not \"chown %s:%s %s\" (-%d).\n",
2211                          AOXUSER, AOXGROUP, mcd.cstr(), errno );
2212         }
2213     }
2214 
2215     EString jd( Configuration::text( Configuration::JailDir ) );
2216 
2217     // If the jail directory exists and has the wrong ownership or
2218     // permissions (i.e. we own it or have any rights to it):
2219     if ( stat( jd.cstr(), &st ) == 0 &&
2220          ( ( st.st_uid != 0 &&
2221              !( p && st.st_uid != p->pw_uid ) ) ||
2222            ( st.st_gid != 0 &&
2223              !( g && (gid_t)st.st_gid != (gid_t)g->gr_gid ) ) ||
2224            ( st.st_mode & S_IRWXO ) != 1 ) )
2225     {
2226         if ( report ) {
2227             todo++;
2228             printf( " - Set permissions and ownership on %s.\n\n"
2229                     "chmod 0701 %s\n"
2230                     "chown root:root %s\n\n",
2231                     jd.cstr(), jd.cstr(), jd.cstr() );
2232         }
2233         else {
2234             if ( !silent )
2235                 printf( "Setting ownership and permissions on %s\n",
2236                         jd.cstr() );
2237 
2238             if ( chmod( jd.cstr(), 0701 ) < 0 )
2239                 fprintf( stderr, "Could not \"chmod 0701 %s\" (-%d).\n",
2240                          jd.cstr(), errno );
2241 
2242             if ( chown( jd.cstr(), 0, 0 ) < 0 )
2243                 fprintf( stderr, "Could not \"chown root:root %s\" (%d).\n",
2244                          jd.cstr(), errno );
2245         }
2246     }
2247 
2248     if ( report && todo == 0 )
2249         printf( "(Nothing.)\n" );
2250     else if ( !silent )
2251         printf( "Done.\n" );
2252 
2253     EventLoop::shutdown();
2254 }
2255 
2256 
psql(const EString & cmd)2257 int psql( const EString &cmd )
2258 {
2259     int n;
2260     int fd[2];
2261     pid_t pid = -1;
2262 
2263     EString host( *dbaddress );
2264     EString port( fn( dbport ) );
2265 
2266     if ( dbsocket ) {
2267         EString s( ".s.PGSQL." + port );
2268         uint l = dbsocket->length() - s.length();
2269         host = dbsocket->mid( 0, l-1 );
2270     }
2271 
2272     n = pipe( fd );
2273     if ( n == 0 )
2274         pid = fork();
2275     if ( n == 0 && pid == 0 ) {
2276         if ( ( postgres != 0 && setreuid( postgres, postgres ) < 0 ) ||
2277              dup2( fd[0], 0 ) < 0 ||
2278              close( fd[1] ) < 0 ||
2279              close( fd[0] ) < 0 )
2280             exit( -1 );
2281         if ( silent )
2282             if ( close( 1 ) < 0 || open( "/dev/null", 0 ) != 1 )
2283                 exit( -1 );
2284         execlp( PSQL, PSQL, "-h", host.cstr(), "-p", port.cstr(),
2285                 "-U", PGUSER, dbname->cstr(), "-f", "-",
2286                 (const char *) 0 );
2287         exit( -1 );
2288     }
2289     else {
2290         int status = 0;
2291         if ( pid > 0 ) {
2292             int ignore = write( fd[1], cmd.cstr(), cmd.length() );
2293             ignore = ignore;
2294             (void)close( fd[1] );
2295             waitpid( pid, &status, 0 );
2296         }
2297         if ( pid < 0 || ( WIFEXITED( status ) &&
2298                           WEXITSTATUS( status ) != 0 ) )
2299         {
2300             fprintf( stderr, "Couldn't execute psql.\n" );
2301             if ( WEXITSTATUS( status ) == 255 )
2302                 fprintf( stderr, "(No psql in PATH=%s)\n", getenv( "PATH" ) );
2303             fprintf( stderr, "Please re-run the installer after "
2304                      "doing the following as user %s:\n\n"
2305                      "%s -h %s -p %s %s -f - <<PSQL;\n%sPSQL\n\n",
2306                      PGUSER, PSQL, host.cstr(), port.cstr(),
2307                      dbname->cstr(), cmd.cstr() );
2308             return -1;
2309         }
2310     }
2311 
2312     return 0;
2313 }
2314