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