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