If /etc/inittab is not found, return quietly instead of
[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
408                 /* If /etc/inittab does not exist, return quietly.
409                  * Not all host platforms have it.
410                  */
411                 if (errno == ENOENT) {
412                         return;
413                 }
414
415                 /* Other errors might mean something really did go wrong.
416                  */
417                 sprintf(buf, "Cannot open /etc/inittab: %s", strerror(errno));
418                 display_error(buf);
419                 return;
420         }
421
422         strcpy(outfilename, "/tmp/ctdlsetup.XXXXXX");
423         outfp = fdopen(mkstemp(outfilename), "w+");
424         if (outfp == NULL) {
425                 sprintf(buf, "Cannot open %s: %s", outfilename, strerror(errno));
426                 display_error(buf);
427                 fclose(infp);
428                 return;
429         }
430
431         while (fgets(buf, sizeof buf, infp) != NULL) {
432                 if (strstr(buf, looking_for) != NULL) {
433                         fwrite("#", 1, 1, outfp);
434                         ++changes_made;
435                 }
436                 fwrite(buf, strlen(buf), 1, outfp);
437         }
438
439         fclose(infp);
440         fclose(outfp);
441
442         if (changes_made) {
443                 sprintf(buf, "/bin/mv -f %s /etc/inittab 2>/dev/null", outfilename);
444                 system(buf);
445                 system("/sbin/init q 2>/dev/null");
446         }
447         else {
448                 unlink(outfilename);
449         }
450 }
451
452
453 /*
454  * install_init_scripts()  -- Try to configure to start Citadel at boot
455  *
456  */
457 void install_init_scripts(void)
458 {
459         FILE *fp;
460
461         if (yesno("Would you like to automatically start Citadel at boot?\n") == 0) {
462                 return;
463         }
464
465         fp = fopen("/etc/init.d/citadel", "w");
466         if (fp == NULL) {
467                 display_error("Cannot create /etc/init.d/citadel");
468                 return;
469         }
470
471         fprintf(fp,     "#!/bin/sh\n"
472                         "\n"
473                         "CITADEL_DIR=%s\n", setup_directory);
474         fprintf(fp,     "\n"
475                         "test -x $CITADEL_DIR/ctdlsvc || exit 0\n"
476                         "test -d /var/run || exit 0\n"
477                         "\n"
478                         "case \"$1\" in\n"
479                         "\n"
480                         "start)         echo -n \"Starting Citadel... \"\n"
481                         "               if $CITADEL_DIR/ctdlsvc /var/run/citadel.pid "
482                                                         "$CITADEL_DIR/citserver "
483                                                         "-t/dev/null\n"
484                         "               then\n"
485                         "                       echo \"ok\"\n"
486                         "               else\n"
487                         "                       echo \"failed\"\n"
488                         "               fi\n");
489         fprintf(fp,     "               ;;\n"
490                         "stop)          echo -n \"Stopping Citadel... \"\n"
491                         "               if $CITADEL_DIR/sendcommand DOWN >/dev/null 2>&1 ; then\n"
492                         "                       echo \"ok\"\n"
493                         "               else\n"
494                         "                       echo \"failed\"\n"
495                         "               fi\n"
496                         "               rm -f /var/run/citadel.pid 2>/dev/null\n");
497         fprintf(fp,     "               ;;\n"
498                         "restart)       $0 stop\n"
499                         "               $0 start\n"
500                         "               ;;\n"
501                         "*)             echo \"Usage: $0 {start|stop|restart}\"\n"
502                         "               exit 1\n"
503                         "               ;;\n"
504                         "esac\n"
505         );
506
507         fclose(fp);
508         chmod("/etc/init.d/citadel", 0755);
509
510         /* Set up the run levels. */
511         system("/bin/rm -f /etc/rc?.d/[SK]??citadel 2>/dev/null");
512         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");
513         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");
514
515 }
516
517
518
519
520
521
522 /*
523  * On systems which use xinetd, see if we can offer to install Citadel as
524  * the default telnet target.
525  */
526 void check_xinetd_entry(void) {
527         char *filename = "/etc/xinetd.d/telnet";
528         FILE *fp;
529         char buf[SIZ];
530         int already_citadel = 0;
531
532         fp = fopen(filename, "r+");
533         if (fp == NULL) return;         /* Not there.  Oh well... */
534
535         while (fgets(buf, sizeof buf, fp) != NULL) {
536                 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
537         }
538         fclose(fp);
539         if (already_citadel) return;    /* Already set up this way. */
540
541         /* Otherwise, prompt the user to create an entry. */
542         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
543                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
544                         return;
545                 }
546         }
547         else {
548                 snprintf(buf, sizeof buf,
549                         "Setup can configure the \"xinetd\" service to automatically\n"
550                         "connect incoming telnet sessions to Citadel, bypassing the\n"
551                         "host system login: prompt.  Would you like to do this?\n"
552                 );
553                 if (yesno(buf) == 0) {
554                         return;
555                 }
556         }
557
558         fp = fopen(filename, "w");
559         fprintf(fp,
560                 "# description: telnet service for Citadel users\n"
561                 "service telnet\n"
562                 "{\n"
563                 "       disable = no\n"
564                 "       flags           = REUSE\n"
565                 "       socket_type     = stream\n"
566                 "       wait            = no\n"
567                 "       user            = root\n"
568                 "       server          = /usr/sbin/in.telnetd\n"
569                 "       server_args     = -h -L %s/citadel\n"
570                 "       log_on_failure  += USERID\n"
571                 "}\n",
572 #ifndef HAVE_RUN_DIR
573                         setup_directory
574 #else
575                         RUN_DIR
576 #endif
577                         );
578         fclose(fp);
579
580         /* Now try to restart the service */
581         system("/etc/init.d/xinetd restart >/dev/null 2>&1");
582 }
583
584
585
586 /*
587  * Offer to disable other MTA's
588  */
589 void disable_other_mta(char *mta) {
590         char buf[SIZ];
591         FILE *fp;
592         int lines = 0;
593
594         sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
595                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
596                 mta, mta);
597         fp = popen(buf, "r");
598         if (fp == NULL) return;
599
600         while (fgets(buf, sizeof buf, fp) != NULL) {
601                 ++lines;
602         }
603         fclose(fp);
604         if (lines == 0) return;         /* Nothing to do. */
605
606
607         /* Offer to replace other MTA with the vastly superior Citadel :)  */
608
609         if (getenv("ACT_AS_MTA")) {
610                 if (strcasecmp(getenv("ACT_AS_MTA"), "yes")) {
611                         return;
612                 }
613         }
614         else {
615                 snprintf(buf, sizeof buf,
616                         "You appear to have the \"%s\" email program\n"
617                         "running on your system.  If you want Citadel mail\n"
618                         "connected with %s, you will have to manually integrate\n"
619                         "them.  It is preferable to disable %s, and use Citadel's\n"
620                         "SMTP, POP3, and IMAP services.\n\n"
621                         "May we disable %s so that Citadel has access to ports\n"
622                         "25, 110, and 143?\n",
623                         mta, mta, mta, mta
624                 );
625                 if (yesno(buf) == 0) {
626                         return;
627                 }
628         }
629
630         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);
631         system(buf);
632         sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
633         system(buf);
634 }
635
636
637
638
639 /* 
640  * Check to see if our server really works.  Returns 0 on success.
641  */
642 int test_server(void) {
643         char cmd[256];
644         char cookie[256];
645         FILE *fp;
646         char buf[4096];
647         int found_it = 0;
648
649         /* Generate a silly little cookie.  We're going to write it out
650          * to the server and try to get it back.  The cookie does not
651          * have to be secret ... just unique.
652          */
653         sprintf(cookie, "--test--%d--", getpid());
654
655         sprintf(cmd, "%s/sendcommand %s%s ECHO %s 2>&1",
656 #ifndef HAVE_RUN_DIR
657                         setup_directory,
658 #else
659                         CTDLDIR,
660 #endif
661                         (enable_home)?"-h":"", 
662                         (enable_home)?setup_directory:"",
663                         cookie);
664
665         fp = popen(cmd, "r");
666         if (fp == NULL) return(errno);
667
668         while (fgets(buf, sizeof buf, fp) != NULL) {
669                 if ( (buf[0]=='2')
670                    && (strstr(buf, cookie) != NULL) ) {
671                         ++found_it;
672                 }
673         }
674         pclose(fp);
675
676         if (found_it) {
677                 return(0);
678         }
679         return(-1);
680 }
681
682 void strprompt(char *prompt_title, char *prompt_text, char *str)
683 {
684 #ifdef HAVE_NEWT
685         newtComponent form;
686         char *result;
687         int i;
688         int prompt_window_height = 0;
689 #endif
690         char buf[SIZ];
691         char setupmsg[SIZ];
692         char dialog_result[PATH_MAX];
693         FILE *fp = NULL;
694
695         strcpy(setupmsg, "");
696
697         switch (setup_type) {
698         case UI_TEXT:
699                 title(prompt_title);
700                 printf("\n%s\n", prompt_text);
701                 printf("This is currently set to:\n%s\n", str);
702                 printf("Enter new value or press return to leave unchanged:\n");
703                 fgets(buf, sizeof buf, stdin);
704                 buf[strlen(buf) - 1] = 0;
705                 if (strlen(buf) != 0)
706                         strcpy(str, buf);
707                 break;
708
709         case UI_DIALOG:
710                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
711                 sprintf(buf, "exec %s --inputbox '%s' 19 72 '%s' 2>%s",
712                         getenv("CTDL_DIALOG"),
713                         prompt_text,
714                         str,
715                         dialog_result);
716                 system(buf);
717                 fp = fopen(dialog_result, "r");
718                 if (fp != NULL) {
719                         fgets(str, sizeof buf, fp);
720                         if (str[strlen(str)-1] == 10) {
721                                 str[strlen(str)-1] = 0;
722                         }
723                         fclose(fp);
724                         unlink(dialog_result);
725                 }
726                 break;
727
728 #ifdef HAVE_NEWT
729         case UI_NEWT:
730
731                 prompt_window_height = num_tokens(prompt_text, '\n') + 5 ;
732                 newtCenteredWindow(76,
733                                 prompt_window_height,
734                                 prompt_title);
735                 form = newtForm(NULL, NULL, 0);
736                 for (i=0; i<num_tokens(prompt_text, '\n'); ++i) {
737                         extract_token(buf, prompt_text, i, '\n', sizeof buf);
738                         newtFormAddComponent(form, newtLabel(1, 1+i, buf));
739                 }
740                 newtFormAddComponent(form,
741                         newtEntry(1,
742                                 (prompt_window_height - 2),
743                                 str,
744                                 74,
745                                 (const char **) &result,
746                                 NEWT_FLAG_RETURNEXIT)
747                 );
748                 newtRunForm(form);
749                 strcpy(str, result);
750
751                 newtPopWindow();
752                 newtFormDestroy(form);  
753
754 #endif
755         }
756 }
757
758 void set_str_val(int msgpos, char *str) {
759         strprompt(setup_titles[msgpos], setup_text[msgpos], str);
760 }
761
762
763
764 void set_int_val(int msgpos, int *ip)
765 {
766         char buf[16];
767         snprintf(buf, sizeof buf, "%d", (int) *ip);
768         set_str_val(msgpos, buf);
769         *ip = atoi(buf);
770 }
771
772
773 void set_char_val(int msgpos, char *ip)
774 {
775         char buf[16];
776         snprintf(buf, sizeof buf, "%d", (int) *ip);
777         set_str_val(msgpos, buf);
778         *ip = (char) atoi(buf);
779 }
780
781
782 void set_long_val(int msgpos, long int *ip)
783 {
784         char buf[16];
785         snprintf(buf, sizeof buf, "%ld", *ip);
786         set_str_val(msgpos, buf);
787         *ip = atol(buf);
788 }
789
790
791 void edit_value(int curr)
792 {
793         int i;
794         struct passwd *pw;
795         char ctdluidname[SIZ];
796
797         switch (curr) {
798
799         case 1:
800                 if (getenv("SYSADMIN_NAME")) {
801                         strcpy(config.c_sysadm, getenv("SYSADMIN_NAME"));
802                 }
803                 else {
804                         set_str_val(curr, config.c_sysadm);
805                 }
806                 break;
807
808         case 2:
809 #ifdef __CYGWIN__
810                 config.c_ctdluid = 0;   /* XXX Windows hack, prob. insecure */
811 #else
812                 i = config.c_ctdluid;
813                 pw = getpwuid(i);
814                 if (pw == NULL) {
815                         set_int_val(curr, &i);
816                         config.c_ctdluid = i;
817                 }
818                 else {
819                         strcpy(ctdluidname, pw->pw_name);
820                         set_str_val(curr, ctdluidname);
821                         pw = getpwnam(ctdluidname);
822                         if (pw != NULL) {
823                                 config.c_ctdluid = pw->pw_uid;
824                         }
825                         else if (atoi(ctdluidname) > 0) {
826                                 config.c_ctdluid = atoi(ctdluidname);
827                         }
828                 }
829 #endif
830                 break;
831
832         case 3:
833                 set_str_val(curr, config.c_ip_addr);
834                 break;
835
836         case 4:
837                 set_int_val(curr, &config.c_port_number);
838                 break;
839
840
841         }
842 }
843
844 /*
845  * (re-)write the config data to disk
846  */
847 void write_config_to_disk(void)
848 {
849         FILE *fp;
850         int fd;
851
852         if ((fd = creat(file_citadel_config, S_IRUSR | S_IWUSR)) == -1) {
853                 display_error("setup: cannot open citadel.config");
854                 cleanup(1);
855         }
856         fp = fdopen(fd, "wb");
857         if (fp == NULL) {
858                 display_error("setup: cannot open citadel.config");
859                 cleanup(1);
860         }
861         fwrite((char *) &config, sizeof(struct config), 1, fp);
862         fclose(fp);
863 }
864
865
866
867
868 /*
869  * Figure out what type of user interface we're going to use
870  */
871 int discover_ui(void)
872 {
873
874         /* Use "dialog" if we have it */
875         if (getenv("CTDL_DIALOG") != NULL) {
876                 return UI_DIALOG;
877         }
878                 
879
880 #ifdef HAVE_NEWT
881         newtInit();
882         newtCls();
883         newtDrawRootText(0, 0, "Citadel Setup");
884         return UI_NEWT;
885 #endif
886         return UI_TEXT;
887 }
888
889
890
891
892
893 /*
894  * Strip "db" entries out of /etc/nsswitch.conf
895  */
896 void fixnss(void) {
897         FILE *fp_read;
898         int fd_write;
899         char buf[256];
900         char buf_nc[256];
901         char question[512];
902         int i;
903         int changed = 0;
904         int file_changed = 0;
905         char new_filename[64];
906
907         fp_read = fopen(NSSCONF, "r");
908         if (fp_read == NULL) {
909                 return;
910         }
911
912         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
913         fd_write = mkstemp(new_filename);
914         if (fd_write < 0) {
915                 fclose(fp_read);
916                 return;
917         }
918
919         while (fgets(buf, sizeof buf, fp_read) != NULL) {
920                 changed = 0;
921                 strcpy(buf_nc, buf);
922                 for (i=0; i<strlen(buf_nc); ++i) {
923                         if (buf_nc[i] == '#') {
924                                 buf_nc[i] = 0;
925                         }
926                 }
927                 for (i=0; i<strlen(buf_nc); ++i) {
928                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
929                                 if (i > 0) {
930                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
931                                                 changed = 1;
932                                                 file_changed = 1;
933                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
934                                                 strcpy(&buf[i], &buf[i+2]);
935                                                 if (buf[i]==32) {
936                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
937                                                         strcpy(&buf[i], &buf[i+1]);
938                                                 }
939                                         }
940                                 }
941                         }
942                 }
943                 if (write(fd_write, buf, strlen(buf)) != strlen(buf)) {
944                         fclose(fp_read);
945                         close(fd_write);
946                         unlink(new_filename);
947                         return;
948                 }
949         }
950
951         fclose(fp_read);
952         
953         if (!file_changed) {
954                 unlink(new_filename);
955                 return;
956         }
957
958         snprintf(question, sizeof question,
959                 "\n"
960                 "/etc/nsswitch.conf is configured to use the 'db' module for\n"
961                 "one or more services.  This is not necessary on most systems,\n"
962                 "and it is known to crash the Citadel server when delivering\n"
963                 "mail to the Internet.\n"
964                 "\n"
965                 "Do you want this module to be automatically disabled?\n"
966                 "\n"
967         );
968
969         if (yesno(question)) {
970                 sprintf(buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
971                 system(buf);
972         }
973         unlink(new_filename);
974 }
975
976
977
978
979
980
981
982
983 int main(int argc, char *argv[])
984 {
985         int a;
986         int curr; 
987         char aaa[128];
988         FILE *fp;
989         int old_setup_level = 0;
990         int info_only = 0;
991         struct utsname my_utsname;
992         struct passwd *pw;
993         struct hostent *he;
994         gid_t gid;
995         int relh=0;
996         int home=0;
997         char relhome[PATH_MAX]="";
998         char ctdldir[PATH_MAX]=CTDLDIR;
999         
1000         /* set an invalid setup type */
1001         setup_type = (-1);
1002
1003         /* Check to see if we're running the web installer */
1004         if (getenv("CITADEL_INSTALLER") != NULL) {
1005                 using_web_installer = 1;
1006         }
1007
1008         /* parse command line args */
1009         for (a = 0; a < argc; ++a) {
1010                 if (!strncmp(argv[a], "-u", 2)) {
1011                         strcpy(aaa, argv[a]);
1012                         strcpy(aaa, &aaa[2]);
1013                         setup_type = atoi(aaa);
1014                 }
1015                 if (!strcmp(argv[a], "-i")) {
1016                         info_only = 1;
1017                 }
1018                 if (!strcmp(argv[a], "-q")) {
1019                         setup_type = UI_SILENT;
1020                 }
1021         }
1022
1023
1024         /* If a setup type was not specified, try to determine automatically
1025          * the best one to use out of all available types.
1026          */
1027         if (setup_type < 0) {
1028                 setup_type = discover_ui();
1029         }
1030         if (info_only == 1) {
1031                 important_message("Citadel Setup", CITADEL);
1032                 cleanup(0);
1033         }
1034
1035         /* Get started in a valid setup directory. */
1036         strcpy(setup_directory, 
1037 #ifdef HAVE_RUN_DIR
1038                    ""
1039 #else
1040                    CTDLDIR
1041 #endif
1042                    );
1043         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
1044                 strcpy(setup_directory, getenv("CITADEL"));
1045         }
1046         else {
1047                 set_str_val(0, setup_directory);
1048         }
1049
1050         home=(setup_directory[1]!='\0');
1051         relh=home&(setup_directory[1]!='/');
1052         if (!relh) {
1053                 safestrncpy(ctdl_home_directory, setup_directory, sizeof ctdl_home_directory);
1054         }
1055         else {
1056                 safestrncpy(relhome, ctdl_home_directory, sizeof relhome);
1057         }
1058
1059         calc_dirs_n_files(relh, home, relhome, ctdldir);
1060         
1061         enable_home=(relh|home);
1062
1063         if (home) {
1064                 if (chdir(setup_directory) == 0) {
1065                         strcpy(file_citadel_config, "./citadel.config");
1066                 }
1067                 else {
1068                         important_message("Citadel Setup",
1069                                 "The directory you specified does not exist.");
1070                         cleanup(errno);
1071                 }
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() == 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 (strlen(config.c_nodename) == 0)
1136                 safestrncpy(config.c_nodename, my_utsname.nodename,
1137                             sizeof config.c_nodename);
1138         strtok(config.c_nodename, ".");
1139         if (strlen(config.c_fqdn) == 0) {
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 (strlen(config.c_humannode) == 0)
1148                 strcpy(config.c_humannode, "My System");
1149         if (strlen(config.c_phonenum) == 0)
1150                 strcpy(config.c_phonenum, "US 800 555 1212");
1151         if (config.c_initax == 0) {
1152                 config.c_initax = 4;
1153         }
1154         if (strlen(config.c_moreprompt) == 0)
1155                 strcpy(config.c_moreprompt, "<more>");
1156         if (strlen(config.c_twitroom) == 0)
1157                 strcpy(config.c_twitroom, "Trashcan");
1158         if (strlen(config.c_baseroom) == 0)
1159                 strcpy(config.c_baseroom, BASEROOM);
1160         if (strlen(config.c_aideroom) == 0)
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
1214         /* Go through a series of dialogs prompting for config info */
1215         if (setup_type != UI_SILENT) {
1216                 for (curr = 1; curr <= MAXSETUP; ++curr) {
1217                         edit_value(curr);
1218                 }
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         /* TODO: where to put this? */
1286         mkdir("netconfigs", 0700);
1287         chmod("netconfigs", 0700);
1288         chown("netconfigs", config.c_ctdluid, -1);
1289
1290         /* Delete files and directories used by older Citadel versions */
1291         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1292         unlink("citadel.log");
1293         unlink("weekly");
1294
1295         check_services_entry(); /* Check /etc/services */
1296 #ifndef __CYGWIN__
1297         delete_inittab_entry(); /* Remove obsolete /etc/inittab entry */
1298         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1299
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("hula");
1313         disable_other_mta("imapd");
1314         disable_other_mta("mta");
1315         disable_other_mta("pop3d");
1316         disable_other_mta("popd");
1317         disable_other_mta("postfix");
1318         disable_other_mta("qmail");
1319         disable_other_mta("saslauthd");
1320         disable_other_mta("sendmail");
1321         disable_other_mta("vmailmgrd");
1322         disable_other_mta("zimbra");
1323 #endif
1324
1325         /* Check for the 'db' nss and offer to disable it */
1326         fixnss();
1327
1328         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1329                 gid = getgid();
1330         else
1331                 gid = pw->pw_gid;
1332
1333         progress("Setting file permissions", 0, 4);
1334         chown(".", config.c_ctdluid, gid);
1335         sleep(1);
1336         progress("Setting file permissions", 1, 4);
1337         chown(file_citadel_config, config.c_ctdluid, gid);
1338         sleep(1);
1339         progress("Setting file permissions", 2, 4);
1340
1341         snprintf(aaa, sizeof aaa,
1342                          "%schkpwd",
1343                          ctdl_sbin_dir);
1344         chown(aaa,0,0); /*  config.c_ctdluid, gid); chkpwd needs to be root owned*/
1345         sleep(1);
1346         progress("Setting file permissions", 3, 4);
1347         chmod(aaa, 04755); 
1348
1349         sleep(1);
1350         progress("Setting file permissions", 3, 4);
1351         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1352         sleep(1);
1353         progress("Setting file permissions", 4, 4);
1354
1355         /* 
1356          * If we're running on SysV, install init scripts.
1357          */
1358         if (!access("/var/run", W_OK)) {
1359
1360                 if (getenv("NO_INIT_SCRIPTS") == NULL) {
1361                         install_init_scripts();
1362                 }
1363
1364                 if (!access("/etc/init.d/citadel", X_OK)) {
1365                         system("/etc/init.d/citadel start");
1366                         sleep(3);
1367                 }
1368
1369                 if (test_server() == 0) {
1370                         important_message("Setup finished",
1371                                 "Setup of the Citadel server is complete.\n"
1372                                 "If you will be using WebCit, please run its\n"
1373                                 "setup program now; otherwise, run './citadel'\n"
1374                                 "to log in.\n");
1375                 }
1376                 else {
1377                         important_message("Setup failed",
1378                                 "Setup is finished, but the Citadel server failed to start.\n"
1379                                 "Go back and check your configuration.\n"
1380                         );
1381                 }
1382
1383         }
1384
1385         else {
1386                 important_message("Setup finished",
1387                         "Setup is finished.  You may now start the server.");
1388         }
1389
1390         cleanup(0);
1391         return 0;
1392 }
1393
1394