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