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