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