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