* we didn't calculate our directories in a proper sequence. fixed hereby.
[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                 else if (!strcmp(argv[a], "-i")) {
971                         info_only = 1;
972                 }
973                 else if (!strcmp(argv[a], "-q")) {
974                         setup_type = UI_SILENT;
975                 }
976                 else if (!strncmp(argv[a], "-h", 2)) {
977                         relh=argv[a][2]!='/';
978                         if (!relh) safestrncpy(ctdl_home_directory, &argv[a][2],
979                                                                    sizeof ctdl_home_directory);
980                         else
981                                 safestrncpy(relhome, &argv[a][2],
982                                                         sizeof relhome);
983                         home_specified = 1;
984                         home=1;
985                 }
986
987         }
988
989         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
990
991         /* If a setup type was not specified, try to determine automatically
992          * the best one to use out of all available types.
993          */
994         if (setup_type < 0) {
995                 setup_type = discover_ui();
996         }
997         if (info_only == 1) {
998                 important_message("Citadel Setup", CITADEL);
999                 cleanup(0);
1000         }
1001
1002         /* Get started in a valid setup directory. */
1003         strcpy(setup_directory, ctdl_run_dir);
1004         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
1005                 strcpy(setup_directory, getenv("CITADEL"));
1006         }
1007         else {
1008                 set_str_val(0, setup_directory);
1009         }
1010
1011         enable_home=(relh|home);
1012
1013         if (home) {
1014                 if (chdir(setup_directory) == 0) {
1015                         strcpy(file_citadel_config, "./citadel.config");
1016                 }
1017                 else {
1018                         important_message("Citadel Setup",
1019                                 "The directory you specified does not exist.");
1020                         cleanup(errno);
1021                 }
1022         }
1023
1024         /* Determine our host name, in case we need to use it as a default */
1025         uname(&my_utsname);
1026
1027         /* Try to stop Citadel if we can */
1028         if (!access("/etc/init.d/citadel", X_OK)) {
1029                 system("/etc/init.d/citadel stop");
1030         }
1031
1032         /* Make sure Citadel is not running. */
1033         if (test_server() == 0) {
1034                 important_message("Citadel Setup",
1035                         "The Citadel service is still running.\n"
1036                         "Please stop the service manually and run "
1037                         "setup again.");
1038                 cleanup(1);
1039         }
1040
1041         /* Now begin. */
1042         switch (setup_type) {
1043
1044         case UI_TEXT:
1045                 printf("\n\n\n"
1046                         "              *** Citadel setup program ***\n\n");
1047                 break;
1048
1049         }
1050
1051         /*
1052          * What we're going to try to do here is append a whole bunch of
1053          * nulls to the citadel.config file, so we can keep the old config
1054          * values if they exist, but if the file is missing or from an
1055          * earlier version with a shorter config structure, when setup tries
1056          * to read the old config parameters, they'll all come up zero.
1057          * The length of the config file will be set to what it's supposed
1058          * to be when we rewrite it, because we replace the old file with a
1059          * completely new copy.
1060          */
1061         if ((a = open(file_citadel_config, O_WRONLY | O_CREAT | O_APPEND,
1062                       S_IRUSR | S_IWUSR)) == -1) {
1063                 display_error("setup: cannot append citadel.config");
1064                 cleanup(errno);
1065         }
1066         fp = fdopen(a, "ab");
1067         if (fp == NULL) {
1068                 display_error("setup: cannot append citadel.config");
1069                 cleanup(errno);
1070         }
1071         for (a = 0; a < sizeof(struct config); ++a)
1072                 putc(0, fp);
1073         fclose(fp);
1074
1075         /* now we re-open it, and read the old or blank configuration */
1076         fp = fopen(file_citadel_config, "rb");
1077         if (fp == NULL) {
1078                 display_error("setup: cannot open citadel.config");
1079                 cleanup(errno);
1080         }
1081         fread((char *) &config, sizeof(struct config), 1, fp);
1082         fclose(fp);
1083
1084         /* set some sample/default values in place of blanks... */
1085         if (IsEmptyStr(config.c_nodename))
1086                 safestrncpy(config.c_nodename, my_utsname.nodename,
1087                             sizeof config.c_nodename);
1088         strtok(config.c_nodename, ".");
1089         if (IsEmptyStr(config.c_fqdn) ) {
1090                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1091                         safestrncpy(config.c_fqdn, he->h_name,
1092                                     sizeof config.c_fqdn);
1093                 else
1094                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1095                                     sizeof config.c_fqdn);
1096         }
1097         if (IsEmptyStr(config.c_humannode))
1098                 strcpy(config.c_humannode, "My System");
1099         if (IsEmptyStr(config.c_phonenum))
1100                 strcpy(config.c_phonenum, "US 800 555 1212");
1101         if (config.c_initax == 0) {
1102                 config.c_initax = 4;
1103         }
1104         if (IsEmptyStr(config.c_moreprompt))
1105                 strcpy(config.c_moreprompt, "<more>");
1106         if (IsEmptyStr(config.c_twitroom))
1107                 strcpy(config.c_twitroom, "Trashcan");
1108         if (IsEmptyStr(config.c_baseroom))
1109                 strcpy(config.c_baseroom, BASEROOM);
1110         if (IsEmptyStr(config.c_aideroom))
1111                 strcpy(config.c_aideroom, "Aide");
1112         if (config.c_port_number == 0) {
1113                 config.c_port_number = 504;
1114         }
1115         if (config.c_sleeping == 0) {
1116                 config.c_sleeping = 900;
1117         }
1118         if (config.c_ctdluid == 0) {
1119                 pw = getpwnam("citadel");
1120                 if (pw != NULL)
1121                         config.c_ctdluid = pw->pw_uid;
1122         }
1123         if (config.c_ctdluid == 0) {
1124                 pw = getpwnam("bbs");
1125                 if (pw != NULL)
1126                         config.c_ctdluid = pw->pw_uid;
1127         }
1128         if (config.c_ctdluid == 0) {
1129                 pw = getpwnam("guest");
1130                 if (pw != NULL)
1131                         config.c_ctdluid = pw->pw_uid;
1132         }
1133         if (config.c_createax == 0) {
1134                 config.c_createax = 3;
1135         }
1136         /*
1137          * Negative values for maxsessions are not allowed.
1138          */
1139         if (config.c_maxsessions < 0) {
1140                 config.c_maxsessions = 0;
1141         }
1142         /* We need a system default message expiry policy, because this is
1143          * the top level and there's no 'higher' policy to fall back on.
1144          * By default, do not expire messages at all.
1145          */
1146         if (config.c_ep.expire_mode == 0) {
1147                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1148                 config.c_ep.expire_value = 0;
1149         }
1150
1151         /*
1152          * Default port numbers for various services
1153          */
1154         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1155         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1156         if (config.c_imap_port == 0) config.c_imap_port = 143;
1157         if (config.c_msa_port == 0) config.c_msa_port = 587;
1158         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1159         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1160         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1161         if (config.c_pftcpdict_port == 0) config.c_pftcpdict_port = -1;
1162         if (config.c_managesieve_port == 0) config.c_managesieve_port = 2020;
1163         if (config.c_xmpp_c2s_port == 0) config.c_xmpp_c2s_port = 5222;
1164         if (config.c_xmpp_s2s_port == 0) config.c_xmpp_s2s_port = 5269;
1165
1166         /* Go through a series of dialogs prompting for config info */
1167         for (curr = 1; curr <= MAXSETUP; ++curr) {
1168                 edit_value(curr);
1169         }
1170
1171 /***** begin version update section ***** */
1172         /* take care of any updating that is necessary */
1173
1174         old_setup_level = config.c_setup_level;
1175
1176         if (old_setup_level == 0) {
1177                 goto NEW_INST;
1178         }
1179
1180         if (old_setup_level < 555) {
1181                 important_message("Citadel Setup",
1182                                   "This Citadel installation is too old "
1183                                   "to be upgraded.");
1184                 cleanup(1);
1185         }
1186         write_config_to_disk();
1187
1188         old_setup_level = config.c_setup_level;
1189
1190         /* end of version update section */
1191
1192 NEW_INST:
1193         config.c_setup_level = REV_LEVEL;
1194
1195 /******************************************/
1196
1197         write_config_to_disk();
1198
1199         mkdir(ctdl_info_dir, 0700);
1200         chmod(ctdl_info_dir, 0700);
1201         chown(ctdl_info_dir, config.c_ctdluid, -1);
1202
1203         mkdir(ctdl_bio_dir, 0700);
1204         chmod(ctdl_bio_dir, 0700);
1205         chown(ctdl_bio_dir, config.c_ctdluid, -1);
1206
1207         mkdir(ctdl_usrpic_dir, 0700);
1208         chmod(ctdl_usrpic_dir, 0700);
1209         chown(ctdl_usrpic_dir, config.c_ctdluid, -1);
1210
1211         mkdir(ctdl_message_dir, 0700);
1212         chmod(ctdl_message_dir, 0700);
1213         chown(ctdl_message_dir, config.c_ctdluid, -1);
1214
1215         mkdir(ctdl_hlp_dir, 0700);
1216         chmod(ctdl_hlp_dir, 0700);
1217         chown(ctdl_hlp_dir, config.c_ctdluid, -1);
1218
1219         mkdir(ctdl_image_dir, 0700);
1220         chmod(ctdl_image_dir, 0700);
1221         chown(ctdl_image_dir, config.c_ctdluid, -1);
1222
1223         mkdir(ctdl_bb_dir, 0700);
1224         chmod(ctdl_bb_dir, 0700);
1225         chown(ctdl_bb_dir, config.c_ctdluid, -1);
1226
1227         mkdir(ctdl_file_dir, 0700);
1228         chmod(ctdl_file_dir, 0700);
1229         chown(ctdl_file_dir, config.c_ctdluid, -1);
1230
1231         mkdir(ctdl_netcfg_dir, 0700);
1232         chmod(ctdl_netcfg_dir, 0700);
1233         chown(ctdl_netcfg_dir, config.c_ctdluid, -1);
1234
1235         /* Delete files and directories used by older Citadel versions */
1236         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1237         unlink("citadel.log");
1238         unlink("weekly");
1239
1240         check_services_entry(); /* Check /etc/services */
1241 #ifndef __CYGWIN__
1242         delete_inittab_entry(); /* Remove obsolete /etc/inittab entry */
1243         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1244
1245         /* Offer to disable other MTA's on the system. */
1246         disable_other_mta("courier-authdaemon");
1247         disable_other_mta("courier-imap");
1248         disable_other_mta("courier-imap-ssl");
1249         disable_other_mta("courier-pop");
1250         disable_other_mta("courier-pop3");
1251         disable_other_mta("courier-pop3d");
1252         disable_other_mta("cyrmaster");
1253         disable_other_mta("cyrus");
1254         disable_other_mta("dovecot");
1255         disable_other_mta("exim");
1256         disable_other_mta("exim4");
1257         disable_other_mta("imapd");
1258         disable_other_mta("mta");
1259         disable_other_mta("pop3d");
1260         disable_other_mta("popd");
1261         disable_other_mta("postfix");
1262         disable_other_mta("qmail");
1263         disable_other_mta("saslauthd");
1264         disable_other_mta("sendmail");
1265         disable_other_mta("vmailmgrd");
1266 #endif
1267
1268         /* Check for the 'db' nss and offer to disable it */
1269         fixnss();
1270
1271         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1272                 gid = getgid();
1273         else
1274                 gid = pw->pw_gid;
1275
1276         progress("Setting file permissions", 0, 3);
1277         chown(ctdl_run_dir, config.c_ctdluid, gid);
1278         progress("Setting file permissions", 1, 3);
1279         chown(file_citadel_config, config.c_ctdluid, gid);
1280         progress("Setting file permissions", 2, 3);
1281         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1282         progress("Setting file permissions", 3, 3);
1283
1284         /* 
1285          * If we're running on SysV, install init scripts.
1286          */
1287         if (!access("/var/run", W_OK)) {
1288
1289                 if (getenv("NO_INIT_SCRIPTS") == NULL) {
1290                         install_init_scripts();
1291                 }
1292
1293                 if (!access("/etc/init.d/citadel", X_OK)) {
1294                         system("/etc/init.d/citadel start");
1295                         sleep(3);
1296                 }
1297
1298                 if (test_server() == 0) {
1299                         important_message("Setup finished",
1300                                 "Setup of the Citadel server is complete.\n"
1301                                 "If you will be using WebCit, please run its\n"
1302                                 "setup program now; otherwise, run './citadel'\n"
1303                                 "to log in.\n");
1304                 }
1305                 else {
1306                         important_message("Setup failed",
1307                                 "Setup is finished, but the Citadel server failed to start.\n"
1308                                 "Go back and check your configuration.\n"
1309                         );
1310                 }
1311
1312         }
1313
1314         else {
1315                 important_message("Setup finished",
1316                         "Setup is finished.  You may now start the server.");
1317         }
1318
1319         cleanup(0);
1320         return 0;
1321 }
1322
1323