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