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