more changes to batch mode setup
[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("ACT_AS_MTA")) {
644                 if (strcasecmp(getenv("ACT_AS_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                 if (getenv("SYSADMIN_NAME")) {
831                         strcpy(config.c_sysadm, getenv("SYSADMIN_NAME"));
832                 }
833                 else {
834                         set_str_val(curr, config.c_sysadm);
835                 }
836                 break;
837
838         case 2:
839 #ifdef __CYGWIN__
840                 config.c_ctdluid = 0;   /* XXX Windows hack, prob. insecure */
841 #else
842                 i = config.c_ctdluid;
843                 pw = getpwuid(i);
844                 if (pw == NULL) {
845                         set_int_val(curr, &i);
846                         config.c_ctdluid = i;
847                 }
848                 else {
849                         strcpy(ctdluidname, pw->pw_name);
850                         set_str_val(curr, ctdluidname);
851                         pw = getpwnam(ctdluidname);
852                         if (pw != NULL) {
853                                 config.c_ctdluid = pw->pw_uid;
854                         }
855                         else if (atoi(ctdluidname) > 0) {
856                                 config.c_ctdluid = atoi(ctdluidname);
857                         }
858                 }
859 #endif
860                 break;
861
862         case 3:
863                 set_str_val(curr, config.c_ip_addr);
864                 break;
865
866         case 4:
867                 set_int_val(curr, &config.c_port_number);
868                 break;
869
870
871         }
872 }
873
874 /*
875  * (re-)write the config data to disk
876  */
877 void write_config_to_disk(void)
878 {
879         FILE *fp;
880         int fd;
881
882         if ((fd = creat(
883 #ifndef HAVE_ETC_DIR
884                                         "."
885 #else
886                                         ETC_DIR
887 #endif
888                                         "/citadel.config", S_IRUSR | S_IWUSR)) == -1) {
889                 display_error("setup: cannot open citadel.config");
890                 cleanup(1);
891         }
892         fp = fdopen(fd, "wb");
893         if (fp == NULL) {
894                 display_error("setup: cannot open citadel.config");
895                 cleanup(1);
896         }
897         fwrite((char *) &config, sizeof(struct config), 1, fp);
898         fclose(fp);
899 }
900
901
902
903
904 /*
905  * Figure out what type of user interface we're going to use
906  */
907 int discover_ui(void)
908 {
909
910         /* Use "dialog" if we have it */
911         if (getenv("CTDL_DIALOG") != NULL) {
912                 return UI_DIALOG;
913         }
914                 
915
916 #ifdef HAVE_NEWT
917         newtInit();
918         newtCls();
919         newtDrawRootText(0, 0, "Citadel Setup");
920         return UI_NEWT;
921 #endif
922         return UI_TEXT;
923 }
924
925
926
927
928
929 int main(int argc, char *argv[])
930 {
931         int a;
932         int curr;
933         char aaa[128];
934         FILE *fp;
935         int old_setup_level = 0;
936         int info_only = 0;
937         struct utsname my_utsname;
938         struct passwd *pw;
939         struct hostent *he;
940         gid_t gid;
941
942         /* set an invalid setup type */
943         setup_type = (-1);
944
945         /* Check to see if we're running the web installer */
946         if (getenv("CITADEL_INSTALLER") != NULL) {
947                 using_web_installer = 1;
948         }
949
950         /* parse command line args */
951         for (a = 0; a < argc; ++a) {
952                 if (!strncmp(argv[a], "-u", 2)) {
953                         strcpy(aaa, argv[a]);
954                         strcpy(aaa, &aaa[2]);
955                         setup_type = atoi(aaa);
956                 }
957                 if (!strcmp(argv[a], "-i")) {
958                         info_only = 1;
959                 }
960                 if (!strcmp(argv[a], "-q")) {
961                         setup_type = UI_SILENT;
962                 }
963         }
964
965
966         /* If a setup type was not specified, try to determine automatically
967          * the best one to use out of all available types.
968          */
969         if (setup_type < 0) {
970                 setup_type = discover_ui();
971         }
972         if (info_only == 1) {
973                 important_message("Citadel Setup", CITADEL);
974                 cleanup(0);
975         }
976
977         /* Get started in a valid setup directory. */
978         strcpy(setup_directory, CTDLDIR);
979         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
980                 strcpy(setup_directory, getenv("CITADEL"));
981         }
982         else {
983                 set_str_val(0, setup_directory);
984         }
985
986         if (chdir(setup_directory) != 0) {
987                 important_message("Citadel Setup",
988                           "The directory you specified does not exist.");
989                 cleanup(errno);
990         }
991
992         /* Determine our host name, in case we need to use it as a default */
993         uname(&my_utsname);
994
995         /* See if we need to shut down the Citadel service. */
996         for (a=0; a<=3; ++a) {
997                 progress("Shutting down the Citadel service...", a, 3);
998                 if (a == 0) shutdown_citserver();
999                 sleep(1);
1000         }
1001
1002         /* Make sure it's stopped. */
1003         if (test_server() == 0) {
1004                 important_message("Citadel Setup",
1005                         "The Citadel service is still running.\n"
1006                         "Please stop the service manually and run "
1007                         "setup again.");
1008                 cleanup(1);
1009         }
1010
1011         /* Now begin. */
1012         switch (setup_type) {
1013
1014         case UI_TEXT:
1015                 printf("\n\n\n"
1016                         "               *** Citadel setup program ***\n\n");
1017                 break;
1018
1019         }
1020
1021         /*
1022          * What we're going to try to do here is append a whole bunch of
1023          * nulls to the citadel.config file, so we can keep the old config
1024          * values if they exist, but if the file is missing or from an
1025          * earlier version with a shorter config structure, when setup tries
1026          * to read the old config parameters, they'll all come up zero.
1027          * The length of the config file will be set to what it's supposed
1028          * to be when we rewrite it, because we replace the old file with a
1029          * completely new copy.
1030          */
1031
1032         if ((a = open(
1033 #ifndef HAVE_ETC_DIR
1034                                   "."
1035 #else
1036                                   ETC_DIR
1037 #endif
1038                                   "/citadel.config", O_WRONLY | O_CREAT | O_APPEND,
1039                       S_IRUSR | S_IWUSR)) == -1) {
1040                 display_error("setup: cannot append citadel.config");
1041                 cleanup(errno);
1042         }
1043         fp = fdopen(a, "ab");
1044         if (fp == NULL) {
1045                 display_error("setup: cannot append citadel.config");
1046                 cleanup(errno);
1047         }
1048         for (a = 0; a < sizeof(struct config); ++a)
1049                 putc(0, fp);
1050         fclose(fp);
1051
1052         /* now we re-open it, and read the old or blank configuration */
1053         fp = fopen(
1054 #ifndef HAVE_ETC_DIR
1055                            "."
1056 #else
1057                            ETC_DIR
1058 #endif
1059                            "/citadel.config", "rb");
1060         if (fp == NULL) {
1061                 display_error("setup: cannot open citadel.config");
1062                 cleanup(errno);
1063         }
1064         fread((char *) &config, sizeof(struct config), 1, fp);
1065         fclose(fp);
1066
1067         /* set some sample/default values in place of blanks... */
1068         if (strlen(config.c_nodename) == 0)
1069                 safestrncpy(config.c_nodename, my_utsname.nodename,
1070                             sizeof config.c_nodename);
1071         strtok(config.c_nodename, ".");
1072         if (strlen(config.c_fqdn) == 0) {
1073                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1074                         safestrncpy(config.c_fqdn, he->h_name,
1075                                     sizeof config.c_fqdn);
1076                 else
1077                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1078                                     sizeof config.c_fqdn);
1079         }
1080         if (strlen(config.c_humannode) == 0)
1081                 strcpy(config.c_humannode, "My System");
1082         if (strlen(config.c_phonenum) == 0)
1083                 strcpy(config.c_phonenum, "US 800 555 1212");
1084         if (config.c_initax == 0) {
1085                 config.c_initax = 4;
1086         }
1087         if (strlen(config.c_moreprompt) == 0)
1088                 strcpy(config.c_moreprompt, "<more>");
1089         if (strlen(config.c_twitroom) == 0)
1090                 strcpy(config.c_twitroom, "Trashcan");
1091         if (strlen(config.c_baseroom) == 0)
1092                 strcpy(config.c_baseroom, BASEROOM);
1093         if (strlen(config.c_aideroom) == 0)
1094                 strcpy(config.c_aideroom, "Aide");
1095         if (config.c_port_number == 0) {
1096                 config.c_port_number = 504;
1097         }
1098         if (config.c_sleeping == 0) {
1099                 config.c_sleeping = 900;
1100         }
1101         if (config.c_ctdluid == 0) {
1102                 pw = getpwnam("citadel");
1103                 if (pw != NULL)
1104                         config.c_ctdluid = pw->pw_uid;
1105         }
1106         if (config.c_ctdluid == 0) {
1107                 pw = getpwnam("bbs");
1108                 if (pw != NULL)
1109                         config.c_ctdluid = pw->pw_uid;
1110         }
1111         if (config.c_ctdluid == 0) {
1112                 pw = getpwnam("guest");
1113                 if (pw != NULL)
1114                         config.c_ctdluid = pw->pw_uid;
1115         }
1116         if (config.c_createax == 0) {
1117                 config.c_createax = 3;
1118         }
1119         /*
1120          * Negative values for maxsessions are not allowed.
1121          */
1122         if (config.c_maxsessions < 0) {
1123                 config.c_maxsessions = 0;
1124         }
1125         /* We need a system default message expiry policy, because this is
1126          * the top level and there's no 'higher' policy to fall back on.
1127          * By default, do not expire messages at all.
1128          */
1129         if (config.c_ep.expire_mode == 0) {
1130                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1131                 config.c_ep.expire_value = 0;
1132         }
1133
1134         /*
1135          * Default port numbers for various services
1136          */
1137         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1138         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1139         if (config.c_imap_port == 0) config.c_imap_port = 143;
1140         if (config.c_msa_port == 0) config.c_msa_port = 587;
1141         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1142         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1143         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1144
1145         /* Go through a series of dialogs prompting for config info */
1146         if (setup_type != UI_SILENT) {
1147                 for (curr = 1; curr <= MAXSETUP; ++curr) {
1148                         edit_value(curr);
1149                 }
1150         }
1151
1152 /***** begin version update section ***** */
1153         /* take care of any updating that is necessary */
1154
1155         old_setup_level = config.c_setup_level;
1156
1157         if (old_setup_level == 0) {
1158                 goto NEW_INST;
1159         }
1160
1161         if (old_setup_level < 555) {
1162                 important_message("Citadel Setup",
1163                                   "This Citadel installation is too old "
1164                                   "to be upgraded.");
1165                 cleanup(1);
1166         }
1167         write_config_to_disk();
1168
1169         old_setup_level = config.c_setup_level;
1170
1171         /* end of version update section */
1172
1173 NEW_INST:
1174         config.c_setup_level = REV_LEVEL;
1175
1176 /******************************************/
1177
1178         write_config_to_disk();
1179
1180         mkdir("info", 0700);
1181         chmod("info", 0700);
1182         mkdir("bio", 0700);
1183         chmod("bio", 0700);
1184         mkdir("userpics", 0700);
1185         chmod("userpics", 0700);
1186         mkdir("messages", 0700);
1187         chmod("messages", 0700);
1188         mkdir("help", 0700);
1189         chmod("help", 0700);
1190         mkdir("images", 0700);
1191         chmod("images", 0700);
1192         mkdir("netconfigs", 0700);
1193         chmod("netconfigs", 0700);
1194
1195         /* Delete files and directories used by older Citadel versions */
1196         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1197         unlink("citadel.log");
1198         unlink("weekly");
1199
1200         check_services_entry(); /* Check /etc/services */
1201 #ifndef __CYGWIN__
1202         check_inittab_entry();  /* Check /etc/inittab */
1203         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1204
1205         /* Offer to disable other MTA's on the system. */
1206         disable_other_mta("sendmail");
1207         disable_other_mta("postfix");
1208         disable_other_mta("qmail");
1209         disable_other_mta("cyrus");
1210         disable_other_mta("cyrmaster");
1211         disable_other_mta("saslauthd");
1212         disable_other_mta("mta");
1213         disable_other_mta("courier-imap");
1214         disable_other_mta("courier-imap-ssl");
1215         disable_other_mta("courier-authdaemon");
1216         disable_other_mta("courier-pop3");
1217         disable_other_mta("courier-pop3d");
1218         disable_other_mta("courier-pop");
1219         disable_other_mta("vmailmgrd");
1220         disable_other_mta("imapd");
1221         disable_other_mta("popd");
1222         disable_other_mta("pop3d");
1223         disable_other_mta("exim");
1224         disable_other_mta("exim4");
1225         disable_other_mta("dovecot");
1226         disable_other_mta("hula");
1227 #endif
1228
1229         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1230                 gid = getgid();
1231         else
1232                 gid = pw->pw_gid;
1233
1234         progress("Setting file permissions", 0, 4);
1235         chown(".", config.c_ctdluid, gid);
1236         sleep(1);
1237         progress("Setting file permissions", 1, 4);
1238         chown(
1239 #ifndef HAVE_ETC_DIR
1240                   "."
1241 #else
1242                   ETC_DIR
1243 #endif
1244                   "/citadel.config", config.c_ctdluid, gid);
1245         sleep(1);
1246         progress("Setting file permissions", 2, 4);
1247         snprintf(aaa, sizeof aaa,
1248                 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
1249                 (long)config.c_ctdluid, (long)gid);
1250         system(aaa);
1251         sleep(1);
1252         progress("Setting file permissions", 3, 4);
1253         chmod(
1254 #ifndef HAVE_ETC_DIR
1255                   "."
1256 #else
1257                   ETC_DIR
1258 #endif
1259                   "/citadel.config", S_IRUSR | S_IWUSR);
1260         sleep(1);
1261         progress("Setting file permissions", 4, 4);
1262
1263 #ifdef HAVE_LDAP
1264         /* Contemplate the possibility of auto-configuring OpenLDAP */
1265         contemplate_ldap();
1266 #endif
1267
1268         /* See if we can start the Citadel service. */
1269         if (strlen(citserver_init_entry) > 0) {
1270                 for (a=0; a<=3; ++a) {
1271                         progress("Starting the Citadel service...", a, 3);
1272                         if (a == 0) start_citserver();
1273                         sleep(1);
1274                 }
1275                 if (test_server() == 0) {
1276                         important_message("Setup finished",
1277                                 "Setup of the Citadel server is complete.\n"
1278                                 "If you will be using WebCit, please run its\n"
1279                                 "setup program now; otherwise, run './citadel'\n"
1280                                 "to log in.\n");
1281                 }
1282                 else {
1283                         important_message("Setup finished",
1284                                 "Setup is finished, but the Citadel service "
1285                                 "failed to start.\n"
1286                                 "Go back and check your configuration.");
1287                 }
1288         }
1289         else {
1290                 important_message("Setup finished",
1291                         "Setup is finished.  You may now start the server.");
1292         }
1293
1294         cleanup(0);
1295         return 0;
1296 }
1297
1298
1299 #ifdef HAVE_LDAP
1300 /*
1301  * If we're in the middle of an Easy Install, we might just be able to
1302  * auto-configure a standalone OpenLDAP server.
1303  */
1304 void contemplate_ldap(void) {
1305         char question[SIZ];
1306         char slapd_init_entry[SIZ];
1307         FILE *fp;
1308
1309         /* If conditions are not ideal, give up on this idea... */
1310         if (using_web_installer == 0) return;
1311         if (getenv("LDAP_CONFIG") == NULL) return;
1312         if (getenv("SUPPORT") == NULL) return;
1313         if (getenv("SLAPD_BINARY") == NULL) return;
1314         if (getenv("CITADEL") == NULL) return;
1315
1316         /* And if inittab is already starting slapd, bail out... */
1317         locate_init_entry(slapd_init_entry, getenv("SLAPD_BINARY"));
1318         if (strlen(slapd_init_entry) > 0) {
1319                 important_message("Citadel Setup",
1320                         "You appear to already have a standalone LDAP "
1321                         "service\nconfigured for use with Citadel.  No "
1322                         "changes will be made.\n");
1323                 /* set_init_entry(slapd_init_entry, "off"); */
1324                 return;
1325         }
1326
1327         /* Generate a unique entry name for slapd if we don't have one. */
1328         else {
1329                 generate_entry_name(slapd_init_entry);
1330         }
1331
1332         /* Ask the user if it's ok to set up slapd automatically. */
1333         snprintf(question, sizeof question,
1334                 "\n"
1335                 "Do you want this computer configured to start a standalone\n"
1336                 "LDAP service automatically?  (If you answer yes, a new\n"
1337                 "slapd.conf will be written, and an /etc/inittab entry\n"
1338                 "pointing to %s will be added.)\n"
1339                 "\n",
1340                 getenv("SLAPD_BINARY")
1341         );
1342         if (yesno(question) == 0)
1343                 return;
1344
1345         strcpy(config.c_ldap_base_dn, "dc=example,dc=com");
1346         strprompt("Base DN",
1347                 "\n"
1348                 "Please enter the Base DN for your directory.  This will\n"
1349                 "generally be something based on the primary DNS domain in\n"
1350                 "which you receive mail, but it does not have to be.  Your\n"
1351                 "LDAP tree will be built using this Distinguished Name.\n"
1352                 "\n",
1353                 config.c_ldap_base_dn
1354         );
1355
1356         strcpy(config.c_ldap_host, "localhost");
1357         config.c_ldap_port = 389;
1358         sprintf(config.c_ldap_bind_dn, "cn=manager,%s", config.c_ldap_base_dn);
1359
1360         /*
1361          * Generate a bind password.  If you're some grey hat hacker who
1362          * is just dying to get some street cred on Bugtraq, and you think
1363          * this password generation scheme is too weak, please submit a patch
1364          * instead of just whining about it, ok?
1365          */
1366         sprintf(config.c_ldap_bind_pw, "%d%ld", getpid(), (long)time(NULL));
1367
1368         write_config_to_disk();
1369
1370         fp = fopen(getenv("LDAP_CONFIG"), "w");
1371         if (fp == NULL) {
1372                 sprintf(question, "\nCannot create %s:\n%s\n\n"
1373                                 "Citadel will still function, but you will "
1374                                 "not have an LDAP service.\n\n",
1375                                 getenv("LDAP_CONFIG"),
1376                                 strerror(errno)
1377                 );
1378                 important_message("Error", question);
1379                 return;
1380         }
1381
1382         fprintf(fp, "include    %s/citadel-openldap.schema\n",
1383                 getenv("CITADEL"));
1384         fprintf(fp, "pidfile    %s/openldap-data/slapd.pid\n",
1385                 getenv("CITADEL"));
1386         fprintf(fp, "argsfile   %s/openldap-data/slapd.args\n",
1387                 getenv("CITADEL"));
1388         fprintf(fp,     "allow          bind_v2\n"
1389                         "database       bdb\n"
1390                         "schemacheck    off\n"
1391         );
1392         fprintf(fp,     "suffix         \"%s\"\n", config.c_ldap_base_dn);
1393         fprintf(fp,     "rootdn         \"%s\"\n", config.c_ldap_bind_dn);
1394         fprintf(fp,     "rootpw         %s\n", config.c_ldap_bind_pw);
1395         fprintf(fp,     "directory      %s/openldap-data\n",
1396                 getenv("CITADEL"));
1397         fprintf(fp,     "index          objectClass     eq\n");
1398
1399         fclose(fp);
1400
1401         /* This is where our OpenLDAP server will keep its data. */
1402         mkdir("openldap-data", 0700);
1403
1404         /* Now write it out to /etc/inittab.
1405          * FIXME make it run as some non-root user.
1406          * The "-d 0" seems superfluous, but it's actually a way to make
1407          * slapd run in the foreground without spewing messages to the console.
1408          */
1409         fp = fopen("/etc/inittab", "a");
1410         if (fp == NULL) {
1411                 display_error(strerror(errno));
1412         } else {
1413                 fprintf(fp, "# Start the OpenLDAP server for Citadel...\n");
1414                 fprintf(fp, "%s:2345:respawn:%s -d 0 -f %s\n",
1415                         slapd_init_entry,
1416                         getenv("SLAPD_BINARY"),
1417                         getenv("LDAP_CONFIG")
1418                 );
1419                 fclose(fp);
1420         }
1421
1422 }
1423 #endif  /* HAVE_LDAP */