* Updated setup to allow more batch mode control of it;
[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
31 #ifdef HAVE_NEWT
32 #include <newt.h>
33 #endif
34
35
36 #define MAXSETUP 4      /* How many setup questions to ask */
37
38 #define UI_TEXT         0       /* Default setup type -- text only */
39 #define UI_DIALOG       2       /* Use the 'dialog' program */
40 #define UI_SILENT       3       /* Silent running, for use in scripts */
41 #define UI_NEWT         4       /* Use the "newt" window library */
42
43 #define SERVICE_NAME    "citadel"
44 #define PROTO_NAME      "tcp"
45
46 int setup_type;
47 char setup_directory[SIZ];
48 char citserver_init_entry[SIZ];
49 int using_web_installer = 0;
50
51 #ifdef HAVE_LDAP
52 void contemplate_ldap(void);
53 #endif
54
55 char *setup_titles[] =
56 {
57         "Citadel Home Directory",
58         "System Administrator",
59         "Citadel User ID",
60         "Server IP address",
61         "Server port number",
62 };
63
64
65 char *setup_text[] = {
66 "Enter the full pathname of the directory in which the Citadel\n"
67 "installation you are creating or updating resides.  If you\n"
68 "specify a directory other than the default, you will need to\n"
69 "specify the -h flag to the server when you start it up.\n",
70
71 "Enter the name of the system administrator (which is probably\n"
72 "you).  When an account is created with this name, it will\n"
73 "automatically be given administrator-level access.\n",
74
75 "Citadel needs to run under its own user ID.  This would\n"
76 "typically be called \"citadel\", but if you are running Citadel\n"
77 "as a public BBS, you might also call it \"bbs\" or \"guest\".\n"
78 "The server will run under this user ID.  Please specify that\n"
79 "user ID here.  You may specify either a user name or a numeric\n"
80 "UID.\n",
81
82 "Specify the IP address on which your server will run.  If you\n"
83 "leave this blank, or if you specify 0.0.0.0, Citadel will listen\n"
84 "on all addresses.  You can usually skip this unless you are\n"
85 "running multiple instances of Citadel on the same computer.\n",
86
87 "Specify the TCP port number on which your server will run.\n"
88 "Normally, this will be port 504, which is the official port\n"
89 "assigned by the IANA for Citadel servers.  You will only need\n"
90 "to specify a different port number if you run multiple instances\n"
91 "of Citadel on the same computer and there is something else\n"
92 "already using port 504.\n",
93
94 };
95
96 struct config config;
97 int direction;
98
99 /*
100  * Set an entry in inittab to the desired state
101  */
102 void set_init_entry(char *which_entry, char *new_state) {
103         char *inittab = NULL;
104         FILE *fp;
105         char buf[SIZ];
106         char entry[SIZ];
107         char levels[SIZ];
108         char state[SIZ];
109         char prog[SIZ];
110
111         if (which_entry == NULL) return;
112         if (strlen(which_entry) == 0) return;
113
114         inittab = strdup("");
115         if (inittab == NULL) return;
116
117         fp = fopen("/etc/inittab", "r");
118         if (fp == NULL) return;
119
120         while(fgets(buf, sizeof buf, fp) != NULL) {
121
122                 if (num_tokens(buf, ':') == 4) {
123                         extract_token(entry, buf, 0, ':', sizeof entry);
124                         extract_token(levels, buf, 1, ':', sizeof levels);
125                         extract_token(state, buf, 2, ':', sizeof state);
126                         extract_token(prog, buf, 3, ':', sizeof prog); /* includes 0x0a LF */
127
128                         if (!strcmp(entry, which_entry)) {
129                                 strcpy(state, new_state);
130                                 sprintf(buf, "%s:%s:%s:%s",
131                                         entry, levels, state, prog);
132                         }
133                 }
134
135                 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
136                 if (inittab == NULL) {
137                         fclose(fp);
138                         return;
139                 }
140                 
141                 strcat(inittab, buf);
142         }
143         fclose(fp);
144         fp = fopen("/etc/inittab", "w");
145         if (fp != NULL) {
146                 fwrite(inittab, strlen(inittab), 1, fp);
147                 fclose(fp);
148                 kill(1, SIGHUP);        /* Tell init to re-read /etc/inittab */
149         }
150         free(inittab);
151 }
152
153
154 /*
155  * Locate the name of an inittab entry for a specific program
156  */
157 void locate_init_entry(char *init_entry, char *looking_for) {
158
159         FILE *infp;
160         char buf[SIZ];
161         int have_entry = 0;
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, ':', sizeof entry);
177                         extract_token(prog, buf, 3, ':', sizeof prog);
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", setup_directory);
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', sizeof buf);
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', sizeof buf);
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", setup_directory);
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         if (getenv("CREATE_INITTAB_ENTRY") != NULL) {
528                 if (strcasecmp(getenv("CREATE_INITTAB_ENTRY"), "yes")) {
529                         return;
530                 }
531         }
532         else {
533                 snprintf(question, sizeof question,
534                         "Do you want this computer configured to start the Citadel\n"
535                         "service automatically?  (If you answer yes, an entry in\n"
536                         "/etc/inittab pointing to %s will be added.)\n",
537                         looking_for);
538                 if (yesno(question) == 0) {
539                         return;
540                 }
541         }
542
543         /* Generate a unique entry name for /etc/inittab */
544         generate_entry_name(entryname);
545
546         /* Now write it out to /etc/inittab */
547         infp = fopen("/etc/inittab", "a");
548         if (infp == NULL) {
549                 display_error(strerror(errno));
550         } else {
551                 fprintf(infp, "# Start the Citadel server...\n");
552                 fprintf(infp, "%s:2345:respawn:%s -h%s -x3 -llocal4\n",
553                         entryname, looking_for, setup_directory);
554                 fclose(infp);
555                 strcpy(citserver_init_entry, entryname);
556         }
557 }
558
559
560 /*
561  * On systems which use xinetd, see if we can offer to install Citadel as
562  * the default telnet target.
563  */
564 void check_xinetd_entry(void) {
565         char *filename = "/etc/xinetd.d/telnet";
566         FILE *fp;
567         char buf[SIZ];
568         int already_citadel = 0;
569
570         fp = fopen(filename, "r+");
571         if (fp == NULL) return;         /* Not there.  Oh well... */
572
573         while (fgets(buf, sizeof buf, fp) != NULL) {
574                 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
575         }
576         fclose(fp);
577         if (already_citadel) return;    /* Already set up this way. */
578
579         /* Otherwise, prompt the user to create an entry. */
580         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
581                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
582                         return;
583                 }
584         }
585         else {
586                 snprintf(buf, sizeof buf,
587                         "Setup can configure the \"xinetd\" service to automatically\n"
588                         "connect incoming telnet sessions to Citadel, bypassing the\n"
589                         "host system login: prompt.  Would you like to do this?\n"
590                 );
591                 if (yesno(buf) == 0) {
592                         return;
593                 }
594         }
595
596         fp = fopen(filename, "w");
597         fprintf(fp,
598                 "# description: telnet service for Citadel users\n"
599                 "service telnet\n"
600                 "{\n"
601                 "       disable = no\n"
602                 "       flags           = REUSE\n"
603                 "       socket_type     = stream\n"
604                 "       wait            = no\n"
605                 "       user            = root\n"
606                 "       server          = /usr/sbin/in.telnetd\n"
607                 "       server_args     = -h -L %s/citadel\n"
608                 "       log_on_failure  += USERID\n"
609                 "}\n",
610                 setup_directory
611         );
612         fclose(fp);
613
614         /* Now try to restart the service */
615         system("/etc/init.d/xinetd restart >/dev/null 2>&1");
616 }
617
618
619
620 /*
621  * Offer to disable other MTA's
622  */
623 void disable_other_mta(char *mta) {
624         char buf[SIZ];
625         FILE *fp;
626         int lines = 0;
627
628         sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
629                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
630                 mta, mta);
631         fp = popen(buf, "r");
632         if (fp == NULL) return;
633
634         while (fgets(buf, sizeof buf, fp) != NULL) {
635                 ++lines;
636         }
637         fclose(fp);
638         if (lines == 0) return;         /* Nothing to do. */
639
640
641         /* Offer to replace other MTA with the vastly superior Citadel :)  */
642
643         if (getenv("DISABLE_OTHER_MTA")) {
644                 if (strcasecmp(getenv("DISABLE_OTHER_MTA"), "yes")) {
645                         return;
646                 }
647         }
648         else {
649                 snprintf(buf, sizeof buf,
650                         "You appear to have the \"%s\" email program\n"
651                         "running on your system.  If you want Citadel mail\n"
652                         "connected with %s, you will have to manually integrate\n"
653                         "them.  It is preferable to disable %s, and use Citadel's\n"
654                         "SMTP, POP3, and IMAP services.\n\n"
655                         "May we disable %s so that Citadel has access to ports\n"
656                         "25, 110, and 143?\n",
657                         mta, mta, mta, mta
658                 );
659                 if (yesno(buf) == 0) {
660                         return;
661                 }
662         }
663
664         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);
665         system(buf);
666         sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
667         system(buf);
668 }
669
670
671
672
673 /* 
674  * Check to see if our server really works.  Returns 0 on success.
675  */
676 int test_server(void) {
677         char cmd[256];
678         char cookie[256];
679         FILE *fp;
680         char buf[4096];
681         int found_it = 0;
682
683         /* Generate a silly little cookie.  We're going to write it out
684          * to the server and try to get it back.  The cookie does not
685          * have to be secret ... just unique.
686          */
687         sprintf(cookie, "--test--%d--", getpid());
688
689         sprintf(cmd, "%s/sendcommand -h%s ECHO %s 2>&1",
690                 setup_directory,
691                 setup_directory,
692                 cookie);
693
694         fp = popen(cmd, "r");
695         if (fp == NULL) return(errno);
696
697         while (fgets(buf, sizeof buf, fp) != NULL) {
698                 if ( (buf[0]=='2')
699                    && (strstr(buf, cookie) != NULL) ) {
700                         ++found_it;
701                 }
702         }
703         pclose(fp);
704
705         if (found_it) {
706                 return(0);
707         }
708         return(-1);
709 }
710
711 void strprompt(char *prompt_title, char *prompt_text, char *str)
712 {
713 #ifdef HAVE_NEWT
714         newtComponent form;
715         char *result;
716         int i;
717         int prompt_window_height = 0;
718 #endif
719         char buf[SIZ];
720         char setupmsg[SIZ];
721         char *dialog_result;
722         FILE *fp = NULL;
723
724         strcpy(setupmsg, "");
725
726         switch (setup_type) {
727         case UI_TEXT:
728                 title(prompt_title);
729                 printf("\n%s\n", prompt_text);
730                 printf("This is currently set to:\n%s\n", str);
731                 printf("Enter new value or press return to leave unchanged:\n");
732                 fgets(buf, sizeof buf, stdin);
733                 buf[strlen(buf) - 1] = 0;
734                 if (strlen(buf) != 0)
735                         strcpy(str, buf);
736                 break;
737
738         case UI_DIALOG:
739                 dialog_result = tmpnam(NULL);
740                 sprintf(buf, "exec %s --backtitle '%s' --inputbox '%s' 19 72 '%s' 2>%s",
741                         getenv("CTDL_DIALOG"),
742                         prompt_title,
743                         prompt_text,
744                         str,
745                         dialog_result);
746                 system(buf);
747                 fp = fopen(dialog_result, "r");
748                 if (fp != NULL) {
749                         fgets(str, sizeof buf, fp);
750                         if (str[strlen(str)-1] == 10) {
751                                 str[strlen(str)-1] = 0;
752                         }
753                         fclose(fp);
754                         unlink(dialog_result);
755                 }
756                 break;
757
758 #ifdef HAVE_NEWT
759         case UI_NEWT:
760
761                 prompt_window_height = num_tokens(prompt_text, '\n') + 5 ;
762                 newtCenteredWindow(76,
763                                 prompt_window_height,
764                                 prompt_title);
765                 form = newtForm(NULL, NULL, 0);
766                 for (i=0; i<num_tokens(prompt_text, '\n'); ++i) {
767                         extract_token(buf, prompt_text, i, '\n', sizeof buf);
768                         newtFormAddComponent(form, newtLabel(1, 1+i, buf));
769                 }
770                 newtFormAddComponent(form,
771                         newtEntry(1,
772                                 (prompt_window_height - 2),
773                                 str,
774                                 74,
775                                 (const char **) &result,
776                                 NEWT_FLAG_RETURNEXIT)
777                 );
778                 newtRunForm(form);
779                 strcpy(str, result);
780
781                 newtPopWindow();
782                 newtFormDestroy(form);  
783
784 #endif
785         }
786 }
787
788 void set_str_val(int msgpos, char *str) {
789         strprompt(setup_titles[msgpos], setup_text[msgpos], str);
790 }
791
792
793
794 void set_int_val(int msgpos, int *ip)
795 {
796         char buf[16];
797         snprintf(buf, sizeof buf, "%d", (int) *ip);
798         set_str_val(msgpos, buf);
799         *ip = atoi(buf);
800 }
801
802
803 void set_char_val(int msgpos, char *ip)
804 {
805         char buf[16];
806         snprintf(buf, sizeof buf, "%d", (int) *ip);
807         set_str_val(msgpos, buf);
808         *ip = (char) atoi(buf);
809 }
810
811
812 void set_long_val(int msgpos, long int *ip)
813 {
814         char buf[16];
815         snprintf(buf, sizeof buf, "%ld", *ip);
816         set_str_val(msgpos, buf);
817         *ip = atol(buf);
818 }
819
820
821 void edit_value(int curr)
822 {
823         int i;
824         struct passwd *pw;
825         char ctdluidname[SIZ];
826
827         switch (curr) {
828
829         case 1:
830                 set_str_val(curr, config.c_sysadm);
831                 break;
832
833         case 2:
834 #ifdef __CYGWIN__
835                 config.c_ctdluid = 0;   /* XXX Windows hack, prob. insecure */
836 #else
837                 i = config.c_ctdluid;
838                 pw = getpwuid(i);
839                 if (pw == NULL) {
840                         set_int_val(curr, &i);
841                         config.c_ctdluid = i;
842                 }
843                 else {
844                         strcpy(ctdluidname, pw->pw_name);
845                         set_str_val(curr, ctdluidname);
846                         pw = getpwnam(ctdluidname);
847                         if (pw != NULL) {
848                                 config.c_ctdluid = pw->pw_uid;
849                         }
850                         else if (atoi(ctdluidname) > 0) {
851                                 config.c_ctdluid = atoi(ctdluidname);
852                         }
853                 }
854 #endif
855                 break;
856
857         case 3:
858                 set_str_val(curr, config.c_ip_addr);
859                 break;
860
861         case 4:
862                 set_int_val(curr, &config.c_port_number);
863                 break;
864
865
866         }
867 }
868
869 /*
870  * (re-)write the config data to disk
871  */
872 void write_config_to_disk(void)
873 {
874         FILE *fp;
875         int fd;
876
877         if ((fd = creat(
878 #ifndef HAVE_ETC_DIR
879                                         "."
880 #else
881                                         ETC_DIR
882 #endif
883                                         "/citadel.config", S_IRUSR | S_IWUSR)) == -1) {
884                 display_error("setup: cannot open citadel.config");
885                 cleanup(1);
886         }
887         fp = fdopen(fd, "wb");
888         if (fp == NULL) {
889                 display_error("setup: cannot open citadel.config");
890                 cleanup(1);
891         }
892         fwrite((char *) &config, sizeof(struct config), 1, fp);
893         fclose(fp);
894 }
895
896
897
898
899 /*
900  * Figure out what type of user interface we're going to use
901  */
902 int discover_ui(void)
903 {
904
905         /* Use "dialog" if we have it */
906         if (getenv("CTDL_DIALOG") != NULL) {
907                 return UI_DIALOG;
908         }
909                 
910
911 #ifdef HAVE_NEWT
912         newtInit();
913         newtCls();
914         newtDrawRootText(0, 0, "Citadel Setup");
915         return UI_NEWT;
916 #endif
917         return UI_TEXT;
918 }
919
920
921
922
923
924 int main(int argc, char *argv[])
925 {
926         int a;
927         int curr;
928         char aaa[128];
929         FILE *fp;
930         int old_setup_level = 0;
931         int info_only = 0;
932         struct utsname my_utsname;
933         struct passwd *pw;
934         struct hostent *he;
935         gid_t gid;
936
937         /* set an invalid setup type */
938         setup_type = (-1);
939
940         /* Check to see if we're running the web installer */
941         if (getenv("CITADEL_INSTALLER") != NULL) {
942                 using_web_installer = 1;
943         }
944
945         /* parse command line args */
946         for (a = 0; a < argc; ++a) {
947                 if (!strncmp(argv[a], "-u", 2)) {
948                         strcpy(aaa, argv[a]);
949                         strcpy(aaa, &aaa[2]);
950                         setup_type = atoi(aaa);
951                 }
952                 if (!strcmp(argv[a], "-i")) {
953                         info_only = 1;
954                 }
955                 if (!strcmp(argv[a], "-q")) {
956                         setup_type = UI_SILENT;
957                 }
958         }
959
960
961         /* If a setup type was not specified, try to determine automatically
962          * the best one to use out of all available types.
963          */
964         if (setup_type < 0) {
965                 setup_type = discover_ui();
966         }
967         if (info_only == 1) {
968                 important_message("Citadel Setup", CITADEL);
969                 cleanup(0);
970         }
971
972         /* Get started in a valid setup directory. */
973         strcpy(setup_directory, CTDLDIR);
974         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
975                 strcpy(setup_directory, getenv("CITADEL"));
976         }
977         else {
978                 set_str_val(0, setup_directory);
979         }
980
981         if (chdir(setup_directory) != 0) {
982                 important_message("Citadel Setup",
983                           "The directory you specified does not exist.");
984                 cleanup(errno);
985         }
986
987         /* Determine our host name, in case we need to use it as a default */
988         uname(&my_utsname);
989
990         /* See if we need to shut down the Citadel service. */
991         for (a=0; a<=3; ++a) {
992                 progress("Shutting down the Citadel service...", a, 3);
993                 if (a == 0) shutdown_citserver();
994                 sleep(1);
995         }
996
997         /* Make sure it's stopped. */
998         if (test_server() == 0) {
999                 important_message("Citadel Setup",
1000                         "The Citadel service is still running.\n"
1001                         "Please stop the service manually and run "
1002                         "setup again.");
1003                 cleanup(1);
1004         }
1005
1006         /* Now begin. */
1007         switch (setup_type) {
1008
1009         case UI_TEXT:
1010                 printf("\n\n\n"
1011                         "               *** Citadel setup program ***\n\n");
1012                 break;
1013
1014         }
1015
1016         /*
1017          * What we're going to try to do here is append a whole bunch of
1018          * nulls to the citadel.config file, so we can keep the old config
1019          * values if they exist, but if the file is missing or from an
1020          * earlier version with a shorter config structure, when setup tries
1021          * to read the old config parameters, they'll all come up zero.
1022          * The length of the config file will be set to what it's supposed
1023          * to be when we rewrite it, because we replace the old file with a
1024          * completely new copy.
1025          */
1026
1027         if ((a = open(
1028 #ifndef HAVE_ETC_DIR
1029                                   "."
1030 #else
1031                                   ETC_DIR
1032 #endif
1033                                   "/citadel.config", O_WRONLY | O_CREAT | O_APPEND,
1034                       S_IRUSR | S_IWUSR)) == -1) {
1035                 display_error("setup: cannot append citadel.config");
1036                 cleanup(errno);
1037         }
1038         fp = fdopen(a, "ab");
1039         if (fp == NULL) {
1040                 display_error("setup: cannot append citadel.config");
1041                 cleanup(errno);
1042         }
1043         for (a = 0; a < sizeof(struct config); ++a)
1044                 putc(0, fp);
1045         fclose(fp);
1046
1047         /* now we re-open it, and read the old or blank configuration */
1048         fp = fopen(
1049 #ifndef HAVE_ETC_DIR
1050                            "."
1051 #else
1052                            ETC_DIR
1053 #endif
1054                            "/citadel.config", "rb");
1055         if (fp == NULL) {
1056                 display_error("setup: cannot open citadel.config");
1057                 cleanup(errno);
1058         }
1059         fread((char *) &config, sizeof(struct config), 1, fp);
1060         fclose(fp);
1061
1062         /* set some sample/default values in place of blanks... */
1063         if (strlen(config.c_nodename) == 0)
1064                 safestrncpy(config.c_nodename, my_utsname.nodename,
1065                             sizeof config.c_nodename);
1066         strtok(config.c_nodename, ".");
1067         if (strlen(config.c_fqdn) == 0) {
1068                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1069                         safestrncpy(config.c_fqdn, he->h_name,
1070                                     sizeof config.c_fqdn);
1071                 else
1072                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1073                                     sizeof config.c_fqdn);
1074         }
1075         if (strlen(config.c_humannode) == 0)
1076                 strcpy(config.c_humannode, "My System");
1077         if (strlen(config.c_phonenum) == 0)
1078                 strcpy(config.c_phonenum, "US 800 555 1212");
1079         if (config.c_initax == 0) {
1080                 config.c_initax = 4;
1081         }
1082         if (strlen(config.c_moreprompt) == 0)
1083                 strcpy(config.c_moreprompt, "<more>");
1084         if (strlen(config.c_twitroom) == 0)
1085                 strcpy(config.c_twitroom, "Trashcan");
1086         if (strlen(config.c_baseroom) == 0)
1087                 strcpy(config.c_baseroom, BASEROOM);
1088         if (strlen(config.c_aideroom) == 0)
1089                 strcpy(config.c_aideroom, "Aide");
1090         if (config.c_port_number == 0) {
1091                 config.c_port_number = 504;
1092         }
1093         if (config.c_sleeping == 0) {
1094                 config.c_sleeping = 900;
1095         }
1096         if (config.c_ctdluid == 0) {
1097                 pw = getpwnam("citadel");
1098                 if (pw != NULL)
1099                         config.c_ctdluid = pw->pw_uid;
1100         }
1101         if (config.c_ctdluid == 0) {
1102                 pw = getpwnam("bbs");
1103                 if (pw != NULL)
1104                         config.c_ctdluid = pw->pw_uid;
1105         }
1106         if (config.c_ctdluid == 0) {
1107                 pw = getpwnam("guest");
1108                 if (pw != NULL)
1109                         config.c_ctdluid = pw->pw_uid;
1110         }
1111         if (config.c_createax == 0) {
1112                 config.c_createax = 3;
1113         }
1114         /*
1115          * Negative values for maxsessions are not allowed.
1116          */
1117         if (config.c_maxsessions < 0) {
1118                 config.c_maxsessions = 0;
1119         }
1120         /* We need a system default message expiry policy, because this is
1121          * the top level and there's no 'higher' policy to fall back on.
1122          * By default, do not expire messages at all.
1123          */
1124         if (config.c_ep.expire_mode == 0) {
1125                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1126                 config.c_ep.expire_value = 0;
1127         }
1128
1129         /*
1130          * Default port numbers for various services
1131          */
1132         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1133         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1134         if (config.c_imap_port == 0) config.c_imap_port = 143;
1135         if (config.c_msa_port == 0) config.c_msa_port = 587;
1136         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1137         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1138         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1139
1140         /* Go through a series of dialogs prompting for config info */
1141         if (setup_type != UI_SILENT) {
1142                 for (curr = 1; curr <= MAXSETUP; ++curr) {
1143                         edit_value(curr);
1144                 }
1145         }
1146
1147 /***** begin version update section ***** */
1148         /* take care of any updating that is necessary */
1149
1150         old_setup_level = config.c_setup_level;
1151
1152         if (old_setup_level == 0) {
1153                 goto NEW_INST;
1154         }
1155
1156         if (old_setup_level < 555) {
1157                 important_message("Citadel Setup",
1158                                   "This Citadel installation is too old "
1159                                   "to be upgraded.");
1160                 cleanup(1);
1161         }
1162         write_config_to_disk();
1163
1164         old_setup_level = config.c_setup_level;
1165
1166         /* end of version update section */
1167
1168 NEW_INST:
1169         config.c_setup_level = REV_LEVEL;
1170
1171 /******************************************/
1172
1173         write_config_to_disk();
1174
1175         mkdir("info", 0700);
1176         chmod("info", 0700);
1177         mkdir("bio", 0700);
1178         chmod("bio", 0700);
1179         mkdir("userpics", 0700);
1180         chmod("userpics", 0700);
1181         mkdir("messages", 0700);
1182         chmod("messages", 0700);
1183         mkdir("help", 0700);
1184         chmod("help", 0700);
1185         mkdir("images", 0700);
1186         chmod("images", 0700);
1187         mkdir("netconfigs", 0700);
1188         chmod("netconfigs", 0700);
1189
1190         /* Delete files and directories used by older Citadel versions */
1191         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1192         unlink("citadel.log");
1193         unlink("weekly");
1194
1195         check_services_entry(); /* Check /etc/services */
1196 #ifndef __CYGWIN__
1197         check_inittab_entry();  /* Check /etc/inittab */
1198         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1199
1200         /* Offer to disable other MTA's on the system. */
1201         disable_other_mta("sendmail");
1202         disable_other_mta("postfix");
1203         disable_other_mta("qmail");
1204         disable_other_mta("cyrus");
1205         disable_other_mta("cyrmaster");
1206         disable_other_mta("saslauthd");
1207         disable_other_mta("mta");
1208         disable_other_mta("courier-imap");
1209         disable_other_mta("courier-imap-ssl");
1210         disable_other_mta("courier-authdaemon");
1211         disable_other_mta("courier-pop3");
1212         disable_other_mta("courier-pop3d");
1213         disable_other_mta("courier-pop");
1214         disable_other_mta("vmailmgrd");
1215         disable_other_mta("imapd");
1216         disable_other_mta("popd");
1217         disable_other_mta("pop3d");
1218         disable_other_mta("exim");
1219         disable_other_mta("exim4");
1220         disable_other_mta("dovecot");
1221         disable_other_mta("hula");
1222 #endif
1223
1224         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1225                 gid = getgid();
1226         else
1227                 gid = pw->pw_gid;
1228
1229         progress("Setting file permissions", 0, 4);
1230         chown(".", config.c_ctdluid, gid);
1231         sleep(1);
1232         progress("Setting file permissions", 1, 4);
1233         chown(
1234 #ifndef HAVE_ETC_DIR
1235                   "."
1236 #else
1237                   ETC_DIR
1238 #endif
1239                   "/citadel.config", config.c_ctdluid, gid);
1240         sleep(1);
1241         progress("Setting file permissions", 2, 4);
1242         snprintf(aaa, sizeof aaa,
1243                 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
1244                 (long)config.c_ctdluid, (long)gid);
1245         system(aaa);
1246         sleep(1);
1247         progress("Setting file permissions", 3, 4);
1248         chmod(
1249 #ifndef HAVE_ETC_DIR
1250                   "."
1251 #else
1252                   ETC_DIR
1253 #endif
1254                   "/citadel.config", S_IRUSR | S_IWUSR);
1255         sleep(1);
1256         progress("Setting file permissions", 4, 4);
1257
1258 #ifdef HAVE_LDAP
1259         /* Contemplate the possibility of auto-configuring OpenLDAP */
1260         contemplate_ldap();
1261 #endif
1262
1263         /* See if we can start the Citadel service. */
1264         if (strlen(citserver_init_entry) > 0) {
1265                 for (a=0; a<=3; ++a) {
1266                         progress("Starting the Citadel service...", a, 3);
1267                         if (a == 0) start_citserver();
1268                         sleep(1);
1269                 }
1270                 if (test_server() == 0) {
1271                         important_message("Setup finished",
1272                                 "Setup of the Citadel server is complete.\n"
1273                                 "If you will be using WebCit, please run its\n"
1274                                 "setup program now; otherwise, run './citadel'\n"
1275                                 "to log in.\n");
1276                 }
1277                 else {
1278                         important_message("Setup finished",
1279                                 "Setup is finished, but the Citadel service "
1280                                 "failed to start.\n"
1281                                 "Go back and check your configuration.");
1282                 }
1283         }
1284         else {
1285                 important_message("Setup finished",
1286                         "Setup is finished.  You may now start the server.");
1287         }
1288
1289         cleanup(0);
1290         return 0;
1291 }
1292
1293
1294 #ifdef HAVE_LDAP
1295 /*
1296  * If we're in the middle of an Easy Install, we might just be able to
1297  * auto-configure a standalone OpenLDAP server.
1298  */
1299 void contemplate_ldap(void) {
1300         char question[SIZ];
1301         char slapd_init_entry[SIZ];
1302         FILE *fp;
1303
1304         /* If conditions are not ideal, give up on this idea... */
1305         if (using_web_installer == 0) return;
1306         if (getenv("LDAP_CONFIG") == NULL) return;
1307         if (getenv("SUPPORT") == NULL) return;
1308         if (getenv("SLAPD_BINARY") == NULL) return;
1309         if (getenv("CITADEL") == NULL) return;
1310
1311         /* And if inittab is already starting slapd, bail out... */
1312         locate_init_entry(slapd_init_entry, getenv("SLAPD_BINARY"));
1313         if (strlen(slapd_init_entry) > 0) {
1314                 important_message("Citadel Setup",
1315                         "You appear to already have a standalone LDAP "
1316                         "service\nconfigured for use with Citadel.  No "
1317                         "changes will be made.\n");
1318                 /* set_init_entry(slapd_init_entry, "off"); */
1319                 return;
1320         }
1321
1322         /* Generate a unique entry name for slapd if we don't have one. */
1323         else {
1324                 generate_entry_name(slapd_init_entry);
1325         }
1326
1327         /* Ask the user if it's ok to set up slapd automatically. */
1328         snprintf(question, sizeof question,
1329                 "\n"
1330                 "Do you want this computer configured to start a standalone\n"
1331                 "LDAP service automatically?  (If you answer yes, a new\n"
1332                 "slapd.conf will be written, and an /etc/inittab entry\n"
1333                 "pointing to %s will be added.)\n"
1334                 "\n",
1335                 getenv("SLAPD_BINARY")
1336         );
1337         if (yesno(question) == 0)
1338                 return;
1339
1340         strcpy(config.c_ldap_base_dn, "dc=example,dc=com");
1341         strprompt("Base DN",
1342                 "\n"
1343                 "Please enter the Base DN for your directory.  This will\n"
1344                 "generally be something based on the primary DNS domain in\n"
1345                 "which you receive mail, but it does not have to be.  Your\n"
1346                 "LDAP tree will be built using this Distinguished Name.\n"
1347                 "\n",
1348                 config.c_ldap_base_dn
1349         );
1350
1351         strcpy(config.c_ldap_host, "localhost");
1352         config.c_ldap_port = 389;
1353         sprintf(config.c_ldap_bind_dn, "cn=manager,%s", config.c_ldap_base_dn);
1354
1355         /*
1356          * Generate a bind password.  If you're some grey hat hacker who
1357          * is just dying to get some street cred on Bugtraq, and you think
1358          * this password generation scheme is too weak, please submit a patch
1359          * instead of just whining about it, ok?
1360          */
1361         sprintf(config.c_ldap_bind_pw, "%d%ld", getpid(), (long)time(NULL));
1362
1363         write_config_to_disk();
1364
1365         fp = fopen(getenv("LDAP_CONFIG"), "w");
1366         if (fp == NULL) {
1367                 sprintf(question, "\nCannot create %s:\n%s\n\n"
1368                                 "Citadel will still function, but you will "
1369                                 "not have an LDAP service.\n\n",
1370                                 getenv("LDAP_CONFIG"),
1371                                 strerror(errno)
1372                 );
1373                 important_message("Error", question);
1374                 return;
1375         }
1376
1377         fprintf(fp, "include    %s/citadel-openldap.schema\n",
1378                 getenv("CITADEL"));
1379         fprintf(fp, "pidfile    %s/openldap-data/slapd.pid\n",
1380                 getenv("CITADEL"));
1381         fprintf(fp, "argsfile   %s/openldap-data/slapd.args\n",
1382                 getenv("CITADEL"));
1383         fprintf(fp,     "allow          bind_v2\n"
1384                         "database       bdb\n"
1385                         "schemacheck    off\n"
1386         );
1387         fprintf(fp,     "suffix         \"%s\"\n", config.c_ldap_base_dn);
1388         fprintf(fp,     "rootdn         \"%s\"\n", config.c_ldap_bind_dn);
1389         fprintf(fp,     "rootpw         %s\n", config.c_ldap_bind_pw);
1390         fprintf(fp,     "directory      %s/openldap-data\n",
1391                 getenv("CITADEL"));
1392         fprintf(fp,     "index          objectClass     eq\n");
1393
1394         fclose(fp);
1395
1396         /* This is where our OpenLDAP server will keep its data. */
1397         mkdir("openldap-data", 0700);
1398
1399         /* Now write it out to /etc/inittab.
1400          * FIXME make it run as some non-root user.
1401          * The "-d 0" seems superfluous, but it's actually a way to make
1402          * slapd run in the foreground without spewing messages to the console.
1403          */
1404         fp = fopen("/etc/inittab", "a");
1405         if (fp == NULL) {
1406                 display_error(strerror(errno));
1407         } else {
1408                 fprintf(fp, "# Start the OpenLDAP server for Citadel...\n");
1409                 fprintf(fp, "%s:2345:respawn:%s -d 0 -f %s\n",
1410                         slapd_init_entry,
1411                         getenv("SLAPD_BINARY"),
1412                         getenv("LDAP_CONFIG")
1413                 );
1414                 fclose(fp);
1415         }
1416
1417 }
1418 #endif  /* HAVE_LDAP */