HUGE PATCH. This moves all of mime_parser.c and all
[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)       $0 stop\n"
450                         "               $0 start\n"
451                         "               ;;\n"
452                         "*)             echo \"Usage: $0 {start|stop|restart}\"\n"
453                         "               exit 1\n"
454                         "               ;;\n"
455                         "esac\n"
456         );
457
458         fclose(fp);
459         chmod(initfile, 0755);
460
461         /* Set up the run levels. */
462         system("/bin/rm -f /etc/rc?.d/[SK]??citadel 2>/dev/null");
463         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);
464         system(command);
465         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);
466         system(command);
467
468 }
469
470
471
472
473
474
475 /*
476  * On systems which use xinetd, see if we can offer to install Citadel as
477  * the default telnet target.
478  */
479 void check_xinetd_entry(void) {
480         char *filename = "/etc/xinetd.d/telnet";
481         FILE *fp;
482         char buf[SIZ];
483         int already_citadel = 0;
484
485         fp = fopen(filename, "r+");
486         if (fp == NULL) return;         /* Not there.  Oh well... */
487
488         while (fgets(buf, sizeof buf, fp) != NULL) {
489                 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
490         }
491         fclose(fp);
492         if (already_citadel) return;    /* Already set up this way. */
493
494         /* Otherwise, prompt the user to create an entry. */
495         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
496                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
497                         return;
498                 }
499         }
500         else {
501                 snprintf(buf, sizeof buf,
502                         "Setup can configure the \"xinetd\" service to automatically\n"
503                         "connect incoming telnet sessions to Citadel, bypassing the\n"
504                         "host system login: prompt.  Would you like to do this?\n"
505                 );
506                 if (yesno(buf, 1) == 0) {
507                         return;
508                 }
509         }
510
511         fp = fopen(filename, "w");
512         fprintf(fp,
513                 "# description: telnet service for Citadel users\n"
514                 "service telnet\n"
515                 "{\n"
516                 "       disable = no\n"
517                 "       flags           = REUSE\n"
518                 "       socket_type     = stream\n"
519                 "       wait            = no\n"
520                 "       user            = root\n"
521                 "       server          = /usr/sbin/in.telnetd\n"
522                 "       server_args     = -h -L %s/citadel\n"
523                 "       log_on_failure  += USERID\n"
524                 "}\n",
525                 ctdl_bin_dir);
526         fclose(fp);
527
528         /* Now try to restart the service */
529         system("/etc/init.d/xinetd restart >/dev/null 2>&1");
530 }
531
532
533
534 /*
535  * Offer to disable other MTA's
536  */
537 void disable_other_mta(char *mta) {
538         char buf[SIZ];
539         FILE *fp;
540         int lines = 0;
541
542         sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
543                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
544                 mta, mta);
545         fp = popen(buf, "r");
546         if (fp == NULL) return;
547
548         while (fgets(buf, sizeof buf, fp) != NULL) {
549                 ++lines;
550         }
551         fclose(fp);
552         if (lines == 0) return;         /* Nothing to do. */
553
554
555         /* Offer to replace other MTA with the vastly superior Citadel :)  */
556
557         if (getenv("ACT_AS_MTA")) {
558                 if (strcasecmp(getenv("ACT_AS_MTA"), "yes")) {
559                         return;
560                 }
561         }
562         else {
563                 snprintf(buf, sizeof buf,
564                         "You appear to have the \"%s\" email program\n"
565                         "running on your system.  If you want Citadel mail\n"
566                         "connected with %s, you will have to manually integrate\n"
567                         "them.  It is preferable to disable %s, and use Citadel's\n"
568                         "SMTP, POP3, and IMAP services.\n\n"
569                         "May we disable %s so that Citadel has access to ports\n"
570                         "25, 110, and 143?\n",
571                         mta, mta, mta, mta
572                 );
573                 if (yesno(buf, 1) == 0) {
574                         return;
575                 }
576         }
577
578         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);
579         system(buf);
580         sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
581         system(buf);
582 }
583
584
585
586
587 /* 
588  * Check to see if our server really works.  Returns 0 on success.
589  */
590 int test_server(void) {
591         char cmd[256];
592         char cookie[256];
593         FILE *fp;
594         char buf[4096];
595         int found_it = 0;
596
597         /* Generate a silly little cookie.  We're going to write it out
598          * to the server and try to get it back.  The cookie does not
599          * have to be secret ... just unique.
600          */
601         sprintf(cookie, "--test--%d--", getpid());
602
603         sprintf(cmd, "%s/sendcommand %s%s ECHO %s 2>&1",
604                 ctdl_sbin_dir,
605                 (enable_home)?"-h":"", 
606                 (enable_home)?ctdl_run_dir:"",
607                 cookie);
608
609         fp = popen(cmd, "r");
610         if (fp == NULL) return(errno);
611
612         while (fgets(buf, sizeof buf, fp) != NULL) {
613                 if ( (buf[0]=='2')
614                    && (strstr(buf, cookie) != NULL) ) {
615                         ++found_it;
616                 }
617         }
618         pclose(fp);
619
620         if (found_it) {
621                 return(0);
622         }
623         return(-1);
624 }
625
626 void strprompt(char *prompt_title, char *prompt_text, char *str)
627 {
628         char buf[SIZ];
629         char setupmsg[SIZ];
630         char dialog_result[PATH_MAX];
631         FILE *fp = NULL;
632
633         strcpy(setupmsg, "");
634
635         switch (setup_type) {
636         case UI_TEXT:
637                 title(prompt_title);
638                 printf("\n%s\n", prompt_text);
639                 printf("This is currently set to:\n%s\n", str);
640                 printf("Enter new value or press return to leave unchanged:\n");
641                 fgets(buf, sizeof buf, stdin);
642                 buf[strlen(buf) - 1] = 0;
643                 if (!IsEmptyStr(buf))
644                         strcpy(str, buf);
645                 break;
646
647         case UI_DIALOG:
648                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
649                 sprintf(buf, "exec %s --inputbox '%s' 19 72 '%s' 2>%s",
650                         getenv("CTDL_DIALOG"),
651                         prompt_text,
652                         str,
653                         dialog_result);
654                 system(buf);
655                 fp = fopen(dialog_result, "r");
656                 if (fp != NULL) {
657                         fgets(str, sizeof buf, fp);
658                         if (str[strlen(str)-1] == 10) {
659                                 str[strlen(str)-1] = 0;
660                         }
661                         fclose(fp);
662                         unlink(dialog_result);
663                 }
664                 break;
665
666         }
667 }
668
669 void set_bool_val(int msgpos, int *ip) {
670         title(setup_titles[msgpos]);
671         *ip = yesno(setup_text[msgpos], *ip);
672 }
673
674 void set_str_val(int msgpos, char *str) {
675         strprompt(setup_titles[msgpos], setup_text[msgpos], str);
676 }
677
678 void set_int_val(int msgpos, int *ip)
679 {
680         char buf[16];
681         snprintf(buf, sizeof buf, "%d", (int) *ip);
682         set_str_val(msgpos, buf);
683         *ip = atoi(buf);
684 }
685
686
687 void set_char_val(int msgpos, char *ip)
688 {
689         char buf[16];
690         snprintf(buf, sizeof buf, "%d", (int) *ip);
691         set_str_val(msgpos, buf);
692         *ip = (char) atoi(buf);
693 }
694
695
696 void set_long_val(int msgpos, long int *ip)
697 {
698         char buf[16];
699         snprintf(buf, sizeof buf, "%ld", *ip);
700         set_str_val(msgpos, buf);
701         *ip = atol(buf);
702 }
703
704
705 void edit_value(int curr)
706 {
707         int i;
708         struct passwd *pw;
709         char ctdluidname[256];
710
711         switch (curr) {
712
713         case 1:
714                 if (setup_type == UI_SILENT)
715                 {
716                         if (getenv("SYSADMIN_NAME")) {
717                                 strcpy(config.c_sysadm, getenv("SYSADMIN_NAME"));
718                         }
719                 }
720                 else {
721                         set_str_val(curr, config.c_sysadm);
722                 }
723                 break;
724
725         case 2:
726                 if (setup_type == UI_SILENT)
727                 {               
728                         if (getenv("CITADEL_UID")) {
729                                 config.c_ctdluid = atoi(getenv("CITADEL_UID"));
730                         }                                       
731                 }
732                 else
733                 {
734 #ifdef __CYGWIN__
735                         config.c_ctdluid = 0;   /* XXX Windows hack, prob. insecure */
736 #else
737                         i = config.c_ctdluid;
738                         pw = getpwuid(i);
739                         if (pw == NULL) {
740                                 set_int_val(curr, &i);
741                                 config.c_ctdluid = i;
742                         }
743                         else {
744                                 strcpy(ctdluidname, pw->pw_name);
745                                 set_str_val(curr, ctdluidname);
746                                 pw = getpwnam(ctdluidname);
747                                 if (pw != NULL) {
748                                         config.c_ctdluid = pw->pw_uid;
749                                 }
750                                 else if (atoi(ctdluidname) > 0) {
751                                         config.c_ctdluid = atoi(ctdluidname);
752                                 }
753                         }
754 #endif
755                 }
756                 break;
757
758         case 3:
759                 if (setup_type == UI_SILENT)
760                 {
761                         if (getenv("IP_ADDR")) {
762                                 strcpy(config.c_ip_addr, getenv("IP_ADDR"));
763                         }
764                 }
765                 else {
766                         set_str_val(curr, config.c_ip_addr);
767                 }
768                 break;
769
770         case 4:
771                 if (setup_type == UI_SILENT)
772                 {
773                         if (getenv("CITADEL_PORT")) {
774                                 config.c_port_number = atoi(getenv("CITADEL_PORT"));
775                         }
776                 }
777                 else
778                 {
779                         set_int_val(curr, &config.c_port_number);
780                 }
781                 break;
782
783         case 5:
784                 if (setup_type == UI_SILENT)
785                 {
786                         if (getenv("ENABLE_UNIX_AUTH")) {
787                                 if (!strcasecmp(getenv("ENABLE_UNIX_AUTH"), "yes")) {
788                                         config.c_auth_mode = 1;
789                                 }
790                                 else {
791                                         config.c_auth_mode = 0;
792                                 }
793                         }
794                 }
795                 else {
796                         set_bool_val(curr, &config.c_auth_mode);
797                 }
798                 break;
799
800         }
801 }
802
803 /*
804  * (re-)write the config data to disk
805  */
806 void write_config_to_disk(void)
807 {
808         FILE *fp;
809         int fd;
810
811         if ((fd = creat(file_citadel_config, S_IRUSR | S_IWUSR)) == -1) {
812                 display_error("setup: cannot open citadel.config");
813                 cleanup(1);
814         }
815         fp = fdopen(fd, "wb");
816         if (fp == NULL) {
817                 display_error("setup: cannot open citadel.config");
818                 cleanup(1);
819         }
820         fwrite((char *) &config, sizeof(struct config), 1, fp);
821         fclose(fp);
822 }
823
824
825
826
827 /*
828  * Figure out what type of user interface we're going to use
829  */
830 int discover_ui(void)
831 {
832
833         /* Use "dialog" if we have it */
834         if (getenv("CTDL_DIALOG") != NULL) {
835                 return UI_DIALOG;
836         }
837                 
838         return UI_TEXT;
839 }
840
841
842
843
844
845 /*
846  * Strip "db" entries out of /etc/nsswitch.conf
847  */
848 void fixnss(void) {
849         FILE *fp_read;
850         int fd_write;
851         char buf[256];
852         char buf_nc[256];
853         char question[512];
854         int i;
855         int changed = 0;
856         int file_changed = 0;
857         char new_filename[64];
858
859         fp_read = fopen(NSSCONF, "r");
860         if (fp_read == NULL) {
861                 return;
862         }
863
864         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
865         fd_write = mkstemp(new_filename);
866         if (fd_write < 0) {
867                 fclose(fp_read);
868                 return;
869         }
870
871         while (fgets(buf, sizeof buf, fp_read) != NULL) {
872                 changed = 0;
873                 strcpy(buf_nc, buf);
874                 for (i=0; i<strlen(buf_nc); ++i) {
875                         if (buf_nc[i] == '#') {
876                                 buf_nc[i] = 0;
877                         }
878                 }
879                 for (i=0; i<strlen(buf_nc); ++i) {
880                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
881                                 if (i > 0) {
882                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
883                                                 changed = 1;
884                                                 file_changed = 1;
885                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
886                                                 strcpy(&buf[i], &buf[i+2]);
887                                                 if (buf[i]==32) {
888                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
889                                                         strcpy(&buf[i], &buf[i+1]);
890                                                 }
891                                         }
892                                 }
893                         }
894                 }
895                 if (write(fd_write, buf, strlen(buf)) != strlen(buf)) {
896                         fclose(fp_read);
897                         close(fd_write);
898                         unlink(new_filename);
899                         return;
900                 }
901         }
902
903         fclose(fp_read);
904         
905         if (!file_changed) {
906                 unlink(new_filename);
907                 return;
908         }
909
910         snprintf(question, sizeof question,
911                 "\n"
912                 "/etc/nsswitch.conf is configured to use the 'db' module for\n"
913                 "one or more services.  This is not necessary on most systems,\n"
914                 "and it is known to crash the Citadel server when delivering\n"
915                 "mail to the Internet.\n"
916                 "\n"
917                 "Do you want this module to be automatically disabled?\n"
918                 "\n"
919         );
920
921         if (yesno(question, 1)) {
922                 sprintf(buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
923                 system(buf);
924         }
925         unlink(new_filename);
926 }
927
928
929
930
931
932
933
934
935 int main(int argc, char *argv[])
936 {
937         int a;
938         int curr; 
939         char aaa[128];
940         FILE *fp;
941         int old_setup_level = 0;
942         int info_only = 0;
943         struct utsname my_utsname;
944         struct passwd *pw;
945         struct hostent *he;
946         gid_t gid;
947         int relh=0;
948         int home=0;
949         char relhome[PATH_MAX]="";
950         char ctdldir[PATH_MAX]=CTDLDIR;
951         
952         CtdlInitBase64Table();
953
954         /* set an invalid setup type */
955         setup_type = (-1);
956
957         /* Check to see if we're running the web installer */
958         if (getenv("CITADEL_INSTALLER") != NULL) {
959                 using_web_installer = 1;
960         }
961
962         /* parse command line args */
963         for (a = 0; a < argc; ++a) {
964                 if (!strncmp(argv[a], "-u", 2)) {
965                         strcpy(aaa, argv[a]);
966                         strcpy(aaa, &aaa[2]);
967                         setup_type = atoi(aaa);
968                 }
969                 if (!strcmp(argv[a], "-i")) {
970                         info_only = 1;
971                 }
972                 if (!strcmp(argv[a], "-q")) {
973                         setup_type = UI_SILENT;
974                 }
975         }
976
977
978         /* If a setup type was not specified, try to determine automatically
979          * the best one to use out of all available types.
980          */
981         if (setup_type < 0) {
982                 setup_type = discover_ui();
983         }
984         if (info_only == 1) {
985                 important_message("Citadel Setup", CITADEL);
986                 cleanup(0);
987         }
988
989         /* Get started in a valid setup directory. */
990         strcpy(setup_directory, ctdl_run_dir);
991         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
992                 strcpy(setup_directory, getenv("CITADEL"));
993         }
994         else {
995                 set_str_val(0, setup_directory);
996         }
997
998         home=(setup_directory[1]!='\0');
999         relh=home&(setup_directory[1]!='/');
1000         if (!relh) {
1001                 safestrncpy(ctdl_home_directory, setup_directory, sizeof ctdl_home_directory);
1002         }
1003         else {
1004                 safestrncpy(relhome, ctdl_home_directory, sizeof relhome);
1005         }
1006
1007         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1008         
1009         enable_home=(relh|home);
1010
1011         if (home) {
1012                 if (chdir(setup_directory) == 0) {
1013                         strcpy(file_citadel_config, "./citadel.config");
1014                 }
1015                 else {
1016                         important_message("Citadel Setup",
1017                                 "The directory you specified does not exist.");
1018                         cleanup(errno);
1019                 }
1020         }
1021
1022         /* Determine our host name, in case we need to use it as a default */
1023         uname(&my_utsname);
1024
1025         /* Try to stop Citadel if we can */
1026         if (!access("/etc/init.d/citadel", X_OK)) {
1027                 system("/etc/init.d/citadel stop");
1028         }
1029
1030         /* Make sure Citadel is not running. */
1031         if (test_server() == 0) {
1032                 important_message("Citadel Setup",
1033                         "The Citadel service is still running.\n"
1034                         "Please stop the service manually and run "
1035                         "setup again.");
1036                 cleanup(1);
1037         }
1038
1039         /* Now begin. */
1040         switch (setup_type) {
1041
1042         case UI_TEXT:
1043                 printf("\n\n\n"
1044                         "              *** Citadel setup program ***\n\n");
1045                 break;
1046
1047         }
1048
1049         /*
1050          * What we're going to try to do here is append a whole bunch of
1051          * nulls to the citadel.config file, so we can keep the old config
1052          * values if they exist, but if the file is missing or from an
1053          * earlier version with a shorter config structure, when setup tries
1054          * to read the old config parameters, they'll all come up zero.
1055          * The length of the config file will be set to what it's supposed
1056          * to be when we rewrite it, because we replace the old file with a
1057          * completely new copy.
1058          */
1059         if ((a = open(file_citadel_config, O_WRONLY | O_CREAT | O_APPEND,
1060                       S_IRUSR | S_IWUSR)) == -1) {
1061                 display_error("setup: cannot append citadel.config");
1062                 cleanup(errno);
1063         }
1064         fp = fdopen(a, "ab");
1065         if (fp == NULL) {
1066                 display_error("setup: cannot append citadel.config");
1067                 cleanup(errno);
1068         }
1069         for (a = 0; a < sizeof(struct config); ++a)
1070                 putc(0, fp);
1071         fclose(fp);
1072
1073         /* now we re-open it, and read the old or blank configuration */
1074         fp = fopen(file_citadel_config, "rb");
1075         if (fp == NULL) {
1076                 display_error("setup: cannot open citadel.config");
1077                 cleanup(errno);
1078         }
1079         fread((char *) &config, sizeof(struct config), 1, fp);
1080         fclose(fp);
1081
1082         /* set some sample/default values in place of blanks... */
1083         if (IsEmptyStr(config.c_nodename))
1084                 safestrncpy(config.c_nodename, my_utsname.nodename,
1085                             sizeof config.c_nodename);
1086         strtok(config.c_nodename, ".");
1087         if (IsEmptyStr(config.c_fqdn) ) {
1088                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1089                         safestrncpy(config.c_fqdn, he->h_name,
1090                                     sizeof config.c_fqdn);
1091                 else
1092                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1093                                     sizeof config.c_fqdn);
1094         }
1095         if (IsEmptyStr(config.c_humannode))
1096                 strcpy(config.c_humannode, "My System");
1097         if (IsEmptyStr(config.c_phonenum))
1098                 strcpy(config.c_phonenum, "US 800 555 1212");
1099         if (config.c_initax == 0) {
1100                 config.c_initax = 4;
1101         }
1102         if (IsEmptyStr(config.c_moreprompt))
1103                 strcpy(config.c_moreprompt, "<more>");
1104         if (IsEmptyStr(config.c_twitroom))
1105                 strcpy(config.c_twitroom, "Trashcan");
1106         if (IsEmptyStr(config.c_baseroom))
1107                 strcpy(config.c_baseroom, BASEROOM);
1108         if (IsEmptyStr(config.c_aideroom))
1109                 strcpy(config.c_aideroom, "Aide");
1110         if (config.c_port_number == 0) {
1111                 config.c_port_number = 504;
1112         }
1113         if (config.c_sleeping == 0) {
1114                 config.c_sleeping = 900;
1115         }
1116         if (config.c_ctdluid == 0) {
1117                 pw = getpwnam("citadel");
1118                 if (pw != NULL)
1119                         config.c_ctdluid = pw->pw_uid;
1120         }
1121         if (config.c_ctdluid == 0) {
1122                 pw = getpwnam("bbs");
1123                 if (pw != NULL)
1124                         config.c_ctdluid = pw->pw_uid;
1125         }
1126         if (config.c_ctdluid == 0) {
1127                 pw = getpwnam("guest");
1128                 if (pw != NULL)
1129                         config.c_ctdluid = pw->pw_uid;
1130         }
1131         if (config.c_createax == 0) {
1132                 config.c_createax = 3;
1133         }
1134         /*
1135          * Negative values for maxsessions are not allowed.
1136          */
1137         if (config.c_maxsessions < 0) {
1138                 config.c_maxsessions = 0;
1139         }
1140         /* We need a system default message expiry policy, because this is
1141          * the top level and there's no 'higher' policy to fall back on.
1142          * By default, do not expire messages at all.
1143          */
1144         if (config.c_ep.expire_mode == 0) {
1145                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1146                 config.c_ep.expire_value = 0;
1147         }
1148
1149         /*
1150          * Default port numbers for various services
1151          */
1152         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1153         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1154         if (config.c_imap_port == 0) config.c_imap_port = 143;
1155         if (config.c_msa_port == 0) config.c_msa_port = 587;
1156         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1157         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1158         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1159         if (config.c_pftcpdict_port == 0) config.c_pftcpdict_port = -1;
1160         if (config.c_managesieve_port == 0) config.c_managesieve_port = 2020;
1161
1162         /* Go through a series of dialogs prompting for config info */
1163         for (curr = 1; curr <= MAXSETUP; ++curr) {
1164                 edit_value(curr);
1165         }
1166
1167 /***** begin version update section ***** */
1168         /* take care of any updating that is necessary */
1169
1170         old_setup_level = config.c_setup_level;
1171
1172         if (old_setup_level == 0) {
1173                 goto NEW_INST;
1174         }
1175
1176         if (old_setup_level < 555) {
1177                 important_message("Citadel Setup",
1178                                   "This Citadel installation is too old "
1179                                   "to be upgraded.");
1180                 cleanup(1);
1181         }
1182         write_config_to_disk();
1183
1184         old_setup_level = config.c_setup_level;
1185
1186         /* end of version update section */
1187
1188 NEW_INST:
1189         config.c_setup_level = REV_LEVEL;
1190
1191 /******************************************/
1192
1193         write_config_to_disk();
1194
1195         mkdir(ctdl_info_dir, 0700);
1196         chmod(ctdl_info_dir, 0700);
1197         chown(ctdl_info_dir, config.c_ctdluid, -1);
1198
1199         mkdir(ctdl_bio_dir, 0700);
1200         chmod(ctdl_bio_dir, 0700);
1201         chown(ctdl_bio_dir, config.c_ctdluid, -1);
1202
1203         mkdir(ctdl_usrpic_dir, 0700);
1204         chmod(ctdl_usrpic_dir, 0700);
1205         chown(ctdl_usrpic_dir, config.c_ctdluid, -1);
1206
1207         mkdir(ctdl_message_dir, 0700);
1208         chmod(ctdl_message_dir, 0700);
1209         chown(ctdl_message_dir, config.c_ctdluid, -1);
1210
1211         mkdir(ctdl_hlp_dir, 0700);
1212         chmod(ctdl_hlp_dir, 0700);
1213         chown(ctdl_hlp_dir, config.c_ctdluid, -1);
1214
1215         mkdir(ctdl_image_dir, 0700);
1216         chmod(ctdl_image_dir, 0700);
1217         chown(ctdl_image_dir, config.c_ctdluid, -1);
1218
1219         mkdir(ctdl_bb_dir, 0700);
1220         chmod(ctdl_bb_dir, 0700);
1221         chown(ctdl_bb_dir, config.c_ctdluid, -1);
1222
1223         mkdir(ctdl_file_dir, 0700);
1224         chmod(ctdl_file_dir, 0700);
1225         chown(ctdl_file_dir, config.c_ctdluid, -1);
1226
1227         mkdir(ctdl_netcfg_dir, 0700);
1228         chmod(ctdl_netcfg_dir, 0700);
1229         chown(ctdl_netcfg_dir, config.c_ctdluid, -1);
1230
1231         /* Delete files and directories used by older Citadel versions */
1232         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1233         unlink("citadel.log");
1234         unlink("weekly");
1235
1236         check_services_entry(); /* Check /etc/services */
1237 #ifndef __CYGWIN__
1238         delete_inittab_entry(); /* Remove obsolete /etc/inittab entry */
1239         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1240
1241         /* Offer to disable other MTA's on the system. */
1242         disable_other_mta("courier-authdaemon");
1243         disable_other_mta("courier-imap");
1244         disable_other_mta("courier-imap-ssl");
1245         disable_other_mta("courier-pop");
1246         disable_other_mta("courier-pop3");
1247         disable_other_mta("courier-pop3d");
1248         disable_other_mta("cyrmaster");
1249         disable_other_mta("cyrus");
1250         disable_other_mta("dovecot");
1251         disable_other_mta("exim");
1252         disable_other_mta("exim4");
1253         disable_other_mta("imapd");
1254         disable_other_mta("mta");
1255         disable_other_mta("pop3d");
1256         disable_other_mta("popd");
1257         disable_other_mta("postfix");
1258         disable_other_mta("qmail");
1259         disable_other_mta("saslauthd");
1260         disable_other_mta("sendmail");
1261         disable_other_mta("vmailmgrd");
1262 #endif
1263
1264         /* Check for the 'db' nss and offer to disable it */
1265         fixnss();
1266
1267         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1268                 gid = getgid();
1269         else
1270                 gid = pw->pw_gid;
1271
1272         progress("Setting file permissions", 0, 3);
1273         chown(ctdl_run_dir, config.c_ctdluid, gid);
1274         progress("Setting file permissions", 1, 3);
1275         chown(file_citadel_config, config.c_ctdluid, gid);
1276         progress("Setting file permissions", 2, 3);
1277         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1278         progress("Setting file permissions", 3, 3);
1279
1280         /* 
1281          * If we're running on SysV, install init scripts.
1282          */
1283         if (!access("/var/run", W_OK)) {
1284
1285                 if (getenv("NO_INIT_SCRIPTS") == NULL) {
1286                         install_init_scripts();
1287                 }
1288
1289                 if (!access("/etc/init.d/citadel", X_OK)) {
1290                         system("/etc/init.d/citadel start");
1291                         sleep(3);
1292                 }
1293
1294                 if (test_server() == 0) {
1295                         important_message("Setup finished",
1296                                 "Setup of the Citadel server is complete.\n"
1297                                 "If you will be using WebCit, please run its\n"
1298                                 "setup program now; otherwise, run './citadel'\n"
1299                                 "to log in.\n");
1300                 }
1301                 else {
1302                         important_message("Setup failed",
1303                                 "Setup is finished, but the Citadel server failed to start.\n"
1304                                 "Go back and check your configuration.\n"
1305                         );
1306                 }
1307
1308         }
1309
1310         else {
1311                 important_message("Setup finished",
1312                         "Setup is finished.  You may now start the server.");
1313         }
1314
1315         cleanup(0);
1316         return 0;
1317 }
1318
1319