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