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