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