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