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