4 * Citadel/UX setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
35 #define MAXSETUP 3 /* 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",
60 "Enter the full pathname of the directory in which the Citadel installation\n"
61 "you are creating or updating resides. If you specify a directory other\n"
62 "than the default, you will need to specify the -h flag to the server when\n"
65 "Enter the name of the system administrator (which is probably you).\n"
66 "When an account is created with this name, it will automatically be\n"
67 "assigned the highest access level.\n",
69 "Citadel needs to run under its own user ID. This would typically be\n"
70 "called \"citadel\", but if you are running Citadel as a public BBS, you\n"
71 "might also call it \"bbs\" or \"guest\". The server will run under this\n"
72 "user ID. Please specify that user ID here. You may specify either a\n"
73 "user name or a numeric UID.\n",
75 "Specify the TCP port number on which your server will run. Normally, this\n"
76 "will be port 504, which is the official port assigned by the IANA for\n"
77 "Citadel servers. You'll only need to specify a different port number if\n"
78 "you run multiple instances of Citadel on the same computer and there's\n"
79 "something else already using port 504.\n",
81 "Setup has detected that you currently have data files from a Citadel/UX\n"
82 "version 3.2x installation. The program 'conv_32_40' can upgrade your\n"
83 "files to version 4.0x format.\n"
84 " Setup will now exit. Please either run 'conv_32_40' or delete your data\n"
85 "files, and run setup again.\n"
93 * Set an entry in inittab to the desired state
95 void set_init_entry(char *which_entry, char *new_state) {
104 inittab = strdup("");
105 if (inittab == NULL) return;
107 fp = fopen("/etc/inittab", "r");
108 if (fp == NULL) return;
110 while(fgets(buf, sizeof buf, fp) != NULL) {
112 if (num_tokens(buf, ':') == 4) {
113 extract_token(entry, buf, 0, ':');
114 extract_token(levels, buf, 1, ':');
115 extract_token(state, buf, 2, ':');
116 extract_token(prog, buf, 3, ':'); /* includes 0x0a LF */
118 if (!strcmp(entry, which_entry)) {
119 strcpy(state, new_state);
120 sprintf(buf, "%s:%s:%s:%s",
121 entry, levels, state, prog);
125 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
126 if (inittab == NULL) {
131 strcat(inittab, buf);
134 fp = fopen("/etc/inittab", "w");
136 fwrite(inittab, strlen(inittab), 1, fp);
138 kill(1, SIGHUP); /* Tell init to re-read /etc/inittab */
147 * Shut down the Citadel service if necessary, during setup.
149 void shutdown_service(void) {
152 char looking_for[SIZ];
157 strcpy(init_entry, "");
159 /* Determine the fully qualified path name of citserver */
160 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
162 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
163 * an entry is found which we believe starts citserver.
165 infp = fopen("/etc/inittab", "r");
169 while (fgets(buf, sizeof buf, infp) != NULL) {
170 buf[strlen(buf) - 1] = 0;
171 extract_token(entry, buf, 0, ':');
172 extract_token(prog, buf, 3, ':');
173 if (!strncasecmp(prog, looking_for,
174 strlen(looking_for))) {
176 strcpy(init_entry, entry);
182 /* Bail out if there's nothing to do. */
183 if (!have_entry) return;
185 set_init_entry(init_entry, "off");
190 * Start the Citadel service.
192 void start_the_service(void) {
193 if (strlen(init_entry) > 0) {
194 set_init_entry(init_entry, "respawn");
200 void cleanup(int exitcode)
212 void title(char *text)
214 if (setup_type == UI_TEXT) {
215 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
221 int yesno(char *question)
224 newtComponent form = NULL;
225 newtComponent yesbutton = NULL;
226 newtComponent nobutton = NULL;
232 switch (setup_type) {
236 printf("%s\nYes/No --> ", question);
237 fgets(buf, sizeof buf, stdin);
238 answer = tolower(buf[0]);
241 else if (answer == 'n')
243 } while ((answer < 0) || (answer > 1));
248 newtCenteredWindow(76, 10, "Question");
249 form = newtForm(NULL, NULL, 0);
250 for (i=0; i<num_tokens(question, '\n'); ++i) {
251 extract_token(buf, question, i, '\n');
252 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
254 yesbutton = newtButton(10, 5, "Yes");
255 nobutton = newtButton(60, 5, "No");
256 newtFormAddComponent(form, yesbutton);
257 newtFormAddComponent(form, nobutton);
258 if (newtRunForm(form) == yesbutton) {
265 newtFormDestroy(form);
275 void important_message(char *title, char *msgtext)
278 newtComponent form = NULL;
283 switch (setup_type) {
286 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");
287 printf(" %s \n\n%s\n\n", title, msgtext);
288 printf("Press return to continue...");
289 fgets(buf, sizeof buf, stdin);
294 newtCenteredWindow(76, 10, title);
295 form = newtForm(NULL, NULL, 0);
296 for (i=0; i<num_tokens(msgtext, '\n'); ++i) {
297 extract_token(buf, msgtext, i, '\n');
298 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
300 newtFormAddComponent(form, newtButton(35, 5, "OK"));
303 newtFormDestroy(form);
310 void important_msgnum(int msgnum)
312 important_message("Important Message", setup_text[msgnum]);
315 void display_error(char *error_message)
317 important_message("Error", error_message);
320 void progress(char *text, long int curr, long int cmax)
324 /* These variables are static because progress() gets called
325 * multiple times during the course of whatever operation is
326 * being performed. This makes setup non-threadsafe, but who
329 static newtComponent form = NULL;
330 static newtComponent scale = NULL;
332 static long dots_printed = 0L;
335 switch (setup_type) {
339 printf("%s\n", text);
340 printf("..........................");
341 printf("..........................");
342 printf("..........................\r");
345 } else if (curr == cmax) {
346 printf("\r%79s\n", "");
348 a = (curr * 100) / cmax;
351 while (dots_printed < a) {
362 newtCenteredWindow(76, 8, text);
363 form = newtForm(NULL, NULL, 0);
364 scale = newtScale(1, 3, 74, cmax);
365 newtFormAddComponent(form, scale);
369 if ((curr > 0) && (curr <= cmax)) {
370 newtScaleSet(scale, curr);
374 newtFormDestroy(form);
387 * check_services_entry() -- Make sure "citadel" is in /etc/services
390 void check_services_entry(void)
395 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
396 for (i=0; i<3; ++i) {
397 progress("Adding service entry...", i, 3);
399 sfp = fopen("/etc/services", "a");
401 display_error(strerror(errno));
403 fprintf(sfp, "%s 504/tcp\n",
415 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
418 void check_inittab_entry(void)
422 char looking_for[SIZ];
426 /* Determine the fully qualified path name of citserver */
427 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
429 /* If there's already an entry, then we have nothing left to do. */
430 if (strlen(init_entry) > 0) {
434 /* Otherwise, prompt the user to create an entry. */
435 snprintf(question, sizeof question,
436 "There is no '%s' entry in /etc/inittab.\n"
437 "Would you like to add one?",
439 if (yesno(question) == 0)
442 /* Generate a unique entry name for /etc/inittab */
443 snprintf(entryname, sizeof entryname, "c0");
446 if (entryname[1] > '9') {
449 if (entryname[0] > 'z') {
451 "Can't generate a unique entry name");
455 snprintf(buf, sizeof buf,
456 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
457 } while (system(buf) == 0);
459 /* Now write it out to /etc/inittab */
460 infp = fopen("/etc/inittab", "a");
462 display_error(strerror(errno));
464 fprintf(infp, "# Start the Citadel/UX server...\n");
465 fprintf(infp, "%s:2345:respawn:%s -h%s -x3 -llocal4\n",
466 entryname, looking_for, setup_directory);
468 strcpy(init_entry, entryname);
474 * On systems which use xinetd, see if we can offer to install Citadel as
475 * the default telnet target.
477 void check_xinetd_entry(void) {
478 char *filename = "/etc/xinetd.d/telnet";
481 int already_citadel = 0;
483 fp = fopen(filename, "r+");
484 if (fp == NULL) return; /* Not there. Oh well... */
486 while (fgets(buf, sizeof buf, fp) != NULL) {
487 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
490 if (already_citadel) return; /* Already set up this way. */
492 /* Otherwise, prompt the user to create an entry. */
493 snprintf(buf, sizeof buf,
494 "Setup can configure the 'xinetd' service to automatically\n"
495 "connect incoming telnet sessions to Citadel, bypassing the\n"
496 "host system's login prompt. Would you like to do this?\n"
501 fp = fopen(filename, "w");
503 "# description: telnet service for Citadel users\n"
508 " socket_type = stream\n"
511 " server = /usr/sbin/in.telnetd\n"
512 " server_args = -h -L %s/citadel\n"
513 " log_on_failure += USERID\n"
519 /* Now try to restart the service */
520 system("/etc/init.d/xinetd restart >/dev/null 2>&1");
526 * Offer to disable other MTA's
528 void disable_other_mta(char *mta) {
533 sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null", mta);
534 fp = popen(buf, "r");
535 if (fp == NULL) return;
537 while (fgets(buf, sizeof buf, fp) != NULL) {
541 if (lines == 0) return; /* Nothing to do. */
543 /* Offer to replace other MTA with the vastly superior Citadel :) */
544 snprintf(buf, sizeof buf,
545 "You appear to have the '%s' email program\n"
546 "running on your system. Would you like to disable it,\n"
547 "allowing Citadel to handle your system's Internet mail\n"
554 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);
556 sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
564 * Check to see if our server really works. Returns 0 on success.
566 int test_server(void) {
573 /* Generate a silly little cookie. We're going to write it out
574 * to the server and try to get it back. The cookie does not
575 * have to be secret ... just unique.
577 sprintf(cookie, "%ld.%d", time(NULL), getpid());
579 sprintf(cmd, "%s/sendcommand -h%s ECHO %s 2>&1",
584 fp = popen(cmd, "r");
585 if (fp == NULL) return(errno);
587 while (fgets(buf, sizeof buf, fp) != NULL) {
589 && (strstr(buf, cookie) != NULL) ) {
606 void set_str_val(int msgpos, char str[])
616 strcpy(setupmsg, "");
618 switch (setup_type) {
620 title(setup_titles[msgpos]);
621 printf("\n%s\n", setup_text[msgpos]);
622 printf("This is currently set to:\n%s\n", str);
623 printf("Enter new value or press return to leave unchanged:\n");
624 fgets(buf, sizeof buf, stdin);
625 buf[strlen(buf) - 1] = 0;
626 if (strlen(buf) != 0)
632 newtCenteredWindow(76, 10, setup_titles[msgpos]);
633 form = newtForm(NULL, NULL, 0);
634 for (i=0; i<num_tokens(setup_text[msgpos], '\n'); ++i) {
635 extract_token(buf, setup_text[msgpos], i, '\n');
636 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
638 newtFormAddComponent(form, newtEntry(1, 8, str, 74, &result,
639 NEWT_FLAG_RETURNEXIT));
644 newtFormDestroy(form);
650 void set_int_val(int msgpos, int *ip)
653 snprintf(buf, sizeof buf, "%d", (int) *ip);
654 set_str_val(msgpos, buf);
659 void set_char_val(int msgpos, char *ip)
662 snprintf(buf, sizeof buf, "%d", (int) *ip);
663 set_str_val(msgpos, buf);
664 *ip = (char) atoi(buf);
668 void set_long_val(int msgpos, long int *ip)
671 snprintf(buf, sizeof buf, "%ld", *ip);
672 set_str_val(msgpos, buf);
677 void edit_value(int curr)
681 char bbsuidname[SIZ];
686 set_str_val(curr, config.c_sysadm);
691 config.c_bbsuid = 0; /* XXX Windows hack, prob. insecure */
696 set_int_val(curr, &i);
700 strcpy(bbsuidname, pw->pw_name);
701 set_str_val(curr, bbsuidname);
702 pw = getpwnam(bbsuidname);
704 config.c_bbsuid = pw->pw_uid;
706 else if (atoi(bbsuidname) > 0) {
707 config.c_bbsuid = atoi(bbsuidname);
714 set_int_val(curr, &config.c_port_number);
722 * (re-)write the config data to disk
724 void write_config_to_disk(void)
729 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
730 display_error("setup: cannot open citadel.config");
733 fp = fdopen(fd, "wb");
735 display_error("setup: cannot open citadel.config");
738 fwrite((char *) &config, sizeof(struct config), 1, fp);
746 * Figure out what type of user interface we're going to use
748 int discover_ui(void)
754 newtDrawRootText(0, 0, "Citadel/UX Setup");
764 int main(int argc, char *argv[])
770 int old_setup_level = 0;
772 struct utsname my_utsname;
777 /* set an invalid setup type */
780 /* Check to see if we're running the web installer */
781 if (getenv("CITADEL_INSTALLER") != NULL) {
782 using_web_installer = 1;
785 /* parse command line args */
786 for (a = 0; a < argc; ++a) {
787 if (!strncmp(argv[a], "-u", 2)) {
788 strcpy(aaa, argv[a]);
789 strcpy(aaa, &aaa[2]);
790 setup_type = atoi(aaa);
792 if (!strcmp(argv[a], "-i")) {
795 if (!strcmp(argv[a], "-q")) {
796 setup_type = UI_SILENT;
801 /* If a setup type was not specified, try to determine automatically
802 * the best one to use out of all available types.
804 if (setup_type < 0) {
805 setup_type = discover_ui();
807 if (info_only == 1) {
808 important_message("Citadel/UX Setup", CITADEL);
812 /* Get started in a valid setup directory. */
813 strcpy(setup_directory, BBSDIR);
814 if ( (using_web_installer) && (getenv("CITADEL") != NULL) ) {
815 strcpy(setup_directory, getenv("CITADEL"));
818 set_str_val(0, setup_directory);
821 if (chdir(setup_directory) != 0) {
822 important_message("Citadel/UX Setup",
823 "The directory you specified does not exist.");
827 /* Determine our host name, in case we need to use it as a default */
830 /* See if we need to shut down the Citadel service. */
831 for (a=0; a<=3; ++a) {
832 progress("Shutting down the Citadel service...", a, 3);
833 if (a == 0) shutdown_service();
837 /* Make sure it's stopped. */
838 if (test_server() == 0) {
839 important_message("Citadel/UX Setup",
840 "The Citadel service is still running.\n"
841 "Please stop the service manually and run "
847 switch (setup_type) {
851 " *** Citadel/UX setup program ***\n\n");
857 * What we're going to try to do here is append a whole bunch of
858 * nulls to the citadel.config file, so we can keep the old config
859 * values if they exist, but if the file is missing or from an
860 * earlier version with a shorter config structure, when setup tries
861 * to read the old config parameters, they'll all come up zero.
862 * The length of the config file will be set to what it's supposed
863 * to be when we rewrite it, because we replace the old file with a
864 * completely new copy.
867 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
868 S_IRUSR | S_IWUSR)) == -1) {
869 display_error("setup: cannot append citadel.config");
872 fp = fdopen(a, "ab");
874 display_error("setup: cannot append citadel.config");
877 for (a = 0; a < sizeof(struct config); ++a)
881 /* now we re-open it, and read the old or blank configuration */
882 fp = fopen("citadel.config", "rb");
884 display_error("setup: cannot open citadel.config");
887 fread((char *) &config, sizeof(struct config), 1, fp);
890 /* set some sample/default values in place of blanks... */
891 if (strlen(config.c_nodename) == 0)
892 safestrncpy(config.c_nodename, my_utsname.nodename,
893 sizeof config.c_nodename);
894 strtok(config.c_nodename, ".");
895 if (strlen(config.c_fqdn) == 0) {
896 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
897 safestrncpy(config.c_fqdn, he->h_name,
898 sizeof config.c_fqdn);
900 safestrncpy(config.c_fqdn, my_utsname.nodename,
901 sizeof config.c_fqdn);
903 if (strlen(config.c_humannode) == 0)
904 strcpy(config.c_humannode, "My System");
905 if (strlen(config.c_phonenum) == 0)
906 strcpy(config.c_phonenum, "US 800 555 1212");
907 if (config.c_initax == 0) {
910 if (strlen(config.c_moreprompt) == 0)
911 strcpy(config.c_moreprompt, "<more>");
912 if (strlen(config.c_twitroom) == 0)
913 strcpy(config.c_twitroom, "Trashcan");
914 if (strlen(config.c_baseroom) == 0)
915 strcpy(config.c_baseroom, "Lobby");
916 if (strlen(config.c_aideroom) == 0)
917 strcpy(config.c_aideroom, "Aide");
918 if (config.c_port_number == 0) {
919 config.c_port_number = 504;
921 if (config.c_sleeping == 0) {
922 config.c_sleeping = 900;
924 if (config.c_bbsuid == 0) {
925 pw = getpwnam("citadel");
927 config.c_bbsuid = pw->pw_uid;
929 if (config.c_bbsuid == 0) {
930 pw = getpwnam("bbs");
932 config.c_bbsuid = pw->pw_uid;
934 if (config.c_bbsuid == 0) {
935 pw = getpwnam("guest");
937 config.c_bbsuid = pw->pw_uid;
939 if (config.c_createax == 0) {
940 config.c_createax = 3;
943 * Negative values for maxsessions are not allowed.
945 if (config.c_maxsessions < 0) {
946 config.c_maxsessions = 0;
948 /* We need a system default message expiry policy, because this is
949 * the top level and there's no 'higher' policy to fall back on.
951 if (config.c_ep.expire_mode == 0) {
952 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
953 config.c_ep.expire_value = 150;
957 * Default port numbers for various services
959 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
960 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
961 if (config.c_imap_port == 0) config.c_imap_port = 143;
963 /* Go through a series of dialogs prompting for config info */
964 if (setup_type != UI_SILENT) {
965 for (curr = 1; curr <= MAXSETUP; ++curr) {
971 if (setuid(config.c_bbsuid) != 0) {
972 important_message("Citadel/UX Setup",
973 "Failed to change the user ID to your Citadel user.");
978 /***** begin version update section ***** */
979 /* take care of any updating that is necessary */
981 old_setup_level = config.c_setup_level;
983 if (old_setup_level == 0) {
987 if (old_setup_level < 555) {
988 important_message("Citadel/UX Setup",
989 "This Citadel/UX installation is too old "
993 write_config_to_disk();
995 old_setup_level = config.c_setup_level;
997 /* end of version update section */
1000 config.c_setup_level = REV_LEVEL;
1002 /******************************************/
1004 write_config_to_disk();
1006 mkdir("info", 0700);
1007 chmod("info", 0700);
1010 mkdir("userpics", 0700);
1011 chmod("userpics", 0700);
1012 mkdir("messages", 0700);
1013 chmod("messages", 0700);
1014 mkdir("help", 0700);
1015 chmod("help", 0700);
1016 mkdir("images", 0700);
1017 chmod("images", 0700);
1018 mkdir("netconfigs", 0700);
1019 chmod("netconfigs", 0700);
1021 /* Delete files and directories used by older Citadel versions */
1022 system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1023 unlink("citadel.log");
1026 check_services_entry(); /* Check /etc/services */
1028 check_inittab_entry(); /* Check /etc/inittab */
1029 check_xinetd_entry(); /* Check /etc/xinetd.d/telnet */
1031 /* Offer to disable other MTA's on the system. */
1032 disable_other_mta("sendmail");
1033 disable_other_mta("postfix");
1034 disable_other_mta("qmail");
1035 disable_other_mta("cyrus");
1036 disable_other_mta("cyrmaster");
1037 disable_other_mta("saslauthd");
1038 disable_other_mta("mta");
1039 disable_other_mta("courier-imap");
1040 disable_other_mta("courier-imap-ssl");
1041 disable_other_mta("courier-authdaemon");
1042 disable_other_mta("courier-pop3");
1043 disable_other_mta("courier-pop3d");
1044 disable_other_mta("courier-pop");
1045 disable_other_mta("vmailmgrd");
1046 disable_other_mta("imapd");
1047 disable_other_mta("popd");
1048 disable_other_mta("pop3d");
1049 disable_other_mta("exim");
1052 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
1057 progress("Setting file permissions", 0, 4);
1058 chown(".", config.c_bbsuid, gid);
1059 progress("Setting file permissions", 1, 4);
1060 chown("citadel.config", config.c_bbsuid, gid);
1061 progress("Setting file permissions", 2, 4);
1062 snprintf(aaa, sizeof aaa,
1063 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
1064 (long)config.c_bbsuid, (long)gid);
1066 progress("Setting file permissions", 3, 4);
1067 chmod("citadel.config", S_IRUSR | S_IWUSR);
1068 progress("Setting file permissions", 4, 4);
1070 /* See if we can start the Citadel service. */
1071 if (strlen(init_entry) > 0) {
1072 for (a=0; a<=3; ++a) {
1073 progress("Starting the Citadel service...", a, 3);
1074 if (a == 0) start_the_service();
1077 if (test_server() == 0) {
1078 important_message("Setup finished",
1079 "Setup is finished. You may now log in.");
1082 important_message("Setup finished",
1083 "Setup is finished, but the Citadel service "
1084 "failed to start.\n"
1085 "Go back and check your configuration.");
1089 important_message("Setup finished",
1090 "Setup is finished. You may now start the server.");