]> code.citadel.org Git - citadel.git/blob - citadel/setup.c
*** empty log message ***
[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 prompt_window_height = 0;
242 #endif
243         int i = 0;
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' 7 72",
399                                 getenv("CTDL_DIALOG"),
400                                 text);
401                         fp = popen(buf, "w");
402                         if (fp != NULL) {
403                                 fprintf(fp, "0\n");
404                                 fflush(fp);
405                         }
406                 } 
407                 else if (curr == cmax) {
408                         if (fp != NULL) {
409                                 fprintf(fp, "100\n");
410                                 pclose(fp);
411                                 fp = NULL;
412                         }
413                 }
414                 else {
415                         a = (curr * 100) / cmax;
416                         if (fp != NULL) {
417                                 fprintf(fp, "%ld\n", a);
418                                 fflush(fp);
419                         }
420                 }
421                 break;
422
423 #ifdef HAVE_NEWT
424         case UI_NEWT:
425                 if (curr == 0) {
426                         newtCenteredWindow(76, 8, text);
427                         form = newtForm(NULL, NULL, 0);
428                         scale = newtScale(1, 3, 74, cmax);
429                         newtFormAddComponent(form, scale);
430                         newtDrawForm(form);
431                         newtRefresh();
432                 }
433                 if ((curr > 0) && (curr <= cmax)) {
434                         newtScaleSet(scale, curr);
435                         newtRefresh();
436                 }
437                 if (curr == cmax) {
438                         newtFormDestroy(form);  
439                         newtPopWindow();
440                         newtRefresh();
441                 }
442                 break;
443 #endif
444
445         }
446 }
447
448
449
450 /*
451  * check_services_entry()  -- Make sure "citadel" is in /etc/services
452  *
453  */
454 void check_services_entry(void)
455 {
456         int i;
457         FILE *sfp;
458
459         if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
460                 for (i=0; i<=3; ++i) {
461                         progress("Adding service entry...", i, 3);
462                         if (i == 0) {
463                                 sfp = fopen("/etc/services", "a");
464                                 if (sfp == NULL) {
465                                         display_error(strerror(errno));
466                                 } else {
467                                         fprintf(sfp, "%s                504/tcp\n",
468                                                 SERVICE_NAME);
469                                         fclose(sfp);
470                                 }
471                         }
472                         sleep(1);
473                 }
474         }
475 }
476
477
478 /*
479  * Generate a unique entry name for a new inittab entry
480  */
481 void generate_entry_name(char *entryname) {
482         char buf[SIZ];
483
484         snprintf(entryname, sizeof entryname, "c0");
485         do {
486                 ++entryname[1];
487                 if (entryname[1] > '9') {
488                         entryname[1] = 0;
489                         ++entryname[0];
490                         if (entryname[0] > 'z') {
491                                 display_error(
492                                    "Can't generate a unique entry name");
493                                 return;
494                         }
495                 }
496                 snprintf(buf, sizeof buf,
497                      "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
498         } while (system(buf) == 0);
499 }
500
501
502
503 /*
504  * check_inittab_entry()  -- Make sure "citadel" is in /etc/inittab
505  *
506  */
507 void check_inittab_entry(void)
508 {
509         FILE *infp;
510         char looking_for[SIZ];
511         char question[SIZ];
512         char entryname[5];
513
514         /* Determine the fully qualified path name of citserver */
515         snprintf(looking_for, sizeof looking_for, "%s/citserver", BBSDIR);
516         locate_init_entry(citserver_init_entry, looking_for);
517
518         /* If there's already an entry, then we have nothing left to do. */
519         if (strlen(citserver_init_entry) > 0) {
520                 return;
521         }
522
523         /* Otherwise, prompt the user to create an entry. */
524         snprintf(question, sizeof question,
525                 "Do you want this computer configured to start the Citadel\n"
526                 "service automatically?  (If you answer yes, an entry in\n"
527                 "/etc/inittab pointing to %s will be added.)\n",
528                 looking_for);
529         if (yesno(question) == 0)
530                 return;
531
532         /* Generate a unique entry name for /etc/inittab */
533         generate_entry_name(entryname);
534
535         /* Now write it out to /etc/inittab */
536         infp = fopen("/etc/inittab", "a");
537         if (infp == NULL) {
538                 display_error(strerror(errno));
539         } else {
540                 fprintf(infp, "# Start the Citadel server...\n");
541                 fprintf(infp, "%s:2345:respawn:%s -h%s -x3 -llocal4\n",
542                         entryname, looking_for, setup_directory);
543                 fclose(infp);
544                 strcpy(citserver_init_entry, entryname);
545         }
546 }
547
548
549 /*
550  * On systems which use xinetd, see if we can offer to install Citadel as
551  * the default telnet target.
552  */
553 void check_xinetd_entry(void) {
554         char *filename = "/etc/xinetd.d/telnet";
555         FILE *fp;
556         char buf[SIZ];
557         int already_citadel = 0;
558
559         fp = fopen(filename, "r+");
560         if (fp == NULL) return;         /* Not there.  Oh well... */
561
562         while (fgets(buf, sizeof buf, fp) != NULL) {
563                 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
564         }
565         fclose(fp);
566         if (already_citadel) return;    /* Already set up this way. */
567
568         /* Otherwise, prompt the user to create an entry. */
569         snprintf(buf, sizeof buf,
570                 "Setup can configure the \"xinetd\" service to automatically\n"
571                 "connect incoming telnet sessions to Citadel, bypassing the\n"
572                 "host system login: prompt.  Would you like to do this?\n"
573         );
574         if (yesno(buf) == 0)
575                 return;
576
577         fp = fopen(filename, "w");
578         fprintf(fp,
579                 "# description: telnet service for Citadel users\n"
580                 "service telnet\n"
581                 "{\n"
582                 "       disable = no\n"
583                 "       flags           = REUSE\n"
584                 "       socket_type     = stream\n"
585                 "       wait            = no\n"
586                 "       user            = root\n"
587                 "       server          = /usr/sbin/in.telnetd\n"
588                 "       server_args     = -h -L %s/citadel\n"
589                 "       log_on_failure  += USERID\n"
590                 "}\n",
591                 setup_directory
592         );
593         fclose(fp);
594
595         /* Now try to restart the service */
596         system("/etc/init.d/xinetd restart >/dev/null 2>&1");
597 }
598
599
600
601 /*
602  * Offer to disable other MTA's
603  */
604 void disable_other_mta(char *mta) {
605         char buf[SIZ];
606         FILE *fp;
607         int lines = 0;
608
609         sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null", mta);
610         fp = popen(buf, "r");
611         if (fp == NULL) return;
612
613         while (fgets(buf, sizeof buf, fp) != NULL) {
614                 ++lines;
615         }
616         fclose(fp);
617         if (lines == 0) return;         /* Nothing to do. */
618
619         /* Offer to replace other MTA with the vastly superior Citadel :)  */
620         snprintf(buf, sizeof buf,
621                 "You appear to have the \"%s\" email program\n"
622                 "running on your system.  Would you like to disable it,\n"
623                 "allowing Citadel to handle your Internet mail instead?\n",
624                 mta
625         );
626         if (yesno(buf) == 0)
627                 return;
628
629         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);
630         system(buf);
631         sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
632         system(buf);
633 }
634
635
636
637
638 /* 
639  * Check to see if our server really works.  Returns 0 on success.
640  */
641 int test_server(void) {
642         char cmd[256];
643         char cookie[256];
644         FILE *fp;
645         char buf[4096];
646         int found_it = 0;
647
648         /* Generate a silly little cookie.  We're going to write it out
649          * to the server and try to get it back.  The cookie does not
650          * have to be secret ... just unique.
651          */
652         sprintf(cookie, "%ld.%d", time(NULL), getpid());
653
654         sprintf(cmd, "%s/sendcommand -h%s ECHO %s 2>&1",
655                 setup_directory,
656                 setup_directory,
657                 cookie);
658
659         fp = popen(cmd, "r");
660         if (fp == NULL) return(errno);
661
662         while (fgets(buf, sizeof buf, fp) != NULL) {
663                 if ( (buf[0]=='2')
664                    && (strstr(buf, cookie) != NULL) ) {
665                         ++found_it;
666                 }
667         }
668         pclose(fp);
669
670         if (found_it) {
671                 return(0);
672         }
673         return(-1);
674 }
675
676 void strprompt(char *prompt_title, char *prompt_text, char *str)
677 {
678 #ifdef HAVE_NEWT
679         newtComponent form;
680         char *result;
681         int i;
682         int prompt_window_height = 0;
683 #endif
684         char buf[SIZ];
685         char setupmsg[SIZ];
686         char *dialog_result;
687         FILE *fp = NULL;
688
689         strcpy(setupmsg, "");
690
691         switch (setup_type) {
692         case UI_TEXT:
693                 title(prompt_title);
694                 printf("\n%s\n", prompt_text);
695                 printf("This is currently set to:\n%s\n", str);
696                 printf("Enter new value or press return to leave unchanged:\n");
697                 fgets(buf, sizeof buf, stdin);
698                 buf[strlen(buf) - 1] = 0;
699                 if (strlen(buf) != 0)
700                         strcpy(str, buf);
701                 break;
702
703         case UI_DIALOG:
704                 dialog_result = tmpnam(NULL);
705                 sprintf(buf, "%s --backtitle '%s' --inputbox '%s' 0 0 '%s' 2>%s",
706                         getenv("CTDL_DIALOG"),
707                         prompt_title,
708                         prompt_text,
709                         str,
710                         dialog_result);
711                 system(buf);
712                 fp = fopen(dialog_result, "r");
713                 if (fp != NULL) {
714                         fgets(str, sizeof buf, fp);
715                         if (str[strlen(str)-1] == 10) {
716                                 str[strlen(str)-1] = 0;
717                         }
718                         fclose(fp);
719                         unlink(dialog_result);
720                 }
721                 break;
722
723 #ifdef HAVE_NEWT
724         case UI_NEWT:
725
726                 prompt_window_height = num_tokens(prompt_text, '\n') + 5 ;
727                 newtCenteredWindow(76,
728                                 prompt_window_height,
729                                 prompt_title);
730                 form = newtForm(NULL, NULL, 0);
731                 for (i=0; i<num_tokens(prompt_text, '\n'); ++i) {
732                         extract_token(buf, prompt_text, i, '\n');
733                         newtFormAddComponent(form, newtLabel(1, 1+i, buf));
734                 }
735                 newtFormAddComponent(form,
736                         newtEntry(1,
737                                 (prompt_window_height - 2),
738                                 str,
739                                 74,
740                                 &result,
741                                 NEWT_FLAG_RETURNEXIT)
742                 );
743                 newtRunForm(form);
744                 strcpy(str, result);
745
746                 newtPopWindow();
747                 newtFormDestroy(form);  
748
749 #endif
750         }
751 }
752
753 void set_str_val(int msgpos, char *str) {
754         strprompt(setup_titles[msgpos], setup_text[msgpos], str);
755 }
756
757
758
759 void set_int_val(int msgpos, int *ip)
760 {
761         char buf[16];
762         snprintf(buf, sizeof buf, "%d", (int) *ip);
763         set_str_val(msgpos, buf);
764         *ip = atoi(buf);
765 }
766
767
768 void set_char_val(int msgpos, char *ip)
769 {
770         char buf[16];
771         snprintf(buf, sizeof buf, "%d", (int) *ip);
772         set_str_val(msgpos, buf);
773         *ip = (char) atoi(buf);
774 }
775
776
777 void set_long_val(int msgpos, long int *ip)
778 {
779         char buf[16];
780         snprintf(buf, sizeof buf, "%ld", *ip);
781         set_str_val(msgpos, buf);
782         *ip = atol(buf);
783 }
784
785
786 void edit_value(int curr)
787 {
788         int i;
789         struct passwd *pw;
790         char bbsuidname[SIZ];
791
792         switch (curr) {
793
794         case 1:
795                 set_str_val(curr, config.c_sysadm);
796                 break;
797
798         case 2:
799 #ifdef __CYGWIN__
800                 config.c_bbsuid = 0;    /* XXX Windows hack, prob. insecure */
801 #else
802                 i = config.c_bbsuid;
803                 pw = getpwuid(i);
804                 if (pw == NULL) {
805                         set_int_val(curr, &i);
806                         config.c_bbsuid = i;
807                 }
808                 else {
809                         strcpy(bbsuidname, pw->pw_name);
810                         set_str_val(curr, bbsuidname);
811                         pw = getpwnam(bbsuidname);
812                         if (pw != NULL) {
813                                 config.c_bbsuid = pw->pw_uid;
814                         }
815                         else if (atoi(bbsuidname) > 0) {
816                                 config.c_bbsuid = atoi(bbsuidname);
817                         }
818                 }
819 #endif
820                 break;
821
822         case 3:
823                 set_str_val(curr, config.c_ip_addr);
824                 break;
825
826         case 4:
827                 set_int_val(curr, &config.c_port_number);
828                 break;
829
830
831         }
832 }
833
834 /*
835  * (re-)write the config data to disk
836  */
837 void write_config_to_disk(void)
838 {
839         FILE *fp;
840         int fd;
841
842         if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
843                 display_error("setup: cannot open citadel.config");
844                 cleanup(1);
845         }
846         fp = fdopen(fd, "wb");
847         if (fp == NULL) {
848                 display_error("setup: cannot open citadel.config");
849                 cleanup(1);
850         }
851         fwrite((char *) &config, sizeof(struct config), 1, fp);
852         fclose(fp);
853 }
854
855
856
857
858 /*
859  * Figure out what type of user interface we're going to use
860  */
861 int discover_ui(void)
862 {
863
864         /* Use "dialog" if we have it */
865         if (getenv("CTDL_DIALOG") != NULL) {
866                 return UI_DIALOG;
867         }
868                 
869
870 #ifdef HAVE_NEWT
871         newtInit();
872         newtCls();
873         newtDrawRootText(0, 0, "Citadel Setup");
874         return UI_NEWT;
875 #endif
876         return UI_TEXT;
877 }
878
879
880
881
882
883 int main(int argc, char *argv[])
884 {
885         int a;
886         int curr;
887         char aaa[128];
888         FILE *fp;
889         int old_setup_level = 0;
890         int info_only = 0;
891         struct utsname my_utsname;
892         struct passwd *pw;
893         struct hostent *he;
894         gid_t gid;
895
896         /* set an invalid setup type */
897         setup_type = (-1);
898
899         /* Check to see if we're running the web installer */
900         if (getenv("CITADEL_INSTALLER") != NULL) {
901                 using_web_installer = 1;
902         }
903
904         /* parse command line args */
905         for (a = 0; a < argc; ++a) {
906                 if (!strncmp(argv[a], "-u", 2)) {
907                         strcpy(aaa, argv[a]);
908                         strcpy(aaa, &aaa[2]);
909                         setup_type = atoi(aaa);
910                 }
911                 if (!strcmp(argv[a], "-i")) {
912                         info_only = 1;
913                 }
914                 if (!strcmp(argv[a], "-q")) {
915                         setup_type = UI_SILENT;
916                 }
917         }
918
919
920         /* If a setup type was not specified, try to determine automatically
921          * the best one to use out of all available types.
922          */
923         if (setup_type < 0) {
924                 setup_type = discover_ui();
925         }
926         if (info_only == 1) {
927                 important_message("Citadel Setup", CITADEL);
928                 cleanup(0);
929         }
930
931         /* Get started in a valid setup directory. */
932         strcpy(setup_directory, BBSDIR);
933         if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
934                 strcpy(setup_directory, getenv("CITADEL"));
935         }
936         else {
937                 set_str_val(0, setup_directory);
938         }
939
940         if (chdir(setup_directory) != 0) {
941                 important_message("Citadel Setup",
942                           "The directory you specified does not exist.");
943                 cleanup(errno);
944         }
945
946         /* Determine our host name, in case we need to use it as a default */
947         uname(&my_utsname);
948
949         /* See if we need to shut down the Citadel service. */
950         for (a=0; a<=3; ++a) {
951                 progress("Shutting down the Citadel service...", a, 3);
952                 if (a == 0) shutdown_citserver();
953                 sleep(1);
954         }
955
956         /* Make sure it's stopped. */
957         if (test_server() == 0) {
958                 important_message("Citadel Setup",
959                         "The Citadel service is still running.\n"
960                         "Please stop the service manually and run "
961                         "setup again.");
962                 cleanup(1);
963         }
964
965         /* Now begin. */
966         switch (setup_type) {
967
968         case UI_TEXT:
969                 printf("\n\n\n"
970                         "               *** Citadel setup program ***\n\n");
971                 break;
972
973         }
974
975         /*
976          * What we're going to try to do here is append a whole bunch of
977          * nulls to the citadel.config file, so we can keep the old config
978          * values if they exist, but if the file is missing or from an
979          * earlier version with a shorter config structure, when setup tries
980          * to read the old config parameters, they'll all come up zero.
981          * The length of the config file will be set to what it's supposed
982          * to be when we rewrite it, because we replace the old file with a
983          * completely new copy.
984          */
985
986         if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
987                       S_IRUSR | S_IWUSR)) == -1) {
988                 display_error("setup: cannot append citadel.config");
989                 cleanup(errno);
990         }
991         fp = fdopen(a, "ab");
992         if (fp == NULL) {
993                 display_error("setup: cannot append citadel.config");
994                 cleanup(errno);
995         }
996         for (a = 0; a < sizeof(struct config); ++a)
997                 putc(0, fp);
998         fclose(fp);
999
1000         /* now we re-open it, and read the old or blank configuration */
1001         fp = fopen("citadel.config", "rb");
1002         if (fp == NULL) {
1003                 display_error("setup: cannot open citadel.config");
1004                 cleanup(errno);
1005         }
1006         fread((char *) &config, sizeof(struct config), 1, fp);
1007         fclose(fp);
1008
1009         /* set some sample/default values in place of blanks... */
1010         if (strlen(config.c_nodename) == 0)
1011                 safestrncpy(config.c_nodename, my_utsname.nodename,
1012                             sizeof config.c_nodename);
1013         strtok(config.c_nodename, ".");
1014         if (strlen(config.c_fqdn) == 0) {
1015                 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
1016                         safestrncpy(config.c_fqdn, he->h_name,
1017                                     sizeof config.c_fqdn);
1018                 else
1019                         safestrncpy(config.c_fqdn, my_utsname.nodename,
1020                                     sizeof config.c_fqdn);
1021         }
1022         if (strlen(config.c_humannode) == 0)
1023                 strcpy(config.c_humannode, "My System");
1024         if (strlen(config.c_phonenum) == 0)
1025                 strcpy(config.c_phonenum, "US 800 555 1212");
1026         if (config.c_initax == 0) {
1027                 config.c_initax = 4;
1028         }
1029         if (strlen(config.c_moreprompt) == 0)
1030                 strcpy(config.c_moreprompt, "<more>");
1031         if (strlen(config.c_twitroom) == 0)
1032                 strcpy(config.c_twitroom, "Trashcan");
1033         if (strlen(config.c_baseroom) == 0)
1034                 strcpy(config.c_baseroom, "Lobby");
1035         if (strlen(config.c_aideroom) == 0)
1036                 strcpy(config.c_aideroom, "Aide");
1037         if (config.c_port_number == 0) {
1038                 config.c_port_number = 504;
1039         }
1040         if (config.c_sleeping == 0) {
1041                 config.c_sleeping = 900;
1042         }
1043         if (config.c_bbsuid == 0) {
1044                 pw = getpwnam("citadel");
1045                 if (pw != NULL)
1046                         config.c_bbsuid = pw->pw_uid;
1047         }
1048         if (config.c_bbsuid == 0) {
1049                 pw = getpwnam("bbs");
1050                 if (pw != NULL)
1051                         config.c_bbsuid = pw->pw_uid;
1052         }
1053         if (config.c_bbsuid == 0) {
1054                 pw = getpwnam("guest");
1055                 if (pw != NULL)
1056                         config.c_bbsuid = pw->pw_uid;
1057         }
1058         if (config.c_createax == 0) {
1059                 config.c_createax = 3;
1060         }
1061         /*
1062          * Negative values for maxsessions are not allowed.
1063          */
1064         if (config.c_maxsessions < 0) {
1065                 config.c_maxsessions = 0;
1066         }
1067         /* We need a system default message expiry policy, because this is
1068          * the top level and there's no 'higher' policy to fall back on.
1069          */
1070         if (config.c_ep.expire_mode == 0) {
1071                 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
1072                 config.c_ep.expire_value = 150;
1073         }
1074
1075         /*
1076          * Default port numbers for various services
1077          */
1078         if (config.c_smtp_port == 0) config.c_smtp_port = 25;
1079         if (config.c_pop3_port == 0) config.c_pop3_port = 110;
1080         if (config.c_imap_port == 0) config.c_imap_port = 143;
1081
1082         /* Go through a series of dialogs prompting for config info */
1083         if (setup_type != UI_SILENT) {
1084                 for (curr = 1; curr <= MAXSETUP; ++curr) {
1085                         edit_value(curr);
1086                 }
1087         }
1088
1089         /*
1090            if (setuid(config.c_bbsuid) != 0) {
1091            important_message("Citadel Setup",
1092            "Failed to change the user ID to your Citadel user.");
1093            cleanup(errno);
1094            }
1095          */
1096
1097 /***** begin version update section ***** */
1098         /* take care of any updating that is necessary */
1099
1100         old_setup_level = config.c_setup_level;
1101
1102         if (old_setup_level == 0) {
1103                 goto NEW_INST;
1104         }
1105
1106         if (old_setup_level < 555) {
1107                 important_message("Citadel Setup",
1108                                   "This Citadel installation is too old "
1109                                   "to be upgraded.");
1110                 cleanup(1);
1111         }
1112         write_config_to_disk();
1113
1114         old_setup_level = config.c_setup_level;
1115
1116         /* end of version update section */
1117
1118 NEW_INST:
1119         config.c_setup_level = REV_LEVEL;
1120
1121 /******************************************/
1122
1123         write_config_to_disk();
1124
1125         mkdir("info", 0700);
1126         chmod("info", 0700);
1127         mkdir("bio", 0700);
1128         chmod("bio", 0700);
1129         mkdir("userpics", 0700);
1130         chmod("userpics", 0700);
1131         mkdir("messages", 0700);
1132         chmod("messages", 0700);
1133         mkdir("help", 0700);
1134         chmod("help", 0700);
1135         mkdir("images", 0700);
1136         chmod("images", 0700);
1137         mkdir("netconfigs", 0700);
1138         chmod("netconfigs", 0700);
1139
1140         /* Delete files and directories used by older Citadel versions */
1141         system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1142         unlink("citadel.log");
1143         unlink("weekly");
1144
1145         check_services_entry(); /* Check /etc/services */
1146 #ifndef __CYGWIN__
1147         check_inittab_entry();  /* Check /etc/inittab */
1148         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1149
1150         /* Offer to disable other MTA's on the system. */
1151         disable_other_mta("sendmail");
1152         disable_other_mta("postfix");
1153         disable_other_mta("qmail");
1154         disable_other_mta("cyrus");
1155         disable_other_mta("cyrmaster");
1156         disable_other_mta("saslauthd");
1157         disable_other_mta("mta");
1158         disable_other_mta("courier-imap");
1159         disable_other_mta("courier-imap-ssl");
1160         disable_other_mta("courier-authdaemon");
1161         disable_other_mta("courier-pop3");
1162         disable_other_mta("courier-pop3d");
1163         disable_other_mta("courier-pop");
1164         disable_other_mta("vmailmgrd");
1165         disable_other_mta("imapd");
1166         disable_other_mta("popd");
1167         disable_other_mta("pop3d");
1168         disable_other_mta("exim");
1169 #endif
1170
1171         if ((pw = getpwuid(config.c_bbsuid)) == NULL)
1172                 gid = getgid();
1173         else
1174                 gid = pw->pw_gid;
1175
1176         progress("Setting file permissions", 0, 4);
1177         chown(".", config.c_bbsuid, gid);
1178         sleep(1);
1179         progress("Setting file permissions", 1, 4);
1180         chown("citadel.config", config.c_bbsuid, gid);
1181         sleep(1);
1182         progress("Setting file permissions", 2, 4);
1183         snprintf(aaa, sizeof aaa,
1184                 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
1185                 (long)config.c_bbsuid, (long)gid);
1186         system(aaa);
1187         sleep(1);
1188         progress("Setting file permissions", 3, 4);
1189         chmod("citadel.config", S_IRUSR | S_IWUSR);
1190         sleep(1);
1191         progress("Setting file permissions", 4, 4);
1192
1193 #ifdef HAVE_LDAP
1194         /* Contemplate the possibility of auto-configuring OpenLDAP */
1195         contemplate_ldap();
1196 #endif
1197
1198         /* See if we can start the Citadel service. */
1199         if (strlen(citserver_init_entry) > 0) {
1200                 for (a=0; a<=3; ++a) {
1201                         progress("Starting the Citadel service...", a, 3);
1202                         if (a == 0) start_citserver();
1203                         sleep(1);
1204                 }
1205                 if (test_server() == 0) {
1206                         important_message("Setup finished",
1207                                 "Setup is finished.  You may now log in.");
1208                 }
1209                 else {
1210                         important_message("Setup finished",
1211                                 "Setup is finished, but the Citadel service "
1212                                 "failed to start.\n"
1213                                 "Go back and check your configuration.");
1214                 }
1215         }
1216         else {
1217                 important_message("Setup finished",
1218                         "Setup is finished.  You may now start the server.");
1219         }
1220
1221         cleanup(0);
1222         return 0;
1223 }
1224
1225
1226 #ifdef HAVE_LDAP
1227 /*
1228  * If we're in the middle of an Easy Install, we might just be able to
1229  * auto-configure a standalone OpenLDAP server.
1230  */
1231 void contemplate_ldap(void) {
1232         char question[SIZ];
1233         char slapd_init_entry[SIZ];
1234         FILE *fp;
1235
1236         /* If conditions are not ideal, give up on this idea. */
1237         if (using_web_installer == 0) return;
1238         if (getenv("LDAP_CONFIG") == NULL) return;
1239         if (getenv("SUPPORT") == NULL) return;
1240         if (getenv("SLAPD_BINARY") == NULL) return;
1241         if (getenv("CITADEL") == NULL) return;
1242
1243         /* Otherwise, prompt the user to create an entry. */
1244         snprintf(question, sizeof question,
1245                 "\n"
1246                 "Do you want this computer configured to start a standalone\n"
1247                 "LDAP service automatically?  (If you answer yes, a custom\n"
1248                 "slapd.conf will be written, and an /etc/inittab entry\n"
1249                 "pointing to %s will be added.)\n"
1250                 "\n",
1251                 getenv("SLAPD_BINARY")
1252         );
1253         if (yesno(question) == 0)
1254                 return;
1255
1256         strcpy(config.c_ldap_base_dn, "dc=example,dc=com");
1257         strprompt("Base DN",
1258                 "\n"
1259                 "Please enter the Base DN for your directory.  This will\n"
1260                 "generally be something based on the primary DNS domain in\n"
1261                 "which you receive mail, but it does not have to be.  Your\n"
1262                 "LDAP tree will be built using this Distinguished Name.\n"
1263                 "\n",
1264                 config.c_ldap_base_dn
1265         );
1266
1267         strcpy(config.c_ldap_host, "localhost");
1268         config.c_ldap_port = 389;
1269         sprintf(config.c_ldap_bind_dn, "cn=manager,%s", config.c_ldap_base_dn);
1270
1271         /* FIXME ... make the generated password harder to guess */
1272         sprintf(config.c_ldap_bind_pw, "%d%ld", getpid(), time(NULL));
1273
1274         write_config_to_disk();
1275
1276         fp = fopen(getenv("LDAP_CONFIG"), "w");
1277         if (fp == NULL) {
1278                 sprintf(question, "\nCannot create %s:\n%s\n\n"
1279                                 "Citadel will still function, but you will "
1280                                 "not have an LDAP service.\n\n",
1281                                 getenv("LDAP_CONFIG"),
1282                                 strerror(errno)
1283                 );
1284                 important_message("Error", question);
1285                 return;
1286         }
1287
1288         fprintf(fp, "include    %s/citadel-openldap.schema\n",
1289                 getenv("CITADEL"));
1290         fprintf(fp, "pidfile    %s/openldap-data/slapd.pid\n",
1291                 getenv("CITADEL"));
1292         fprintf(fp, "argsfile   %s/openldap-data/slapd.args\n",
1293                 getenv("CITADEL"));
1294         fprintf(fp,     "allow          bind_v2\n"
1295                         "database       bdb\n"
1296                         "schemacheck    off\n"
1297         );
1298         fprintf(fp,     "suffix         \"%s\"\n", config.c_ldap_base_dn);
1299         fprintf(fp,     "rootdn         \"%s\"\n", config.c_ldap_bind_dn);
1300         fprintf(fp,     "rootpw         %s\n", config.c_ldap_bind_pw);
1301         fprintf(fp,     "directory      %s/openldap-data\n",
1302                 getenv("CITADEL"));
1303         fprintf(fp,     "index          objectClass     eq\n");
1304
1305         fclose(fp);
1306
1307         /* This is where our OpenLDAP server will keep its data. */
1308         mkdir("openldap-data", 0700);
1309
1310         /* If inittab is already starting slapd, disable the old entry. */
1311         locate_init_entry(slapd_init_entry, getenv("SLAPD_BINARY"));
1312         if (strlen(slapd_init_entry) > 0) {
1313                 set_init_entry(slapd_init_entry, "off");
1314         }
1315
1316         /* Generate a unique entry name for slapd */
1317         generate_entry_name(slapd_init_entry);
1318
1319         /* Now write it out to /etc/inittab.
1320          * FIXME make it run as some non-root user.
1321          * The "-d 0" seems superfluous, but it's actually a way to make
1322          * slapd run in the foreground without spewing messages to the console.
1323          */
1324         fp = fopen("/etc/inittab", "a");
1325         if (fp == NULL) {
1326                 display_error(strerror(errno));
1327         } else {
1328                 fprintf(fp, "# Start the OpenLDAP server for Citadel...\n");
1329                 fprintf(fp, "%s:2345:respawn:%s -d 0 -f %s\n",
1330                         slapd_init_entry,
1331                         getenv("SLAPD_BINARY"),
1332                         getenv("LDAP_CONFIG")
1333                 );
1334                 fclose(fp);
1335         }
1336
1337 }
1338 #endif  /* HAVE_LDAP */