* add ssl xmpp port; this should work with pidgin as samjam documented.
[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                 chmod(NSSCONF, 0644);
981         }
982         unlink(new_filename);
983 }
984
985
986
987
988
989
990
991
992 int main(int argc, char *argv[])
993 {
994         int a;
995         int curr; 
996         char aaa[128];
997         FILE *fp;
998         int old_setup_level = 0;
999         int info_only = 0;
1000         struct utsname my_utsname;
1001         struct passwd *pw;
1002         struct hostent *he;
1003         gid_t gid;
1004         int relh=0;
1005         int home=0;
1006         char relhome[PATH_MAX]="";
1007         char ctdldir[PATH_MAX]=CTDLDIR;
1008         
1009         /* set an invalid setup type */
1010         setup_type = (-1);
1011
1012         /* Check to see if we're running the web installer */
1013         if (getenv("CITADEL_INSTALLER") != NULL) {
1014                 using_web_installer = 1;
1015         }
1016
1017         /* parse command line args */
1018         for (a = 0; a < argc; ++a) {
1019                 if (!strncmp(argv[a], "-u", 2)) {
1020                         strcpy(aaa, argv[a]);
1021                         strcpy(aaa, &aaa[2]);
1022                         setup_type = atoi(aaa);
1023                 }
1024                 else if (!strcmp(argv[a], "-i")) {
1025                         info_only = 1;
1026                 }
1027                 else if (!strcmp(argv[a], "-q")) {
1028                         setup_type = UI_SILENT;
1029                 }
1030                 else if (!strncmp(argv[a], "-h", 2)) {
1031                         relh=argv[a][2]!='/';
1032                         if (!relh) safestrncpy(ctdl_home_directory, &argv[a][2],
1033                                                                    sizeof ctdl_home_directory);
1034                         else
1035                                 safestrncpy(relhome, &argv[a][2],
1036                                                         sizeof relhome);
1037                         home = 1;
1038                 }
1039
1040         }
1041
1042         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1043
1044         /* If a setup type was not specified, try to determine automatically
1045          * the best one to use out of all available types.
1046          */
1047         if (setup_type < 0) {
1048                 setup_type = discover_ui();
1049         }
1050         if (info_only == 1) {
1051                 important_message("Citadel Setup", CITADEL);
1052                 cleanup(0);
1053         }
1054
1055         /* Get started in a valid setup directory. */
1056         strcpy(setup_directory, ctdl_run_dir);
1057         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
1058                 strcpy(setup_directory, getenv("CITADEL"));
1059         }
1060         else {
1061                 set_str_val(0, setup_directory);
1062         }
1063
1064         enable_home = ( relh | home );
1065
1066         if (chdir(setup_directory) != 0) {
1067                 char errmsg[SIZ];
1068                 sprintf(errmsg, "The directory you specified does not exist: [%s]\n", setup_directory);
1069                 
1070                 important_message("Citadel Setup", errmsg);
1071                 cleanup(errno);
1072         }
1073
1074         /* Determine our host name, in case we need to use it as a default */
1075         uname(&my_utsname);
1076
1077         /* Try to stop Citadel if we can */
1078         if (!access("/etc/init.d/citadel", X_OK)) {
1079                 system("/etc/init.d/citadel stop");
1080         }
1081
1082         /* Make sure Citadel is not running. */
1083         if (test_server(setup_directory, relhome, enable_home) == 0) {
1084                 important_message("Citadel Setup",
1085                         "The Citadel service is still running.\n"
1086                         "Please stop the service manually and run "
1087                         "setup again.");
1088                 cleanup(1);
1089         }
1090
1091         /* Now begin. */
1092         switch (setup_type) {
1093
1094         case UI_TEXT:
1095                 printf("\n\n\n"
1096                         "              *** Citadel setup program ***\n\n");
1097                 break;
1098
1099         }
1100
1101         /*
1102          * What we're going to try to do here is append a whole bunch of
1103          * nulls to the citadel.config file, so we can keep the old config
1104          * values if they exist, but if the file is missing or from an
1105          * earlier version with a shorter config structure, when setup tries
1106          * to read the old config parameters, they'll all come up zero.
1107          * The length of the config file will be set to what it's supposed
1108          * to be when we rewrite it, because we replace the old file with a
1109          * completely new copy.
1110          */
1111         if ((a = open(file_citadel_config, O_WRONLY | O_CREAT | O_APPEND,
1112                       S_IRUSR | S_IWUSR)) == -1) {
1113                 display_error("setup: cannot append citadel.config");
1114                 cleanup(errno);
1115         }
1116         fp = fdopen(a, "ab");
1117         if (fp == NULL) {
1118                 display_error("setup: cannot append citadel.config");
1119                 cleanup(errno);
1120         }
1121         for (a = 0; a < sizeof(struct config); ++a)
1122                 putc(0, fp);
1123         fclose(fp);
1124
1125         /* now we re-open it, and read the old or blank configuration */
1126         fp = fopen(file_citadel_config, "rb");
1127         if (fp == NULL) {
1128                 display_error("setup: cannot open citadel.config");
1129                 cleanup(errno);
1130         }
1131         fread((char *) &config, sizeof(struct config), 1, fp);
1132         fclose(fp);
1133
1134         /* set some sample/default values in place of blanks... */
1135         if (IsEmptyStr(config.c_nodename))
1136                 safestrncpy(config.c_nodename, my_utsname.nodename,
1137                             sizeof config.c_nodename);
1138         strtok(config.c_nodename, ".");
1139         if (IsEmptyStr(config.c_fqdn) ) {
1140                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1141                         safestrncpy(config.c_fqdn, he->h_name,
1142                                     sizeof config.c_fqdn);
1143                 else
1144                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1145                                     sizeof config.c_fqdn);
1146         }
1147         if (IsEmptyStr(config.c_humannode))
1148                 strcpy(config.c_humannode, "My System");
1149         if (IsEmptyStr(config.c_phonenum))
1150                 strcpy(config.c_phonenum, "US 800 555 1212");
1151         if (config.c_initax == 0) {
1152                 config.c_initax = 4;
1153         }
1154         if (IsEmptyStr(config.c_moreprompt))
1155                 strcpy(config.c_moreprompt, "<more>");
1156         if (IsEmptyStr(config.c_twitroom))
1157                 strcpy(config.c_twitroom, "Trashcan");
1158         if (IsEmptyStr(config.c_baseroom))
1159                 strcpy(config.c_baseroom, BASEROOM);
1160         if (IsEmptyStr(config.c_aideroom))
1161                 strcpy(config.c_aideroom, "Aide");
1162         if (config.c_port_number == 0) {
1163                 config.c_port_number = 504;
1164         }
1165         if (config.c_sleeping == 0) {
1166                 config.c_sleeping = 900;
1167         }
1168         if (config.c_ctdluid == 0) {
1169                 pw = getpwnam("citadel");
1170                 if (pw != NULL)
1171                         config.c_ctdluid = pw->pw_uid;
1172         }
1173         if (config.c_ctdluid == 0) {
1174                 pw = getpwnam("bbs");
1175                 if (pw != NULL)
1176                         config.c_ctdluid = pw->pw_uid;
1177         }
1178         if (config.c_ctdluid == 0) {
1179                 pw = getpwnam("guest");
1180                 if (pw != NULL)
1181                         config.c_ctdluid = pw->pw_uid;
1182         }
1183         if (config.c_createax == 0) {
1184                 config.c_createax = 3;
1185         }
1186         /*
1187          * Negative values for maxsessions are not allowed.
1188          */
1189         if (config.c_maxsessions < 0) {
1190                 config.c_maxsessions = 0;
1191         }
1192         /* We need a system default message expiry policy, because this is
1193          * the top level and there's no 'higher' policy to fall back on.
1194          * By default, do not expire messages at all.
1195          */
1196         if (config.c_ep.expire_mode == 0) {
1197                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1198                 config.c_ep.expire_value = 0;
1199         }
1200
1201         /*
1202          * Default port numbers for various services
1203          */
1204         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1205         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1206         if (config.c_imap_port == 0) config.c_imap_port = 143;
1207         if (config.c_msa_port == 0) config.c_msa_port = 587;
1208         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1209         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1210         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1211         if (config.c_pftcpdict_port == 0) config.c_pftcpdict_port = -1;
1212         if (config.c_managesieve_port == 0) config.c_managesieve_port = 2020;
1213         if (config.c_xmpp_c2s_port == 0) config.c_xmpp_c2s_port = 5222;
1214         if (config.c_xmpp_s2s_port == 0) config.c_xmpp_s2s_port = 5269;
1215         if (config.c_xmpps_c2s_port == 0) config.c_xmpps_c2s_port = 5223;
1216         /* Go through a series of dialogs prompting for config info */
1217         for (curr = 1; curr <= MAXSETUP; ++curr) {
1218                 edit_value(curr);
1219         }
1220
1221 /***** begin version update section ***** */
1222         /* take care of any updating that is necessary */
1223
1224         old_setup_level = config.c_setup_level;
1225
1226         if (old_setup_level == 0) {
1227                 goto NEW_INST;
1228         }
1229
1230         if (old_setup_level < 555) {
1231                 important_message("Citadel Setup",
1232                                   "This Citadel installation is too old "
1233                                   "to be upgraded.");
1234                 cleanup(1);
1235         }
1236         write_config_to_disk();
1237
1238         old_setup_level = config.c_setup_level;
1239
1240         /* end of version update section */
1241
1242 NEW_INST:
1243         config.c_setup_level = REV_LEVEL;
1244
1245 /******************************************/
1246
1247         write_config_to_disk();
1248
1249         mkdir(ctdl_info_dir, 0700);
1250         chmod(ctdl_info_dir, 0700);
1251         chown(ctdl_info_dir, config.c_ctdluid, -1);
1252
1253         mkdir(ctdl_bio_dir, 0700);
1254         chmod(ctdl_bio_dir, 0700);
1255         chown(ctdl_bio_dir, config.c_ctdluid, -1);
1256
1257         mkdir(ctdl_usrpic_dir, 0700);
1258         chmod(ctdl_usrpic_dir, 0700);
1259         chown(ctdl_usrpic_dir, config.c_ctdluid, -1);
1260
1261         mkdir(ctdl_message_dir, 0700);
1262         chmod(ctdl_message_dir, 0700);
1263         chown(ctdl_message_dir, config.c_ctdluid, -1);
1264
1265         mkdir(ctdl_hlp_dir, 0700);
1266         chmod(ctdl_hlp_dir, 0700);
1267         chown(ctdl_hlp_dir, config.c_ctdluid, -1);
1268
1269         mkdir(ctdl_image_dir, 0700);
1270         chmod(ctdl_image_dir, 0700);
1271         chown(ctdl_image_dir, config.c_ctdluid, -1);
1272
1273         mkdir(ctdl_bb_dir, 0700);
1274         chmod(ctdl_bb_dir, 0700);
1275         chown(ctdl_bb_dir, config.c_ctdluid, -1);
1276
1277         mkdir(ctdl_file_dir, 0700);
1278         chmod(ctdl_file_dir, 0700);
1279         chown(ctdl_file_dir, config.c_ctdluid, -1);
1280
1281         mkdir(ctdl_netcfg_dir, 0700);
1282         chmod(ctdl_netcfg_dir, 0700);
1283         chown(ctdl_netcfg_dir, config.c_ctdluid, -1);
1284
1285         /* Delete files and directories used by older Citadel versions */
1286         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1287         unlink("citadel.log");
1288         unlink("weekly");
1289
1290         if (((setup_type == UI_SILENT) && (getenv("ALTER_ETC_SERVICES")!=NULL)) || 
1291             (setup_type != UI_SILENT))
1292                 check_services_entry(); /* Check /etc/services */
1293 #ifndef __CYGWIN__
1294         delete_inittab_entry(); /* Remove obsolete /etc/inittab entry */
1295         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1296
1297         if ((getenv("ACT_AS_MTA") == NULL) || 
1298             (getenv("ACT_AS_MTA") &&
1299              strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
1300                 /* Offer to disable other MTA's on the system. */
1301                 disable_other_mta("courier-authdaemon");
1302                 disable_other_mta("courier-imap");
1303                 disable_other_mta("courier-imap-ssl");
1304                 disable_other_mta("courier-pop");
1305                 disable_other_mta("courier-pop3");
1306                 disable_other_mta("courier-pop3d");
1307                 disable_other_mta("cyrmaster");
1308                 disable_other_mta("cyrus");
1309                 disable_other_mta("dovecot");
1310                 disable_other_mta("exim");
1311                 disable_other_mta("exim4");
1312                 disable_other_mta("imapd");
1313                 disable_other_mta("mta");
1314                 disable_other_mta("pop3d");
1315                 disable_other_mta("popd");
1316                 disable_other_mta("postfix");
1317                 disable_other_mta("qmail");
1318                 disable_other_mta("saslauthd");
1319                 disable_other_mta("sendmail");
1320                 disable_other_mta("vmailmgrd");
1321         }
1322 #endif
1323
1324         /* Check for the 'db' nss and offer to disable it */
1325         fixnss();
1326
1327         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1328                 gid = getgid();
1329         else
1330                 gid = pw->pw_gid;
1331
1332         progress("Setting file permissions", 0, 3);
1333         chown(ctdl_run_dir, config.c_ctdluid, gid);
1334         progress("Setting file permissions", 1, 3);
1335         chown(file_citadel_config, config.c_ctdluid, gid);
1336         progress("Setting file permissions", 2, 3);
1337         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1338         progress("Setting file permissions", 3, 3);
1339
1340         /* 
1341          * If we're running on SysV, install init scripts.
1342          */
1343         if (!access("/var/run", W_OK)) {
1344
1345                 if (getenv("NO_INIT_SCRIPTS") == NULL) {
1346                         install_init_scripts();
1347                 }
1348
1349                 if (!access("/etc/init.d/citadel", X_OK)) {
1350                         system("/etc/init.d/citadel start");
1351                         sleep(3);
1352                 }
1353
1354                 if (test_server(setup_directory, relhome, enable_home) == 0) {
1355                         char buf[SIZ];
1356                         int found_it = 0;
1357
1358                         snprintf (admin_cmd, sizeof(admin_cmd), "%s/sendcommand \"CREU %s|%s\" 2>&1", 
1359                                   ctdl_sbin_dir, config.c_sysadm, admin_pass);
1360                         fp = popen(admin_cmd, "r");
1361                         if (fp != NULL) {
1362                                 while (fgets(buf, sizeof buf, fp) != NULL) 
1363                                 {
1364                                         if ((atol(buf) == 574) || (atol(buf) == 200))
1365                                                 ++found_it;
1366                                 }
1367                                 pclose(fp);
1368                         }
1369                 
1370                         if (found_it == 0)
1371                                 important_message("Error","Setup failed to create your admin user");
1372
1373                         if (setup_type != UI_SILENT)
1374                                 important_message("Setup finished",
1375                                                   "Setup of the Citadel server is complete.\n"
1376                                                   "If you will be using WebCit, please run its\n"
1377                                                   "setup program now; otherwise, run './citadel'\n"
1378                                                   "to log in.\n");
1379                 }
1380                 else {
1381                         important_message("Setup failed",
1382                                 "Setup is finished, but the Citadel server failed to start.\n"
1383                                 "Go back and check your configuration.\n"
1384                         );
1385                 }
1386
1387         }
1388
1389         else {
1390                 important_message("Setup finished",
1391                         "Setup is finished.  You may now start the server.");
1392         }
1393
1394         cleanup(0);
1395         return 0;
1396 }
1397
1398