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