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