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