5a491dad8071d07c7ba85cb41d1e6be34db00e9a
[citadel.git] / citadel / setup.c
1 /*
2  * $Id$
3  *
4  * Citadel setup utility
5  *
6  */
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <ctype.h>
13 #include <fcntl.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <sys/utsname.h>
17 #include <sys/wait.h>
18 #include <signal.h>
19 #include <netdb.h>
20 #include <errno.h>
21 #include <limits.h>
22 #include <pwd.h>
23 #include <time.h>
24 #include <libcitadel.h>
25 #include "citadel.h"
26 #include "axdefs.h"
27 #include "sysdep.h"
28 #include "config.h"
29 #include "citadel_dirs.h"
30 #if HAVE_BACKTRACE
31 #include <execinfo.h>
32 #endif
33
34
35 #define MAXSETUP 11     /* How many setup questions to ask */
36
37 #define UI_TEXT         0       /* Default setup type -- text only */
38 #define UI_DIALOG       2       /* Use the 'dialog' program */
39 #define UI_SILENT       3       /* Silent running, for use in scripts */
40
41 #define SERVICE_NAME    "citadel"
42 #define PROTO_NAME      "tcp"
43 #define NSSCONF         "/etc/nsswitch.conf"
44
45 int setup_type;
46 char setup_directory[PATH_MAX];
47 int using_web_installer = 0;
48 int enable_home = 1;
49 char admin_pass[SIZ];
50 char admin_cmd[SIZ];
51
52 char *setup_titles[] =
53 {
54         "Citadel Home Directory",
55         "System Administrator",
56         "Administrator Password",
57         "Citadel User ID",
58         "Server IP address",
59         "Server port number",
60         "Authentication mode",
61         "LDAP host",
62         "LDAP port number",
63         "LDAP base DN",
64         "LDAP bind DN",
65         "LDAP bind password"
66 };
67
68 /**
69  * \brief print the actual stack frame.
70  */
71 void cit_backtrace(void)
72 {
73 #ifdef HAVE_BACKTRACE
74         void *stack_frames[50];
75         size_t size, i;
76         char **strings;
77
78
79         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
80         strings = backtrace_symbols(stack_frames, size);
81         for (i = 0; i < size; i++) {
82                 if (strings != NULL)
83                         fprintf(stderr, "%s\n", strings[i]);
84                 else
85                         fprintf(stderr, "%p\n", stack_frames[i]);
86         }
87         free(strings);
88 #endif
89 }
90
91 struct config config;
92
93         /* calculate all our path on a central place */
94     /* where to keep our config */
95         
96
97 char *setup_text[] = {
98 #ifndef HAVE_RUN_DIR
99 "Enter the full pathname of the directory in which the Citadel\n"
100 "installation you are creating or updating resides.  If you\n"
101 "specify a directory other than the default, you will need to\n"
102 "specify the -h flag to the server when you start it up.\n",
103 #else
104 "Enter the subdirectory name for an alternate installation of "
105 "Citadel. To do a default installation just leave it blank."
106 "If you specify a directory other than the default, you will need to\n"
107 "specify the -h flag to the server when you start it up.\n"
108 "note that it may not have a leading /",
109 #endif
110
111 "Enter the name of the system administrator (which is probably\n"
112 "you).  When an account is created with this name, it will\n"
113 "automatically be given administrator-level access.\n",
114
115 "Enter a password for the system administrator. When setup\n"
116 "completes it will attempt to create the administrator user\n"
117 "and set the password specified here.\n",
118
119 "Citadel needs to run under its own user ID.  This would\n"
120 "typically be called \"citadel\", but if you are running Citadel\n"
121 "as a public BBS, you might also call it \"bbs\" or \"guest\".\n"
122 "The server will run under this user ID.  Please specify that\n"
123 "user ID here.  You may specify either a user name or a numeric\n"
124 "UID.\n",
125
126 "Specify the IP address on which your server will run.  If you\n"
127 "leave this blank, or if you specify 0.0.0.0, Citadel will listen\n"
128 "on all addresses.  You can usually skip this unless you are\n"
129 "running multiple instances of Citadel on the same computer.\n",
130
131 "Specify the TCP port number on which your server will run.\n"
132 "Normally, this will be port 504, which is the official port\n"
133 "assigned by the IANA for Citadel servers.  You will only need\n"
134 "to specify a different port number if you run multiple instances\n"
135 "of Citadel on the same computer and there is something else\n"
136 "already using port 504.\n",
137
138
139
140 "How would you like to authenticate users on your Citadel system?\n"
141 "\n"
142 " 0. Self contained authentication - Citadel maintains its own user database\n"
143 "    (This mode is *strongly* recommended)\n"
144 "\n"
145 " 1. Authenticate users against the host system (unix or linux accounts)\n"
146 "\n"
147 " 2. Authenticate users against an external LDAP directory\n"
148 "\n"
149 "WARNING: do *not* change this setting once your system is installed.\n"
150 "\n"
151 "(Answer \"0\" unless you completely understand this option)\n"
152 "Which authentication mode do you want to use?\n",
153
154 "Please enter the host name or IP address of your LDAP server.\n",
155
156 "Please enter the port number of the LDAP service (usually 389).\n",
157
158 "Please enter the Base DN to search for authentication\n"
159 "(for example: dc=example,dc=com)\n",
160
161 "Please enter the DN of an account to use for binding to the LDAP server\n"
162 "for performing queries.  The account does not require any other\n"
163 "privileges.  If your LDAP server allows anonymous queries, you can.\n"
164 "leave this blank.\n",
165
166 "If you entered a Bind DN in the previous question, you must now enter\n"
167 "the password associated with that account.  Otherwise, you can leave this\n"
168 "blank.\n"
169
170 };
171
172 struct config config;
173 int direction;
174
175
176 void cleanup(int exitcode)
177 {
178 //      printf("Exitcode: %d\n", exitcode);
179 //      cit_backtrace();
180         exit(exitcode);
181 }
182
183
184
185 void title(char *text)
186 {
187         if (setup_type == UI_TEXT) {
188                 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
189         }
190 }
191
192
193
194 int yesno(char *question, int default_value)
195 {
196         int i = 0;
197         int answer = 0;
198         char buf[SIZ];
199
200         switch (setup_type) {
201
202         case UI_TEXT:
203                 do {
204                         printf("%s\nYes/No [%s] --> ",
205                                 question,
206                                 ( default_value ? "Yes" : "No" )
207                         );
208                         fgets(buf, sizeof buf, stdin);
209                         answer = tolower(buf[0]);
210                         if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10))
211                                 answer = default_value;
212                         else if (answer == 'y')
213                                 answer = 1;
214                         else if (answer == 'n')
215                                 answer = 0;
216                 } while ((answer < 0) || (answer > 1));
217                 break;
218
219         case UI_DIALOG:
220                 sprintf(buf, "exec %s %s --yesno '%s' 15 75",
221                         getenv("CTDL_DIALOG"),
222                         ( default_value ? "" : "--defaultno" ),
223                         question);
224                 i = system(buf);
225                 if (i == 0) {
226                         answer = 1;
227                 }
228                 else {
229                         answer = 0;
230                 }
231                 break;
232         case UI_SILENT:
233                 break;
234
235         }
236         return (answer);
237 }
238
239
240 void important_message(char *title, char *msgtext)
241 {
242         char buf[SIZ];
243
244         switch (setup_type) {
245
246         case UI_TEXT:
247                 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
248                 printf("       %s \n\n%s\n\n", title, msgtext);
249                 printf("Press return to continue...");
250                 fgets(buf, sizeof buf, stdin);
251                 break;
252
253         case UI_DIALOG:
254                 sprintf(buf, "exec %s --msgbox '%s' 19 72",
255                         getenv("CTDL_DIALOG"),
256                         msgtext);
257                 system(buf);
258                 break;
259         case UI_SILENT:
260                 fprintf(stderr, "%s\n", msgtext);
261                 break;
262         }
263 }
264
265 void important_msgnum(int msgnum)
266 {
267         important_message("Important Message", setup_text[msgnum]);
268 }
269
270 void display_error(char *error_message)
271 {
272         important_message("Error", error_message);
273 }
274
275 void progress(char *text, long int curr, long int cmax)
276 {
277         static long dots_printed = 0L;
278         long a = 0;
279         static FILE *fp = NULL;
280         char buf[SIZ];
281
282         switch (setup_type) {
283
284         case UI_TEXT:
285                 if (curr == 0) {
286                         printf("%s\n", text);
287                         printf("..........................");
288                         printf("..........................");
289                         printf("..........................\r");
290                         fflush(stdout);
291                         dots_printed = 0;
292                 } else if (curr == cmax) {
293                         printf("\r%79s\n", "");
294                 } else {
295                         a = (curr * 100) / cmax;
296                         a = a * 78;
297                         a = a / 100;
298                         while (dots_printed < a) {
299                                 printf("*");
300                                 ++dots_printed;
301                                 fflush(stdout);
302                         }
303                 }
304                 break;
305
306         case UI_DIALOG:
307                 if (curr == 0) {
308                         sprintf(buf, "exec %s --gauge '%s' 7 72 0",
309                                 getenv("CTDL_DIALOG"),
310                                 text);
311                         fp = popen(buf, "w");
312                         if (fp != NULL) {
313                                 fprintf(fp, "0\n");
314                                 fflush(fp);
315                         }
316                 } 
317                 else if (curr == cmax) {
318                         if (fp != NULL) {
319                                 fprintf(fp, "100\n");
320                                 pclose(fp);
321                                 fp = NULL;
322                         }
323                 }
324                 else {
325                         a = (curr * 100) / cmax;
326                         if (fp != NULL) {
327                                 fprintf(fp, "%ld\n", a);
328                                 fflush(fp);
329                         }
330                 }
331                 break;
332         case UI_SILENT:
333                 break;
334
335         }
336 }
337
338
339
340 /*
341  * check_services_entry()  -- Make sure "citadel" is in /etc/services
342  *
343  */
344 void check_services_entry(void)
345 {
346         int i;
347         FILE *sfp;
348         char errmsg[256];
349
350         if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
351                 for (i=0; i<=2; ++i) {
352                         progress("Adding service entry...", i, 2);
353                         if (i == 0) {
354                                 sfp = fopen("/etc/services", "a");
355                                 if (sfp == NULL) {
356                                         sprintf(errmsg, "Cannot open /etc/services: %s", strerror(errno));
357                                         display_error(errmsg);
358                                 } else {
359                                         fprintf(sfp, "%s                504/tcp\n", SERVICE_NAME);
360                                         fclose(sfp);
361                                 }
362                         }
363                 }
364         }
365 }
366
367
368
369
370 /*
371  * delete_inittab_entry()  -- Remove obsolete /etc/inittab entry for Citadel
372  *
373  */
374 void delete_inittab_entry(void)
375 {
376         FILE *infp;
377         FILE *outfp;
378         char looking_for[256];
379         char buf[1024];
380         char outfilename[32];
381         int changes_made = 0;
382
383         /* Determine the fully qualified path name of citserver */
384         snprintf(looking_for, 
385                  sizeof looking_for,
386                  "%s/citserver", 
387                  ctdl_sbin_dir
388                  );
389
390         /* Now tweak /etc/inittab */
391         infp = fopen("/etc/inittab", "r");
392         if (infp == NULL) {
393
394                 /* If /etc/inittab does not exist, return quietly.
395                  * Not all host platforms have it.
396                  */
397                 if (errno == ENOENT) {
398                         return;
399                 }
400
401                 /* Other errors might mean something really did go wrong.
402                  */
403                 sprintf(buf, "Cannot open /etc/inittab: %s", strerror(errno));
404                 display_error(buf);
405                 return;
406         }
407
408         strcpy(outfilename, "/tmp/ctdlsetup.XXXXXX");
409         outfp = fdopen(mkstemp(outfilename), "w+");
410         if (outfp == NULL) {
411                 sprintf(buf, "Cannot open %s: %s", outfilename, strerror(errno));
412                 display_error(buf);
413                 fclose(infp);
414                 return;
415         }
416
417         while (fgets(buf, sizeof buf, infp) != NULL) {
418                 if (strstr(buf, looking_for) != NULL) {
419                         fwrite("#", 1, 1, outfp);
420                         ++changes_made;
421                 }
422                 fwrite(buf, strlen(buf), 1, outfp);
423         }
424
425         fclose(infp);
426         fclose(outfp);
427
428         if (changes_made) {
429                 sprintf(buf, "/bin/mv -f %s /etc/inittab 2>/dev/null", outfilename);
430                 system(buf);
431                 system("/sbin/init q 2>/dev/null");
432         }
433         else {
434                 unlink(outfilename);
435         }
436 }
437
438
439 /*
440  * install_init_scripts()  -- Try to configure to start Citadel at boot
441  *
442  */
443 void install_init_scripts(void)
444 {
445         struct stat etcinitd;
446         FILE *fp;
447         char *initfile = "/etc/init.d/citadel";
448         char command[SIZ];
449
450         if ((stat("/etc/init.d/", &etcinitd) == -1) && 
451             (errno == ENOENT))
452         {
453                 if ((stat("/etc/rc.d/init.d/", &etcinitd) == -1) &&
454                     (errno == ENOENT))
455                         initfile = CTDLDIR"/citadel.init";
456                 else
457                         initfile = "/etc/rc.d/init.d/citadel";
458         }
459
460         fp = fopen(initfile, "r");
461         if (fp != NULL) {
462                 if (yesno("Citadel already appears to be configured to start at boot.\n"
463                    "Would you like to keep your boot configuration as is?\n", 1) == 1) {
464                         return;
465                 }
466                 fclose(fp);
467                 
468         }
469
470         if (yesno("Would you like to automatically start Citadel at boot?\n", 1) == 0) {
471                 return;
472         }
473
474         fp = fopen(initfile, "w");
475         if (fp == NULL) {
476                 display_error("Cannot create /etc/init.d/citadel");
477                 return;
478         }
479
480         fprintf(fp,     "#!/bin/sh\n"
481                         "#\n"
482                         "# Init file for Citadel\n"
483                         "#\n"
484                         "# chkconfig: - 79 30\n"
485                         "# description: Citadel service\n"
486                         "# processname: citserver\n"
487                         "# pidfile: %s/citadel.pid\n"
488                         "\n"
489                         "CITADEL_DIR=%s\n"
490                         ,
491                                 setup_directory,
492                                 setup_directory
493                         );
494         fprintf(fp,     "\n"
495                         "test -d /var/run || exit 0\n"
496                         "\n"
497                         "case \"$1\" in\n"
498                         "\n"
499                         "start)         echo -n \"Starting Citadel... \"\n"
500                         "               if $CITADEL_DIR/citserver -d -h$CITADEL_DIR\n"
501                         "               then\n"
502                         "                       echo \"ok\"\n"
503                         "               else\n"
504                         "                       echo \"failed\"\n"
505                         "               fi\n");
506         fprintf(fp,     "               ;;\n"
507                         "stop)          echo -n \"Stopping Citadel... \"\n"
508                         "               if $CITADEL_DIR/sendcommand DOWN >/dev/null 2>&1 ; then\n"
509                         "                       echo \"ok\"\n"
510                         "               else\n"
511                         "                       echo \"failed\"\n"
512                         "               fi\n"
513                         "               rm -f %s/citadel.pid 2>/dev/null\n"
514                         ,
515                                 setup_directory
516                         );
517         fprintf(fp,     "               ;;\n"
518                         "restart)       if $CITADEL_DIR/sendcommand DOWN 1 >/dev/null 2>&1 ; then\n"
519                         "                       echo \"ok\"\n"
520                         "               else\n"
521                         "                       echo \"failed\"\n"
522                         "               fi\n"
523                         "               ;;\n"
524                         "*)             echo \"Usage: $0 {start|stop|restart}\"\n"
525                         "               exit 1\n"
526                         "               ;;\n"
527                         "esac\n"
528         );
529
530         fclose(fp);
531         chmod(initfile, 0755);
532
533         /* Set up the run levels. */
534         system("/bin/rm -f /etc/rc?.d/[SK]??citadel 2>/dev/null");
535         snprintf(command, sizeof(command), "for x in 2 3 4 5 ; do [ -d /etc/rc$x.d ] && ln -s %s /etc/rc$x.d/S79citadel ; done 2>/dev/null", initfile);
536         system(command);
537         snprintf(command, sizeof(command),"for x in 0 6 S; do [ -d /etc/rc$x.d ] && ln -s %s /etc/rc$x.d/K30citadel ; done 2>/dev/null", initfile);
538         system(command);
539
540 }
541
542
543
544
545
546
547 /*
548  * On systems which use xinetd, see if we can offer to install Citadel as
549  * the default telnet target.
550  */
551 void check_xinetd_entry(void) {
552         char *filename = "/etc/xinetd.d/telnet";
553         FILE *fp;
554         char buf[SIZ];
555         int already_citadel = 0;
556
557         fp = fopen(filename, "r+");
558         if (fp == NULL) return;         /* Not there.  Oh well... */
559
560         while (fgets(buf, sizeof buf, fp) != NULL) {
561                 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
562         }
563         fclose(fp);
564         if (already_citadel) return;    /* Already set up this way. */
565
566         /* Otherwise, prompt the user to create an entry. */
567         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
568                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
569                         return;
570                 }
571         }
572         else {
573                 snprintf(buf, sizeof buf,
574                         "Setup can configure the \"xinetd\" service to automatically\n"
575                         "connect incoming telnet sessions to Citadel, bypassing the\n"
576                         "host system login: prompt.  Would you like to do this?\n"
577                 );
578                 if (yesno(buf, 1) == 0) {
579                         return;
580                 }
581         }
582
583         fp = fopen(filename, "w");
584         fprintf(fp,
585                 "# description: telnet service for Citadel users\n"
586                 "service telnet\n"
587                 "{\n"
588                 "       disable = no\n"
589                 "       flags           = REUSE\n"
590                 "       socket_type     = stream\n"
591                 "       wait            = no\n"
592                 "       user            = root\n"
593                 "       server          = /usr/sbin/in.telnetd\n"
594                 "       server_args     = -h -L %s/citadel\n"
595                 "       log_on_failure  += USERID\n"
596                 "}\n",
597                 ctdl_bin_dir);
598         fclose(fp);
599
600         /* Now try to restart the service */
601         system("/etc/init.d/xinetd restart >/dev/null 2>&1");
602 }
603
604
605
606 /*
607  * Offer to disable other MTA's
608  */
609 void disable_other_mta(char *mta) {
610         char buf[SIZ];
611         FILE *fp;
612         int lines = 0;
613
614         sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
615                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
616                 mta, mta);
617         fp = popen(buf, "r");
618         if (fp == NULL) return;
619
620         while (fgets(buf, sizeof buf, fp) != NULL) {
621                 ++lines;
622         }
623         fclose(fp);
624         if (lines == 0) return;         /* Nothing to do. */
625
626
627         /* Offer to replace other MTA with the vastly superior Citadel :)  */
628
629         snprintf(buf, sizeof buf,
630                  "You appear to have the \"%s\" email program\n"
631                  "running on your system.  If you want Citadel mail\n"
632                  "connected with %s, you will have to manually integrate\n"
633                  "them.  It is preferable to disable %s, and use Citadel's\n"
634                  "SMTP, POP3, and IMAP services.\n\n"
635                  "May we disable %s so that Citadel has access to ports\n"
636                  "25, 110, and 143?\n",
637                  mta, mta, mta, mta
638                 );
639         if (yesno(buf, 1) == 0) {
640                 return;
641         }
642         
643
644         sprintf(buf, "for x in /etc/rc*.d/S*%s; do mv $x `echo $x |sed s/S/K/g`; done >/dev/null 2>&1", mta);
645         system(buf);
646         sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
647         system(buf);
648 }
649
650
651
652
653 /* 
654  * Check to see if our server really works.  Returns 0 on success.
655  */
656 int test_server(char *setup_directory, char *relhomestr, int relhome) {
657         char cmd[256];
658         char cookie[256];
659         FILE *fp;
660         char buf[4096];
661         int found_it = 0;
662
663         /* Generate a silly little cookie.  We're going to write it out
664          * to the server and try to get it back.  The cookie does not
665          * have to be secret ... just unique.
666          */
667         sprintf(cookie, "--test--%d--", getpid());
668
669         if (relhome)
670                 sprintf(cmd, "%s/sendcommand -h%s ECHO %s 2>&1",
671                         ctdl_sbin_dir,
672                         relhomestr,
673                         cookie);
674         else
675                 sprintf(cmd, "%s/sendcommand ECHO %s 2>&1",
676                         ctdl_sbin_dir,
677                         cookie);
678
679         fp = popen(cmd, "r");
680         if (fp == NULL) return(errno);
681
682         while (fgets(buf, sizeof buf, fp) != NULL) {
683                 if ( (buf[0]=='2')
684                    && (strstr(buf, cookie) != NULL) ) {
685                         ++found_it;
686                 }
687         }
688         pclose(fp);
689
690         if (found_it) {
691                 return(0);
692         }
693         return(-1);
694 }
695
696 void strprompt(char *prompt_title, char *prompt_text, char *str)
697 {
698         char buf[SIZ];
699         char setupmsg[SIZ];
700         char dialog_result[PATH_MAX];
701         FILE *fp = NULL;
702
703         strcpy(setupmsg, "");
704
705         switch (setup_type) {
706         case UI_TEXT:
707                 title(prompt_title);
708                 printf("\n%s\n", prompt_text);
709                 printf("This is currently set to:\n%s\n", str);
710                 printf("Enter new value or press return to leave unchanged:\n");
711                 fgets(buf, sizeof buf, stdin);
712                 buf[strlen(buf) - 1] = 0;
713                 if (!IsEmptyStr(buf))
714                         strcpy(str, buf);
715                 break;
716
717         case UI_DIALOG:
718                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
719                 sprintf(buf, "exec %s --inputbox '%s' 19 72 '%s' 2>%s",
720                         getenv("CTDL_DIALOG"),
721                         prompt_text,
722                         str,
723                         dialog_result);
724                 system(buf);
725                 fp = fopen(dialog_result, "r");
726                 if (fp != NULL) {
727                         fgets(str, sizeof buf, fp);
728                         if (str[strlen(str)-1] == 10) {
729                                 str[strlen(str)-1] = 0;
730                         }
731                         fclose(fp);
732                         unlink(dialog_result);
733                 }
734                 break;
735         case UI_SILENT:
736                 break;
737         }
738 }
739
740 void set_bool_val(int msgpos, int *ip) {
741         title(setup_titles[msgpos]);
742         *ip = yesno(setup_text[msgpos], *ip);
743 }
744
745 void set_str_val(int msgpos, char *str) {
746         strprompt(setup_titles[msgpos], setup_text[msgpos], str);
747 }
748
749 void set_int_val(int msgpos, int *ip)
750 {
751         char buf[16];
752         snprintf(buf, sizeof buf, "%d", (int) *ip);
753         set_str_val(msgpos, buf);
754         *ip = atoi(buf);
755 }
756
757
758 void set_char_val(int msgpos, char *ip)
759 {
760         char buf[16];
761         snprintf(buf, sizeof buf, "%d", (int) *ip);
762         set_str_val(msgpos, buf);
763         *ip = (char) atoi(buf);
764 }
765
766
767 void set_long_val(int msgpos, long int *ip)
768 {
769         char buf[16];
770         snprintf(buf, sizeof buf, "%ld", *ip);
771         set_str_val(msgpos, buf);
772         *ip = atol(buf);
773 }
774
775
776 void edit_value(int curr)
777 {
778         int i;
779         struct passwd *pw;
780         char ctdluidname[256];
781
782         switch (curr) {
783
784         case 1:
785                 if (setup_type == UI_SILENT)
786                 {
787                         if (getenv("SYSADMIN_NAME")) {
788                                 strcpy(config.c_sysadm, getenv("SYSADMIN_NAME"));
789                         }
790                 }
791                 else {
792                         set_str_val(curr, config.c_sysadm);
793                 }
794                 break;
795
796         case 2:
797                 if (setup_type == UI_SILENT)
798                 {
799                         if (getenv("SYSADMIN_PW")) {
800                                 strcpy(admin_pass, getenv("SYSADMIN_PW"));
801                         }
802                 }
803                 else {
804                         set_str_val(curr, admin_pass);
805                 }
806                 break;
807         
808         case 3:
809                 if (setup_type == UI_SILENT)
810                 {               
811                         if (getenv("CITADEL_UID")) {
812                                 config.c_ctdluid = atoi(getenv("CITADEL_UID"));
813                         }                                       
814                 }
815                 else
816                 {
817 #ifdef __CYGWIN__
818                         config.c_ctdluid = 0;   /* XXX Windows hack, prob. insecure */
819 #else
820                         i = config.c_ctdluid;
821                         pw = getpwuid(i);
822                         if (pw == NULL) {
823                                 set_int_val(curr, &i);
824                                 config.c_ctdluid = i;
825                         }
826                         else {
827                                 strcpy(ctdluidname, pw->pw_name);
828                                 set_str_val(curr, ctdluidname);
829                                 pw = getpwnam(ctdluidname);
830                                 if (pw != NULL) {
831                                         config.c_ctdluid = pw->pw_uid;
832                                 }
833                                 else if (atoi(ctdluidname) > 0) {
834                                         config.c_ctdluid = atoi(ctdluidname);
835                                 }
836                         }
837 #endif
838                 }
839                 break;
840
841         case 4:
842                 if (setup_type == UI_SILENT)
843                 {
844                         if (getenv("IP_ADDR")) {
845                                 strcpy(config.c_ip_addr, getenv("IP_ADDR"));
846                         }
847                 }
848                 else {
849                         set_str_val(curr, config.c_ip_addr);
850                 }
851                 break;
852
853         case 5:
854                 if (setup_type == UI_SILENT)
855                 {
856                         if (getenv("CITADEL_PORT")) {
857                                 config.c_port_number = atoi(getenv("CITADEL_PORT"));
858                         }
859                 }
860                 else
861                 {
862                         set_int_val(curr, &config.c_port_number);
863                 }
864                 break;
865
866         case 6:
867                 if (setup_type == UI_SILENT)
868                 {
869                         if (getenv("ENABLE_UNIX_AUTH")) {
870                                 if (!strcasecmp(getenv("ENABLE_UNIX_AUTH"), "yes")) {
871                                         config.c_auth_mode = AUTHMODE_HOST;
872                                 }
873                                 else {
874                                         config.c_auth_mode = AUTHMODE_NATIVE;
875                                 }
876                         }
877                 }
878                 else {
879                         set_int_val(curr, &config.c_auth_mode);
880                 }
881                 break;
882
883         case 7:
884                 if (setup_type == UI_SILENT)
885                 {
886                         if (getenv("LDAP_HOST")) {
887                                 strcpy(config.c_ldap_host, getenv("LDAP_HOST"));
888                         }
889                 }
890                 else
891                 {
892                         set_str_val(curr, config.c_ldap_host);
893                 }
894                 break;
895
896         case 8:
897                 if (setup_type == UI_SILENT)
898                 {
899                         if (getenv("LDAP_PORT")) {
900                                 config.c_ldap_port = atoi(getenv("LDAP_PORT"));
901                         }
902                 }
903                 else
904                 {
905                         set_int_val(curr, &config.c_ldap_port);
906                 }
907                 break;
908
909         case 9:
910                 if (setup_type == UI_SILENT)
911                 {
912                         if (getenv("LDAP_BASE_DN")) {
913                                 strcpy(config.c_ldap_base_dn, getenv("LDAP_BASE_DN"));
914                         }
915                 }
916                 else
917                 {
918                         set_str_val(curr, config.c_ldap_base_dn);
919                 }
920                 break;
921
922         case 10:
923                 if (setup_type == UI_SILENT)
924                 {
925                         if (getenv("LDAP_BIND_DN")) {
926                                 strcpy(config.c_ldap_bind_dn, getenv("LDAP_BIND_DN"));
927                         }
928                 }
929                 else
930                 {
931                         set_str_val(curr, config.c_ldap_bind_dn);
932                 }
933                 break;
934
935         case 11:
936                 if (setup_type == UI_SILENT)
937                 {
938                         if (getenv("LDAP_BIND_PW")) {
939                                 strcpy(config.c_ldap_bind_pw, getenv("LDAP_BIND_PW"));
940                         }
941                 }
942                 else
943                 {
944                         set_str_val(curr, config.c_ldap_bind_pw);
945                 }
946                 break;
947
948         }
949
950 }
951
952 /*
953  * (re-)write the config data to disk
954  */
955 void write_config_to_disk(void)
956 {
957         FILE *fp;
958         int fd;
959
960         if ((fd = creat(file_citadel_config, S_IRUSR | S_IWUSR)) == -1) {
961                 display_error("setup: cannot open citadel.config");
962                 cleanup(1);
963         }
964         fp = fdopen(fd, "wb");
965         if (fp == NULL) {
966                 display_error("setup: cannot open citadel.config");
967                 cleanup(1);
968         }
969         fwrite((char *) &config, sizeof(struct config), 1, fp);
970         fclose(fp);
971 }
972
973
974
975
976 /*
977  * Figure out what type of user interface we're going to use
978  */
979 int discover_ui(void)
980 {
981
982         /* Use "dialog" if we have it */
983         if (getenv("CTDL_DIALOG") != NULL) {
984                 return UI_DIALOG;
985         }
986                 
987         return UI_TEXT;
988 }
989
990
991
992
993
994 /*
995  * Strip "db" entries out of /etc/nsswitch.conf
996  */
997 void fixnss(void) {
998         FILE *fp_read;
999         int fd_write;
1000         char buf[256];
1001         char buf_nc[256];
1002         char question[512];
1003         int i;
1004         int changed = 0;
1005         int file_changed = 0;
1006         char new_filename[64];
1007
1008         fp_read = fopen(NSSCONF, "r");
1009         if (fp_read == NULL) {
1010                 return;
1011         }
1012
1013         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
1014         fd_write = mkstemp(new_filename);
1015         if (fd_write < 0) {
1016                 fclose(fp_read);
1017                 return;
1018         }
1019
1020         while (fgets(buf, sizeof buf, fp_read) != NULL) {
1021                 changed = 0;
1022                 strcpy(buf_nc, buf);
1023                 for (i=0; i<strlen(buf_nc); ++i) {
1024                         if (buf_nc[i] == '#') {
1025                                 buf_nc[i] = 0;
1026                         }
1027                 }
1028                 for (i=0; i<strlen(buf_nc); ++i) {
1029                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
1030                                 if (i > 0) {
1031                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
1032                                                 changed = 1;
1033                                                 file_changed = 1;
1034                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
1035                                                 strcpy(&buf[i], &buf[i+2]);
1036                                                 if (buf[i]==32) {
1037                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
1038                                                         strcpy(&buf[i], &buf[i+1]);
1039                                                 }
1040                                         }
1041                                 }
1042                         }
1043                 }
1044                 if (write(fd_write, buf, strlen(buf)) != strlen(buf)) {
1045                         fclose(fp_read);
1046                         close(fd_write);
1047                         unlink(new_filename);
1048                         return;
1049                 }
1050         }
1051
1052         fclose(fp_read);
1053         
1054         if (!file_changed) {
1055                 unlink(new_filename);
1056                 return;
1057         }
1058
1059         snprintf(question, sizeof question,
1060                 "\n"
1061                 "/etc/nsswitch.conf is configured to use the 'db' module for\n"
1062                 "one or more services.  This is not necessary on most systems,\n"
1063                 "and it is known to crash the Citadel server when delivering\n"
1064                 "mail to the Internet.\n"
1065                 "\n"
1066                 "Do you want this module to be automatically disabled?\n"
1067                 "\n"
1068         );
1069
1070         if (yesno(question, 1)) {
1071                 sprintf(buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
1072                 system(buf);
1073                 chmod(NSSCONF, 0644);
1074         }
1075         unlink(new_filename);
1076 }
1077
1078
1079
1080
1081
1082
1083
1084
1085 int main(int argc, char *argv[])
1086 {
1087         int a;
1088         int curr; 
1089         char aaa[128];
1090         FILE *fp;
1091         int old_setup_level = 0;
1092         int info_only = 0;
1093         struct utsname my_utsname;
1094         struct passwd *pw;
1095         struct hostent *he;
1096         gid_t gid;
1097         int relh=0;
1098         int home=0;
1099         char relhome[PATH_MAX]="";
1100         char ctdldir[PATH_MAX]=CTDLDIR;
1101         
1102         /* set an invalid setup type */
1103         setup_type = (-1);
1104
1105         /* Check to see if we're running the web installer */
1106         if (getenv("CITADEL_INSTALLER") != NULL) {
1107                 using_web_installer = 1;
1108         }
1109
1110         /* parse command line args */
1111         for (a = 0; a < argc; ++a) {
1112                 if (!strncmp(argv[a], "-u", 2)) {
1113                         strcpy(aaa, argv[a]);
1114                         strcpy(aaa, &aaa[2]);
1115                         setup_type = atoi(aaa);
1116                 }
1117                 else if (!strcmp(argv[a], "-i")) {
1118                         info_only = 1;
1119                 }
1120                 else if (!strcmp(argv[a], "-q")) {
1121                         setup_type = UI_SILENT;
1122                 }
1123                 else if (!strncmp(argv[a], "-h", 2)) {
1124                         relh=argv[a][2]!='/';
1125                         if (!relh) safestrncpy(ctdl_home_directory, &argv[a][2],
1126                                                                    sizeof ctdl_home_directory);
1127                         else
1128                                 safestrncpy(relhome, &argv[a][2],
1129                                                         sizeof relhome);
1130                         home = 1;
1131                 }
1132
1133         }
1134
1135         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1136
1137         /* If a setup type was not specified, try to determine automatically
1138          * the best one to use out of all available types.
1139          */
1140         if (setup_type < 0) {
1141                 setup_type = discover_ui();
1142         }
1143         if (info_only == 1) {
1144                 important_message("Citadel Setup", CITADEL);
1145                 cleanup(0);
1146         }
1147
1148         /* Get started in a valid setup directory. */
1149         strcpy(setup_directory, ctdl_run_dir);
1150         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
1151                 strcpy(setup_directory, getenv("CITADEL"));
1152         }
1153         else {
1154                 set_str_val(0, setup_directory);
1155         }
1156
1157         enable_home = ( relh | home );
1158
1159         if (chdir(setup_directory) != 0) {
1160                 char errmsg[SIZ];
1161                 sprintf(errmsg, "The directory you specified does not exist: [%s]\n", setup_directory);
1162                 
1163                 important_message("Citadel Setup", errmsg);
1164                 cleanup(errno);
1165         }
1166
1167         /* Determine our host name, in case we need to use it as a default */
1168         uname(&my_utsname);
1169
1170         /* Try to stop Citadel if we can */
1171         if (!access("/etc/init.d/citadel", X_OK)) {
1172                 system("/etc/init.d/citadel stop");
1173         }
1174
1175         /* Make sure Citadel is not running. */
1176         if (test_server(setup_directory, relhome, enable_home) == 0) {
1177                 important_message("Citadel Setup",
1178                         "The Citadel service is still running.\n"
1179                         "Please stop the service manually and run "
1180                         "setup again.");
1181                 cleanup(1);
1182         }
1183
1184         /* Now begin. */
1185         switch (setup_type) {
1186
1187         case UI_TEXT:
1188                 printf("\n\n\n"
1189                         "              *** Citadel setup program ***\n\n");
1190                 break;
1191
1192         }
1193
1194         /*
1195          * What we're going to try to do here is append a whole bunch of
1196          * nulls to the citadel.config file, so we can keep the old config
1197          * values if they exist, but if the file is missing or from an
1198          * earlier version with a shorter config structure, when setup tries
1199          * to read the old config parameters, they'll all come up zero.
1200          * The length of the config file will be set to what it's supposed
1201          * to be when we rewrite it, because we replace the old file with a
1202          * completely new copy.
1203          */
1204         if ((a = open(file_citadel_config, O_WRONLY | O_CREAT | O_APPEND,
1205                       S_IRUSR | S_IWUSR)) == -1) {
1206                 display_error("setup: cannot append citadel.config");
1207                 cleanup(errno);
1208         }
1209         fp = fdopen(a, "ab");
1210         if (fp == NULL) {
1211                 display_error("setup: cannot append citadel.config");
1212                 cleanup(errno);
1213         }
1214         for (a = 0; a < sizeof(struct config); ++a)
1215                 putc(0, fp);
1216         fclose(fp);
1217
1218         /* now we re-open it, and read the old or blank configuration */
1219         fp = fopen(file_citadel_config, "rb");
1220         if (fp == NULL) {
1221                 display_error("setup: cannot open citadel.config");
1222                 cleanup(errno);
1223         }
1224         fread((char *) &config, sizeof(struct config), 1, fp);
1225         fclose(fp);
1226
1227         /* set some sample/default values in place of blanks... */
1228         if (IsEmptyStr(config.c_nodename))
1229                 safestrncpy(config.c_nodename, my_utsname.nodename,
1230                             sizeof config.c_nodename);
1231         strtok(config.c_nodename, ".");
1232         if (IsEmptyStr(config.c_fqdn) ) {
1233                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1234                         safestrncpy(config.c_fqdn, he->h_name,
1235                                     sizeof config.c_fqdn);
1236                 else
1237                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1238                                     sizeof config.c_fqdn);
1239         }
1240         if (IsEmptyStr(config.c_humannode))
1241                 strcpy(config.c_humannode, "My System");
1242         if (IsEmptyStr(config.c_phonenum))
1243                 strcpy(config.c_phonenum, "US 800 555 1212");
1244         if (config.c_initax == 0) {
1245                 config.c_initax = 4;
1246         }
1247         if (IsEmptyStr(config.c_moreprompt))
1248                 strcpy(config.c_moreprompt, "<more>");
1249         if (IsEmptyStr(config.c_twitroom))
1250                 strcpy(config.c_twitroom, "Trashcan");
1251         if (IsEmptyStr(config.c_baseroom))
1252                 strcpy(config.c_baseroom, BASEROOM);
1253         if (IsEmptyStr(config.c_aideroom))
1254                 strcpy(config.c_aideroom, "Aide");
1255         if (config.c_port_number == 0) {
1256                 config.c_port_number = 504;
1257         }
1258         if (config.c_sleeping == 0) {
1259                 config.c_sleeping = 900;
1260         }
1261         if (config.c_ctdluid == 0) {
1262                 pw = getpwnam("citadel");
1263                 if (pw != NULL)
1264                         config.c_ctdluid = pw->pw_uid;
1265         }
1266         if (config.c_ctdluid == 0) {
1267                 pw = getpwnam("bbs");
1268                 if (pw != NULL)
1269                         config.c_ctdluid = pw->pw_uid;
1270         }
1271         if (config.c_ctdluid == 0) {
1272                 pw = getpwnam("guest");
1273                 if (pw != NULL)
1274                         config.c_ctdluid = pw->pw_uid;
1275         }
1276         if (config.c_createax == 0) {
1277                 config.c_createax = 3;
1278         }
1279         /*
1280          * Negative values for maxsessions are not allowed.
1281          */
1282         if (config.c_maxsessions < 0) {
1283                 config.c_maxsessions = 0;
1284         }
1285         /* We need a system default message expiry policy, because this is
1286          * the top level and there's no 'higher' policy to fall back on.
1287          * By default, do not expire messages at all.
1288          */
1289         if (config.c_ep.expire_mode == 0) {
1290                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1291                 config.c_ep.expire_value = 0;
1292         }
1293
1294         /*
1295          * Default port numbers for various services
1296          */
1297         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1298         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1299         if (config.c_imap_port == 0) config.c_imap_port = 143;
1300         if (config.c_msa_port == 0) config.c_msa_port = 587;
1301         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1302         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1303         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1304         if (config.c_pftcpdict_port == 0) config.c_pftcpdict_port = -1;
1305         if (config.c_managesieve_port == 0) config.c_managesieve_port = 2020;
1306         if (config.c_xmpp_c2s_port == 0) config.c_xmpp_c2s_port = 5222;
1307         if (config.c_xmpp_s2s_port == 0) config.c_xmpp_s2s_port = 5269;
1308
1309         /* Go through a series of dialogs prompting for config info */
1310         for (curr = 1; curr <= MAXSETUP; ++curr) {
1311                 edit_value(curr);
1312                 if ((curr == 6) && (config.c_auth_mode != AUTHMODE_LDAP)) curr += 5;    /* skip LDAP questions */
1313         }
1314
1315 /***** begin version update section ***** */
1316         /* take care of any updating that is necessary */
1317
1318         old_setup_level = config.c_setup_level;
1319
1320         if (old_setup_level == 0) {
1321                 goto NEW_INST;
1322         }
1323
1324         if (old_setup_level < 555) {
1325                 important_message("Citadel Setup",
1326                                   "This Citadel installation is too old "
1327                                   "to be upgraded.");
1328                 cleanup(1);
1329         }
1330         write_config_to_disk();
1331
1332         old_setup_level = config.c_setup_level;
1333
1334         /* end of version update section */
1335
1336 NEW_INST:
1337         config.c_setup_level = REV_LEVEL;
1338
1339 /******************************************/
1340
1341         write_config_to_disk();
1342
1343         mkdir(ctdl_info_dir, 0700);
1344         chmod(ctdl_info_dir, 0700);
1345         chown(ctdl_info_dir, config.c_ctdluid, -1);
1346
1347         mkdir(ctdl_bio_dir, 0700);
1348         chmod(ctdl_bio_dir, 0700);
1349         chown(ctdl_bio_dir, config.c_ctdluid, -1);
1350
1351         mkdir(ctdl_usrpic_dir, 0700);
1352         chmod(ctdl_usrpic_dir, 0700);
1353         chown(ctdl_usrpic_dir, config.c_ctdluid, -1);
1354
1355         mkdir(ctdl_message_dir, 0700);
1356         chmod(ctdl_message_dir, 0700);
1357         chown(ctdl_message_dir, config.c_ctdluid, -1);
1358
1359         mkdir(ctdl_hlp_dir, 0700);
1360         chmod(ctdl_hlp_dir, 0700);
1361         chown(ctdl_hlp_dir, config.c_ctdluid, -1);
1362
1363         mkdir(ctdl_image_dir, 0700);
1364         chmod(ctdl_image_dir, 0700);
1365         chown(ctdl_image_dir, config.c_ctdluid, -1);
1366
1367         mkdir(ctdl_bb_dir, 0700);
1368         chmod(ctdl_bb_dir, 0700);
1369         chown(ctdl_bb_dir, config.c_ctdluid, -1);
1370
1371         mkdir(ctdl_file_dir, 0700);
1372         chmod(ctdl_file_dir, 0700);
1373         chown(ctdl_file_dir, config.c_ctdluid, -1);
1374
1375         mkdir(ctdl_netcfg_dir, 0700);
1376         chmod(ctdl_netcfg_dir, 0700);
1377         chown(ctdl_netcfg_dir, config.c_ctdluid, -1);
1378
1379         /* Delete files and directories used by older Citadel versions */
1380         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1381         unlink("citadel.log");
1382         unlink("weekly");
1383
1384         if (((setup_type == UI_SILENT) && (getenv("ALTER_ETC_SERVICES")!=NULL)) || 
1385             (setup_type != UI_SILENT))
1386                 check_services_entry(); /* Check /etc/services */
1387 #ifndef __CYGWIN__
1388         delete_inittab_entry(); /* Remove obsolete /etc/inittab entry */
1389         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1390
1391         if ((getenv("ACT_AS_MTA") == NULL) || 
1392             (getenv("ACT_AS_MTA") &&
1393              strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
1394                 /* Offer to disable other MTA's on the system. */
1395                 disable_other_mta("courier-authdaemon");
1396                 disable_other_mta("courier-imap");
1397                 disable_other_mta("courier-imap-ssl");
1398                 disable_other_mta("courier-pop");
1399                 disable_other_mta("courier-pop3");
1400                 disable_other_mta("courier-pop3d");
1401                 disable_other_mta("cyrmaster");
1402                 disable_other_mta("cyrus");
1403                 disable_other_mta("dovecot");
1404                 disable_other_mta("exim");
1405                 disable_other_mta("exim4");
1406                 disable_other_mta("imapd");
1407                 disable_other_mta("mta");
1408                 disable_other_mta("pop3d");
1409                 disable_other_mta("popd");
1410                 disable_other_mta("postfix");
1411                 disable_other_mta("qmail");
1412                 disable_other_mta("saslauthd");
1413                 disable_other_mta("sendmail");
1414                 disable_other_mta("vmailmgrd");
1415         }
1416 #endif
1417
1418         /* Check for the 'db' nss and offer to disable it */
1419         fixnss();
1420
1421         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1422                 gid = getgid();
1423         else
1424                 gid = pw->pw_gid;
1425
1426         progress("Setting file permissions", 0, 3);
1427         chown(ctdl_run_dir, config.c_ctdluid, gid);
1428         progress("Setting file permissions", 1, 3);
1429         chown(file_citadel_config, config.c_ctdluid, gid);
1430         progress("Setting file permissions", 2, 3);
1431         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1432         progress("Setting file permissions", 3, 3);
1433
1434         /* 
1435          * If we're running on SysV, install init scripts.
1436          */
1437         if (!access("/var/run", W_OK)) {
1438
1439                 if (getenv("NO_INIT_SCRIPTS") == NULL) {
1440                         install_init_scripts();
1441                 }
1442
1443                 if (!access("/etc/init.d/citadel", X_OK)) {
1444                         system("/etc/init.d/citadel start");
1445                         sleep(3);
1446                 }
1447
1448                 if (test_server(setup_directory, relhome, enable_home) == 0) {
1449                         char buf[SIZ];
1450                         int found_it = 0;
1451
1452                         snprintf (admin_cmd, sizeof(admin_cmd), "%s/sendcommand \"CREU %s|%s\" 2>&1", 
1453                                   ctdl_sbin_dir, config.c_sysadm, admin_pass);
1454                         fp = popen(admin_cmd, "r");
1455                         if (fp != NULL) {
1456                                 while (fgets(buf, sizeof buf, fp) != NULL) 
1457                                 {
1458                                         if ((atol(buf) == 574) || (atol(buf) == 200))
1459                                                 ++found_it;
1460                                 }
1461                                 pclose(fp);
1462                         }
1463                 
1464                         if (found_it == 0)
1465                                 important_message("Error","Setup failed to create your admin user");
1466
1467                         if (setup_type != UI_SILENT)
1468                                 important_message("Setup finished",
1469                                                   "Setup of the Citadel server is complete.\n"
1470                                                   "If you will be using WebCit, please run its\n"
1471                                                   "setup program now; otherwise, run './citadel'\n"
1472                                                   "to log in.\n");
1473                 }
1474                 else {
1475                         important_message("Setup failed",
1476                                 "Setup is finished, but the Citadel server failed to start.\n"
1477                                 "Go back and check your configuration.\n"
1478                         );
1479                 }
1480
1481         }
1482
1483         else {
1484                 important_message("Setup finished",
1485                         "Setup is finished.  You may now start the server.");
1486         }
1487
1488         cleanup(0);
1489         return 0;
1490 }
1491
1492