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