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