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