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