setup.c: when creating or changing permission of the run directories,
[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 -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         enable_home=(relh|home);
1049
1050         if ((home) && (chdir(setup_directory) != 0)) {
1051                 important_message("Citadel Setup",
1052                           "The directory you specified does not exist.");
1053                 cleanup(errno);
1054         }
1055
1056         /* Determine our host name, in case we need to use it as a default */
1057         uname(&my_utsname);
1058
1059         /* See if we need to shut down the Citadel service. */
1060         for (a=0; a<=3; ++a) {
1061                 progress("Shutting down the Citadel service...", a, 3);
1062                 if (a == 0) shutdown_citserver();
1063                 sleep(1);
1064         }
1065
1066         /* Make sure it's stopped. */
1067         if (test_server() == 0) {
1068                 important_message("Citadel Setup",
1069                         "The Citadel service is still running.\n"
1070                         "Please stop the service manually and run "
1071                         "setup again.");
1072                 cleanup(1);
1073         }
1074
1075         /* Now begin. */
1076         switch (setup_type) {
1077
1078         case UI_TEXT:
1079                 printf("\n\n\n"
1080                         "               *** Citadel setup program ***\n\n");
1081                 break;
1082
1083         }
1084
1085         /*
1086          * What we're going to try to do here is append a whole bunch of
1087          * nulls to the citadel.config file, so we can keep the old config
1088          * values if they exist, but if the file is missing or from an
1089          * earlier version with a shorter config structure, when setup tries
1090          * to read the old config parameters, they'll all come up zero.
1091          * The length of the config file will be set to what it's supposed
1092          * to be when we rewrite it, because we replace the old file with a
1093          * completely new copy.
1094          */
1095         if ((a = open(file_citadel_config, O_WRONLY | O_CREAT | O_APPEND,
1096                       S_IRUSR | S_IWUSR)) == -1) {
1097                 display_error("setup: cannot append citadel.config");
1098                 cleanup(errno);
1099         }
1100         fp = fdopen(a, "ab");
1101         if (fp == NULL) {
1102                 display_error("setup: cannot append citadel.config");
1103                 cleanup(errno);
1104         }
1105         for (a = 0; a < sizeof(struct config); ++a)
1106                 putc(0, fp);
1107         fclose(fp);
1108
1109         /* now we re-open it, and read the old or blank configuration */
1110         fp = fopen(file_citadel_config, "rb");
1111         if (fp == NULL) {
1112                 display_error("setup: cannot open citadel.config");
1113                 cleanup(errno);
1114         }
1115         fread((char *) &config, sizeof(struct config), 1, fp);
1116         fclose(fp);
1117
1118         /* set some sample/default values in place of blanks... */
1119         if (strlen(config.c_nodename) == 0)
1120                 safestrncpy(config.c_nodename, my_utsname.nodename,
1121                             sizeof config.c_nodename);
1122         strtok(config.c_nodename, ".");
1123         if (strlen(config.c_fqdn) == 0) {
1124                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1125                         safestrncpy(config.c_fqdn, he->h_name,
1126                                     sizeof config.c_fqdn);
1127                 else
1128                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1129                                     sizeof config.c_fqdn);
1130         }
1131         if (strlen(config.c_humannode) == 0)
1132                 strcpy(config.c_humannode, "My System");
1133         if (strlen(config.c_phonenum) == 0)
1134                 strcpy(config.c_phonenum, "US 800 555 1212");
1135         if (config.c_initax == 0) {
1136                 config.c_initax = 4;
1137         }
1138         if (strlen(config.c_moreprompt) == 0)
1139                 strcpy(config.c_moreprompt, "<more>");
1140         if (strlen(config.c_twitroom) == 0)
1141                 strcpy(config.c_twitroom, "Trashcan");
1142         if (strlen(config.c_baseroom) == 0)
1143                 strcpy(config.c_baseroom, BASEROOM);
1144         if (strlen(config.c_aideroom) == 0)
1145                 strcpy(config.c_aideroom, "Aide");
1146         if (config.c_port_number == 0) {
1147                 config.c_port_number = 504;
1148         }
1149         if (config.c_sleeping == 0) {
1150                 config.c_sleeping = 900;
1151         }
1152         if (config.c_ctdluid == 0) {
1153                 pw = getpwnam("citadel");
1154                 if (pw != NULL)
1155                         config.c_ctdluid = pw->pw_uid;
1156         }
1157         if (config.c_ctdluid == 0) {
1158                 pw = getpwnam("bbs");
1159                 if (pw != NULL)
1160                         config.c_ctdluid = pw->pw_uid;
1161         }
1162         if (config.c_ctdluid == 0) {
1163                 pw = getpwnam("guest");
1164                 if (pw != NULL)
1165                         config.c_ctdluid = pw->pw_uid;
1166         }
1167         if (config.c_createax == 0) {
1168                 config.c_createax = 3;
1169         }
1170         /*
1171          * Negative values for maxsessions are not allowed.
1172          */
1173         if (config.c_maxsessions < 0) {
1174                 config.c_maxsessions = 0;
1175         }
1176         /* We need a system default message expiry policy, because this is
1177          * the top level and there's no 'higher' policy to fall back on.
1178          * By default, do not expire messages at all.
1179          */
1180         if (config.c_ep.expire_mode == 0) {
1181                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1182                 config.c_ep.expire_value = 0;
1183         }
1184
1185         /*
1186          * Default port numbers for various services
1187          */
1188         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1189         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1190         if (config.c_imap_port == 0) config.c_imap_port = 143;
1191         if (config.c_msa_port == 0) config.c_msa_port = 587;
1192         if (config.c_smtps_port == 0) config.c_smtps_port = 465;
1193         if (config.c_pop3s_port == 0) config.c_pop3s_port = 995;
1194         if (config.c_imaps_port == 0) config.c_imaps_port = 993;
1195
1196         /* Go through a series of dialogs prompting for config info */
1197         if (setup_type != UI_SILENT) {
1198                 for (curr = 1; curr <= MAXSETUP; ++curr) {
1199                         edit_value(curr);
1200                 }
1201         }
1202
1203 /***** begin version update section ***** */
1204         /* take care of any updating that is necessary */
1205
1206         old_setup_level = config.c_setup_level;
1207
1208         if (old_setup_level == 0) {
1209                 goto NEW_INST;
1210         }
1211
1212         if (old_setup_level < 555) {
1213                 important_message("Citadel Setup",
1214                                   "This Citadel installation is too old "
1215                                   "to be upgraded.");
1216                 cleanup(1);
1217         }
1218         write_config_to_disk();
1219
1220         old_setup_level = config.c_setup_level;
1221
1222         /* end of version update section */
1223
1224 NEW_INST:
1225         config.c_setup_level = REV_LEVEL;
1226
1227 /******************************************/
1228
1229         write_config_to_disk();
1230
1231         mkdir(ctdl_info_dir, 0700);
1232         chmod(ctdl_info_dir, 0700);
1233         chown(ctdl_info_dir, config.c_ctdluid, -1);
1234
1235         mkdir(ctdl_bio_dir, 0700);
1236         chmod(ctdl_bio_dir, 0700);
1237         chown(ctdl_bio_dir, config.c_ctdluid, -1);
1238
1239         mkdir(ctdl_usrpic_dir, 0700);
1240         chmod(ctdl_usrpic_dir, 0700);
1241         chown(ctdl_usrpic_dir, config.c_ctdluid, -1);
1242
1243         mkdir(ctdl_message_dir, 0700);
1244         chmod(ctdl_message_dir, 0700);
1245         chown(ctdl_message_dir, config.c_ctdluid, -1);
1246
1247         mkdir(ctdl_hlp_dir, 0700);
1248         chmod(ctdl_hlp_dir, 0700);
1249         chown(ctdl_hlp_dir, config.c_ctdluid, -1);
1250
1251         mkdir(ctdl_image_dir, 0700);
1252         chmod(ctdl_image_dir, 0700);
1253         chown(ctdl_image_dir, config.c_ctdluid, -1);
1254
1255         /* TODO: where to put this? */
1256         mkdir("netconfigs", 0700);
1257         chmod("netconfigs", 0700);
1258         chown("netconfigs", config.c_ctdluid, -1);
1259
1260         /* Delete files and directories used by older Citadel versions */
1261         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1262         unlink("citadel.log");
1263         unlink("weekly");
1264
1265         check_services_entry(); /* Check /etc/services */
1266 #ifndef __CYGWIN__
1267         check_inittab_entry();  /* Check /etc/inittab */
1268         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1269
1270         /* Offer to disable other MTA's on the system. */
1271         disable_other_mta("courier-authdaemon");
1272         disable_other_mta("courier-imap");
1273         disable_other_mta("courier-imap-ssl");
1274         disable_other_mta("courier-pop");
1275         disable_other_mta("courier-pop3");
1276         disable_other_mta("courier-pop3d");
1277         disable_other_mta("cyrmaster");
1278         disable_other_mta("cyrus");
1279         disable_other_mta("dovecot");
1280         disable_other_mta("exim");
1281         disable_other_mta("exim4");
1282         disable_other_mta("hula");
1283         disable_other_mta("imapd");
1284         disable_other_mta("mta");
1285         disable_other_mta("pop3d");
1286         disable_other_mta("popd");
1287         disable_other_mta("postfix");
1288         disable_other_mta("qmail");
1289         disable_other_mta("saslauthd");
1290         disable_other_mta("sendmail");
1291         disable_other_mta("vmailmgrd");
1292         disable_other_mta("zimbra");
1293 #endif
1294
1295         if ((pw = getpwuid(config.c_ctdluid)) == NULL)
1296                 gid = getgid();
1297         else
1298                 gid = pw->pw_gid;
1299
1300         progress("Setting file permissions", 0, 4);
1301         chown(".", config.c_ctdluid, gid);
1302         sleep(1);
1303         progress("Setting file permissions", 1, 4);
1304         chown(file_citadel_config, config.c_ctdluid, gid);
1305         sleep(1);
1306         progress("Setting file permissions", 2, 4);
1307
1308         snprintf(aaa, sizeof aaa,
1309                          "%schkpwd",
1310                          ctdl_sbin_dir);
1311         chown(aaa,0,0); /*  config.c_ctdluid, gid); chkpwd needs to be root owned*/
1312         sleep(1);
1313         progress("Setting file permissions", 3, 4);
1314         chmod(aaa, 04755); 
1315
1316         sleep(1);
1317         progress("Setting file permissions", 3, 4);
1318         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1319         sleep(1);
1320         progress("Setting file permissions", 4, 4);
1321
1322 #ifdef HAVE_LDAP
1323         /* Contemplate the possibility of auto-configuring OpenLDAP */
1324         contemplate_ldap();
1325 #endif
1326
1327         /* See if we can start the Citadel service. */
1328         if (strlen(citserver_init_entry) > 0) {
1329                 for (a=0; a<=3; ++a) {
1330                         progress("Starting the Citadel service...", a, 3);
1331                         if (a == 0) start_citserver();
1332                         sleep(1);
1333                 }
1334                 if (test_server() == 0) {
1335                         important_message("Setup finished",
1336                                 "Setup of the Citadel server is complete.\n"
1337                                 "If you will be using WebCit, please run its\n"
1338                                 "setup program now; otherwise, run './citadel'\n"
1339                                 "to log in.\n");
1340                 }
1341                 else {
1342                         important_message("Setup finished",
1343                                 "Setup is finished, but the Citadel service "
1344                                 "failed to start.\n"
1345                                 "Go back and check your configuration.");
1346                 }
1347         }
1348         else {
1349                 important_message("Setup finished",
1350                         "Setup is finished.  You may now start the server.");
1351         }
1352
1353         cleanup(0);
1354         return 0;
1355 }
1356
1357
1358 #ifdef HAVE_LDAP
1359 /*
1360  * If we're in the middle of an Easy Install, we might just be able to
1361  * auto-configure a standalone OpenLDAP server.
1362  */
1363 void contemplate_ldap(void) {
1364         char question[SIZ];
1365         char slapd_init_entry[SIZ];
1366         FILE *fp;
1367
1368         /* If conditions are not ideal, give up on this idea... */
1369         if (using_web_installer == 0) return;
1370         if (getenv("LDAP_CONFIG") == NULL) return;
1371         if (getenv("SUPPORT") == NULL) return;
1372         if (getenv("SLAPD_BINARY") == NULL) return;
1373         if (getenv("CITADEL") == NULL) return;
1374
1375         /* And if inittab is already starting slapd, bail out... */
1376         locate_init_entry(slapd_init_entry, getenv("SLAPD_BINARY"));
1377         if (strlen(slapd_init_entry) > 0) {
1378                 important_message("Citadel Setup",
1379                         "You appear to already have a standalone LDAP "
1380                         "service\nconfigured for use with Citadel.  No "
1381                         "changes will be made.\n");
1382                 /* set_init_entry(slapd_init_entry, "off"); */
1383                 return;
1384         }
1385
1386         /* Generate a unique entry name for slapd if we don't have one. */
1387         else {
1388                 generate_entry_name(slapd_init_entry);
1389         }
1390
1391         /* Ask the user if it's ok to set up slapd automatically. */
1392         snprintf(question, sizeof question,
1393                 "\n"
1394                 "Do you want this computer configured to start a standalone\n"
1395                 "LDAP service automatically?  (If you answer yes, a new\n"
1396                 "slapd.conf will be written, and an /etc/inittab entry\n"
1397                 "pointing to %s will be added.)\n"
1398                 "\n",
1399                 getenv("SLAPD_BINARY")
1400         );
1401         if (yesno(question) == 0)
1402                 return;
1403
1404         strcpy(config.c_ldap_base_dn, "dc=example,dc=com");
1405         strprompt("Base DN",
1406                 "\n"
1407                 "Please enter the Base DN for your directory.  This will\n"
1408                 "generally be something based on the primary DNS domain in\n"
1409                 "which you receive mail, but it does not have to be.  Your\n"
1410                 "LDAP tree will be built using this Distinguished Name.\n"
1411                 "\n",
1412                 config.c_ldap_base_dn
1413         );
1414
1415         strcpy(config.c_ldap_host, "localhost");
1416         config.c_ldap_port = 389;
1417         sprintf(config.c_ldap_bind_dn, "cn=manager,%s", config.c_ldap_base_dn);
1418
1419         /*
1420          * Generate a bind password.  If you're some grey hat hacker who
1421          * is just dying to get some street cred on Bugtraq, and you think
1422          * this password generation scheme is too weak, please submit a patch
1423          * instead of just whining about it, ok?
1424          */
1425         sprintf(config.c_ldap_bind_pw, "%d%ld", getpid(), (long)time(NULL));
1426
1427         write_config_to_disk();
1428
1429         fp = fopen(getenv("LDAP_CONFIG"), "w");
1430         if (fp == NULL) {
1431                 sprintf(question, "\nCannot create %s:\n%s\n\n"
1432                                 "Citadel will still function, but you will "
1433                                 "not have an LDAP service.\n\n",
1434                                 getenv("LDAP_CONFIG"),
1435                                 strerror(errno)
1436                 );
1437                 important_message("Error", question);
1438                 return;
1439         }
1440
1441         fprintf(fp, "include    %s/citadel-openldap.schema\n",
1442                 getenv("CITADEL"));
1443         fprintf(fp, "pidfile    %s/openldap-data/slapd.pid\n",
1444                 getenv("CITADEL"));
1445         fprintf(fp, "argsfile   %s/openldap-data/slapd.args\n",
1446                 getenv("CITADEL"));
1447         fprintf(fp,     "allow          bind_v2\n"
1448                         "database       bdb\n"
1449                         "schemacheck    off\n"
1450         );
1451         fprintf(fp,     "suffix         \"%s\"\n", config.c_ldap_base_dn);
1452         fprintf(fp,     "rootdn         \"%s\"\n", config.c_ldap_bind_dn);
1453         fprintf(fp,     "rootpw         %s\n", config.c_ldap_bind_pw);
1454         fprintf(fp,     "directory      %s/openldap-data\n",
1455                 getenv("CITADEL"));
1456         fprintf(fp,     "index          objectClass     eq\n");
1457
1458         fclose(fp);
1459
1460         /* This is where our OpenLDAP server will keep its data. */
1461         mkdir("openldap-data", 0700);
1462
1463         /* Now write it out to /etc/inittab.
1464          * FIXME make it run as some non-root user.
1465          * The "-d 0" seems superfluous, but it's actually a way to make
1466          * slapd run in the foreground without spewing messages to the console.
1467          */
1468         fp = fopen("/etc/inittab", "a");
1469         if (fp == NULL) {
1470                 display_error(strerror(errno));
1471         } else {
1472                 fprintf(fp, "# Start the OpenLDAP server for Citadel...\n");
1473                 fprintf(fp, "%s:2345:respawn:%s -d 0 -f %s\n",
1474                         slapd_init_entry,
1475                         getenv("SLAPD_BINARY"),
1476                         getenv("LDAP_CONFIG")
1477                 );
1478                 fclose(fp);
1479         }
1480
1481 }
1482 #endif  /* HAVE_LDAP */