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