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