4 * Citadel setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
35 #define MAXSETUP 4 /* How many setup questions to ask */
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 */
41 #define SERVICE_NAME "citadel"
42 #define PROTO_NAME "tcp"
45 char setup_directory[SIZ];
47 int using_web_installer = 0;
49 char *setup_titles[] =
51 "Citadel Home Directory",
52 "System Administrator",
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"
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",
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",
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",
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",
87 "Setup has detected that you currently have data files from a Citadel\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"
99 * Set an entry in inittab to the desired state
101 void set_init_entry(char *which_entry, char *new_state) {
102 char *inittab = NULL;
110 inittab = strdup("");
111 if (inittab == NULL) return;
113 fp = fopen("/etc/inittab", "r");
114 if (fp == NULL) return;
116 while(fgets(buf, sizeof buf, fp) != NULL) {
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 */
124 if (!strcmp(entry, which_entry)) {
125 strcpy(state, new_state);
126 sprintf(buf, "%s:%s:%s:%s",
127 entry, levels, state, prog);
131 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
132 if (inittab == NULL) {
137 strcat(inittab, buf);
140 fp = fopen("/etc/inittab", "w");
142 fwrite(inittab, strlen(inittab), 1, fp);
144 kill(1, SIGHUP); /* Tell init to re-read /etc/inittab */
153 * Shut down the Citadel service if necessary, during setup.
155 void shutdown_service(void) {
158 char looking_for[SIZ];
163 strcpy(init_entry, "");
165 /* Determine the fully qualified path name of citserver */
166 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
168 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
169 * an entry is found which we believe starts citserver.
171 infp = fopen("/etc/inittab", "r");
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))) {
182 strcpy(init_entry, entry);
188 /* Bail out if there's nothing to do. */
189 if (!have_entry) return;
191 set_init_entry(init_entry, "off");
196 * Start the Citadel service.
198 void start_the_service(void) {
199 if (strlen(init_entry) > 0) {
200 set_init_entry(init_entry, "respawn");
206 void cleanup(int exitcode)
218 void title(char *text)
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);
227 int yesno(char *question)
230 newtComponent form = NULL;
231 newtComponent yesbutton = NULL;
232 newtComponent nobutton = NULL;
238 switch (setup_type) {
242 printf("%s\nYes/No --> ", question);
243 fgets(buf, sizeof buf, stdin);
244 answer = tolower(buf[0]);
247 else if (answer == 'n')
249 } while ((answer < 0) || (answer > 1));
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));
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) {
271 newtFormDestroy(form);
281 void important_message(char *title, char *msgtext)
284 newtComponent form = NULL;
289 switch (setup_type) {
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);
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));
306 newtFormAddComponent(form, newtButton(35, 5, "OK"));
309 newtFormDestroy(form);
316 void important_msgnum(int msgnum)
318 important_message("Important Message", setup_text[msgnum]);
321 void display_error(char *error_message)
323 important_message("Error", error_message);
326 void progress(char *text, long int curr, long int cmax)
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
335 static newtComponent form = NULL;
336 static newtComponent scale = NULL;
338 static long dots_printed = 0L;
341 switch (setup_type) {
345 printf("%s\n", text);
346 printf("..........................");
347 printf("..........................");
348 printf("..........................\r");
351 } else if (curr == cmax) {
352 printf("\r%79s\n", "");
354 a = (curr * 100) / cmax;
357 while (dots_printed < a) {
368 newtCenteredWindow(76, 8, text);
369 form = newtForm(NULL, NULL, 0);
370 scale = newtScale(1, 3, 74, cmax);
371 newtFormAddComponent(form, scale);
375 if ((curr > 0) && (curr <= cmax)) {
376 newtScaleSet(scale, curr);
380 newtFormDestroy(form);
393 * check_services_entry() -- Make sure "citadel" is in /etc/services
396 void check_services_entry(void)
401 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
402 for (i=0; i<3; ++i) {
403 progress("Adding service entry...", i, 3);
405 sfp = fopen("/etc/services", "a");
407 display_error(strerror(errno));
409 fprintf(sfp, "%s 504/tcp\n",
421 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
424 void check_inittab_entry(void)
428 char looking_for[SIZ];
432 /* Determine the fully qualified path name of citserver */
433 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
435 /* If there's already an entry, then we have nothing left to do. */
436 if (strlen(init_entry) > 0) {
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?",
445 if (yesno(question) == 0)
448 /* Generate a unique entry name for /etc/inittab */
449 snprintf(entryname, sizeof entryname, "c0");
452 if (entryname[1] > '9') {
455 if (entryname[0] > 'z') {
457 "Can't generate a unique entry name");
461 snprintf(buf, sizeof buf,
462 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
463 } while (system(buf) == 0);
465 /* Now write it out to /etc/inittab */
466 infp = fopen("/etc/inittab", "a");
468 display_error(strerror(errno));
470 fprintf(infp, "# Start the Citadel server...\n");
471 fprintf(infp, "%s:2345:respawn:%s -h%s -x3 -llocal4\n",
472 entryname, looking_for, setup_directory);
474 strcpy(init_entry, entryname);
480 * On systems which use xinetd, see if we can offer to install Citadel as
481 * the default telnet target.
483 void check_xinetd_entry(void) {
484 char *filename = "/etc/xinetd.d/telnet";
487 int already_citadel = 0;
489 fp = fopen(filename, "r+");
490 if (fp == NULL) return; /* Not there. Oh well... */
492 while (fgets(buf, sizeof buf, fp) != NULL) {
493 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
496 if (already_citadel) return; /* Already set up this way. */
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"
507 fp = fopen(filename, "w");
509 "# description: telnet service for Citadel users\n"
514 " socket_type = stream\n"
517 " server = /usr/sbin/in.telnetd\n"
518 " server_args = -h -L %s/citadel\n"
519 " log_on_failure += USERID\n"
525 /* Now try to restart the service */
526 system("/etc/init.d/xinetd restart >/dev/null 2>&1");
532 * Offer to disable other MTA's
534 void disable_other_mta(char *mta) {
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;
543 while (fgets(buf, sizeof buf, fp) != NULL) {
547 if (lines == 0) return; /* Nothing to do. */
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"
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);
562 sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
570 * Check to see if our server really works. Returns 0 on success.
572 int test_server(void) {
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.
583 sprintf(cookie, "%ld.%d", time(NULL), getpid());
585 sprintf(cmd, "%s/sendcommand -h%s ECHO %s 2>&1",
590 fp = popen(cmd, "r");
591 if (fp == NULL) return(errno);
593 while (fgets(buf, sizeof buf, fp) != NULL) {
595 && (strstr(buf, cookie) != NULL) ) {
612 void set_str_val(int msgpos, char str[])
622 strcpy(setupmsg, "");
624 switch (setup_type) {
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)
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));
644 newtFormAddComponent(form, newtEntry(1, 8, str, 74, &result,
645 NEWT_FLAG_RETURNEXIT));
650 newtFormDestroy(form);
656 void set_int_val(int msgpos, int *ip)
659 snprintf(buf, sizeof buf, "%d", (int) *ip);
660 set_str_val(msgpos, buf);
665 void set_char_val(int msgpos, char *ip)
668 snprintf(buf, sizeof buf, "%d", (int) *ip);
669 set_str_val(msgpos, buf);
670 *ip = (char) atoi(buf);
674 void set_long_val(int msgpos, long int *ip)
677 snprintf(buf, sizeof buf, "%ld", *ip);
678 set_str_val(msgpos, buf);
683 void edit_value(int curr)
687 char bbsuidname[SIZ];
692 set_str_val(curr, config.c_sysadm);
697 config.c_bbsuid = 0; /* XXX Windows hack, prob. insecure */
702 set_int_val(curr, &i);
706 strcpy(bbsuidname, pw->pw_name);
707 set_str_val(curr, bbsuidname);
708 pw = getpwnam(bbsuidname);
710 config.c_bbsuid = pw->pw_uid;
712 else if (atoi(bbsuidname) > 0) {
713 config.c_bbsuid = atoi(bbsuidname);
720 set_str_val(curr, &config.c_ip_addr);
724 set_int_val(curr, &config.c_port_number);
732 * (re-)write the config data to disk
734 void write_config_to_disk(void)
739 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
740 display_error("setup: cannot open citadel.config");
743 fp = fdopen(fd, "wb");
745 display_error("setup: cannot open citadel.config");
748 fwrite((char *) &config, sizeof(struct config), 1, fp);
756 * Figure out what type of user interface we're going to use
758 int discover_ui(void)
764 newtDrawRootText(0, 0, "Citadel Setup");
774 int main(int argc, char *argv[])
780 int old_setup_level = 0;
782 struct utsname my_utsname;
787 /* set an invalid setup type */
790 /* Check to see if we're running the web installer */
791 if (getenv("CITADEL_INSTALLER") != NULL) {
792 using_web_installer = 1;
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);
802 if (!strcmp(argv[a], "-i")) {
805 if (!strcmp(argv[a], "-q")) {
806 setup_type = UI_SILENT;
811 /* If a setup type was not specified, try to determine automatically
812 * the best one to use out of all available types.
814 if (setup_type < 0) {
815 setup_type = discover_ui();
817 if (info_only == 1) {
818 important_message("Citadel Setup", CITADEL);
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"));
828 set_str_val(0, setup_directory);
831 if (chdir(setup_directory) != 0) {
832 important_message("Citadel Setup",
833 "The directory you specified does not exist.");
837 /* Determine our host name, in case we need to use it as a default */
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();
847 /* Make sure it's stopped. */
848 if (test_server() == 0) {
849 important_message("Citadel Setup",
850 "The Citadel service is still running.\n"
851 "Please stop the service manually and run "
857 switch (setup_type) {
861 " *** Citadel setup program ***\n\n");
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.
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");
882 fp = fdopen(a, "ab");
884 display_error("setup: cannot append citadel.config");
887 for (a = 0; a < sizeof(struct config); ++a)
891 /* now we re-open it, and read the old or blank configuration */
892 fp = fopen("citadel.config", "rb");
894 display_error("setup: cannot open citadel.config");
897 fread((char *) &config, sizeof(struct config), 1, fp);
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);
910 safestrncpy(config.c_fqdn, my_utsname.nodename,
911 sizeof config.c_fqdn);
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) {
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;
931 if (config.c_sleeping == 0) {
932 config.c_sleeping = 900;
934 if (config.c_bbsuid == 0) {
935 pw = getpwnam("citadel");
937 config.c_bbsuid = pw->pw_uid;
939 if (config.c_bbsuid == 0) {
940 pw = getpwnam("bbs");
942 config.c_bbsuid = pw->pw_uid;
944 if (config.c_bbsuid == 0) {
945 pw = getpwnam("guest");
947 config.c_bbsuid = pw->pw_uid;
949 if (config.c_createax == 0) {
950 config.c_createax = 3;
953 * Negative values for maxsessions are not allowed.
955 if (config.c_maxsessions < 0) {
956 config.c_maxsessions = 0;
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.
961 if (config.c_ep.expire_mode == 0) {
962 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
963 config.c_ep.expire_value = 150;
967 * Default port numbers for various services
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;
973 /* Go through a series of dialogs prompting for config info */
974 if (setup_type != UI_SILENT) {
975 for (curr = 1; curr <= MAXSETUP; ++curr) {
981 if (setuid(config.c_bbsuid) != 0) {
982 important_message("Citadel Setup",
983 "Failed to change the user ID to your Citadel user.");
988 /***** begin version update section ***** */
989 /* take care of any updating that is necessary */
991 old_setup_level = config.c_setup_level;
993 if (old_setup_level == 0) {
997 if (old_setup_level < 555) {
998 important_message("Citadel Setup",
999 "This Citadel installation is too old "
1003 write_config_to_disk();
1005 old_setup_level = config.c_setup_level;
1007 /* end of version update section */
1010 config.c_setup_level = REV_LEVEL;
1012 /******************************************/
1014 write_config_to_disk();
1016 mkdir("info", 0700);
1017 chmod("info", 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);
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");
1036 check_services_entry(); /* Check /etc/services */
1038 check_inittab_entry(); /* Check /etc/inittab */
1039 check_xinetd_entry(); /* Check /etc/xinetd.d/telnet */
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");
1062 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
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);
1076 progress("Setting file permissions", 3, 4);
1077 chmod("citadel.config", S_IRUSR | S_IWUSR);
1078 progress("Setting file permissions", 4, 4);
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();
1087 if (test_server() == 0) {
1088 important_message("Setup finished",
1089 "Setup is finished. You may now log in.");
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.");
1099 important_message("Setup finished",
1100 "Setup is finished. You may now start the server.");