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