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