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