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