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];
48 char *setup_titles[] =
50 "Citadel Home Directory",
51 "System Administrator",
59 "Enter the full pathname of the directory in which the Citadel installation\n"
60 "you are creating or updating resides. If you specify a directory other\n"
61 "than the default, you will need to specify the -h flag to the server when\n"
64 "Enter the name of the system administrator (which is probably you).\n"
65 "When an account is created with this name, it will automatically be\n"
66 "assigned the highest access level.\n",
68 "Citadel needs to run under its own user ID. This would typically be\n"
69 "called \"citadel\", but if you are running Citadel as a public BBS, you\n"
70 "might also call it \"bbs\" or \"guest\". The server will run under this\n"
71 "user ID. Please specify that user ID here. You may specify either a\n"
72 "user name or a numeric UID.\n",
74 "Specify the TCP port number on which your server will run. Normally, this\n"
75 "will be port 504, which is the official port assigned by the IANA for\n"
76 "Citadel servers. You'll only need to specify a different port number if\n"
77 "you run multiple instances of Citadel on the same computer and there's\n"
78 "something else already using port 504.\n",
80 "Setup has detected that you currently have data files from a Citadel/UX\n"
81 "version 3.2x installation. The program 'conv_32_40' can upgrade your\n"
82 "files to version 4.0x format.\n"
83 " Setup will now exit. Please either run 'conv_32_40' or delete your data\n"
84 "files, and run setup again.\n"
92 * Set an entry in inittab to the desired state
94 void set_init_entry(char *which_entry, char *new_state) {
103 inittab = strdup("");
104 if (inittab == NULL) return;
106 fp = fopen("/etc/inittab", "r");
107 if (fp == NULL) return;
109 while(fgets(buf, sizeof buf, fp) != NULL) {
111 if (num_tokens(buf, ':') == 4) {
112 extract_token(entry, buf, 0, ':');
113 extract_token(levels, buf, 1, ':');
114 extract_token(state, buf, 2, ':');
115 extract_token(prog, buf, 3, ':'); /* includes 0x0a LF */
117 if (!strcmp(entry, which_entry)) {
118 strcpy(state, new_state);
119 sprintf(buf, "%s:%s:%s:%s",
120 entry, levels, state, prog);
124 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
125 if (inittab == NULL) {
130 strcat(inittab, buf);
133 fp = fopen("/etc/inittab", "w");
135 fwrite(inittab, strlen(inittab), 1, fp);
137 kill(1, SIGHUP); /* Tell init to re-read /etc/inittab */
146 * Shut down the Citadel service if necessary, during setup.
148 void shutdown_service(void) {
151 char looking_for[SIZ];
156 strcpy(init_entry, "");
158 /* Determine the fully qualified path name of citserver */
159 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
161 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
162 * an entry is found which we believe starts citserver.
164 infp = fopen("/etc/inittab", "r");
168 while (fgets(buf, sizeof buf, infp) != NULL) {
169 buf[strlen(buf) - 1] = 0;
170 extract_token(entry, buf, 0, ':');
171 extract_token(prog, buf, 3, ':');
172 if (!strncasecmp(prog, looking_for,
173 strlen(looking_for))) {
175 strcpy(init_entry, entry);
181 /* Bail out if there's nothing to do. */
182 if (!have_entry) return;
184 set_init_entry(init_entry, "off");
189 * Start the Citadel service.
191 void start_the_service(void) {
192 if (strlen(init_entry) > 0) {
193 set_init_entry(init_entry, "respawn");
199 void cleanup(int exitcode)
211 void title(char *text)
213 if (setup_type == UI_TEXT) {
214 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
220 int yesno(char *question)
223 newtComponent form = NULL;
224 newtComponent yesbutton = NULL;
225 newtComponent nobutton = NULL;
231 switch (setup_type) {
235 printf("%s\nYes/No --> ", question);
236 fgets(buf, sizeof buf, stdin);
237 answer = tolower(buf[0]);
240 else if (answer == 'n')
242 } while ((answer < 0) || (answer > 1));
247 newtCenteredWindow(76, 10, "Question");
248 form = newtForm(NULL, NULL, 0);
249 for (i=0; i<num_tokens(question, '\n'); ++i) {
250 extract_token(buf, question, i, '\n');
251 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
253 yesbutton = newtButton(10, 5, "Yes");
254 nobutton = newtButton(60, 5, "No");
255 newtFormAddComponent(form, yesbutton);
256 newtFormAddComponent(form, nobutton);
257 if (newtRunForm(form) == yesbutton) {
264 newtFormDestroy(form);
274 void important_message(char *title, char *msgtext)
277 newtComponent form = NULL;
282 switch (setup_type) {
285 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");
286 printf(" %s \n\n%s\n\n", title, msgtext);
287 printf("Press return to continue...");
288 fgets(buf, sizeof buf, stdin);
293 newtCenteredWindow(76, 10, title);
294 form = newtForm(NULL, NULL, 0);
295 for (i=0; i<num_tokens(msgtext, '\n'); ++i) {
296 extract_token(buf, msgtext, i, '\n');
297 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
299 newtFormAddComponent(form, newtButton(35, 5, "OK"));
302 newtFormDestroy(form);
309 void important_msgnum(int msgnum)
311 important_message("Important Message", setup_text[msgnum]);
314 void display_error(char *error_message)
316 important_message("Error", error_message);
319 void progress(char *text, long int curr, long int cmax)
323 /* These variables are static because progress() gets called
324 * multiple times during the course of whatever operation is
325 * being performed. This makes setup non-threadsafe, but who
328 static newtComponent form = NULL;
329 static newtComponent scale = NULL;
331 static long dots_printed = 0L;
334 switch (setup_type) {
338 printf("%s\n", text);
339 printf("..........................");
340 printf("..........................");
341 printf("..........................\r");
344 } else if (curr == cmax) {
345 printf("\r%79s\n", "");
347 a = (curr * 100) / cmax;
350 while (dots_printed < a) {
361 newtCenteredWindow(76, 8, text);
362 form = newtForm(NULL, NULL, 0);
363 scale = newtScale(1, 3, 74, cmax);
364 newtFormAddComponent(form, scale);
368 if ((curr > 0) && (curr <= cmax)) {
369 newtScaleSet(scale, curr);
373 newtFormDestroy(form);
386 * check_services_entry() -- Make sure "citadel" is in /etc/services
389 void check_services_entry(void)
394 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
395 for (i=0; i<3; ++i) {
396 progress("Adding service entry...", i, 3);
398 sfp = fopen("/etc/services", "a");
400 display_error(strerror(errno));
402 fprintf(sfp, "%s 504/tcp\n",
414 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
417 void check_inittab_entry(void)
421 char looking_for[SIZ];
425 /* Determine the fully qualified path name of citserver */
426 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
428 /* If there's already an entry, then we have nothing left to do. */
429 if (strlen(init_entry) > 0) {
433 /* Otherwise, prompt the user to create an entry. */
434 snprintf(question, sizeof question,
435 "There is no '%s' entry in /etc/inittab.\n"
436 "Would you like to add one?",
438 if (yesno(question) == 0)
441 /* Generate a unique entry name for /etc/inittab */
442 snprintf(entryname, sizeof entryname, "c0");
445 if (entryname[1] > '9') {
448 if (entryname[0] > 'z') {
450 "Can't generate a unique entry name");
454 snprintf(buf, sizeof buf,
455 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
456 } while (system(buf) == 0);
458 /* Now write it out to /etc/inittab */
459 infp = fopen("/etc/inittab", "a");
461 display_error(strerror(errno));
463 fprintf(infp, "# Start the Citadel/UX server...\n");
464 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
465 entryname, looking_for, setup_directory);
467 strcpy(init_entry, entryname);
473 * On systems which use xinetd, see if we can offer to install Citadel as
474 * the default telnet target.
476 void check_xinetd_entry(void) {
477 char *filename = "/etc/xinetd.d/telnet";
480 int already_citadel = 0;
482 fp = fopen(filename, "r+");
483 if (fp == NULL) return; /* Not there. Oh well... */
485 while (fgets(buf, sizeof buf, fp) != NULL) {
486 if (strstr(buf, setup_directory) != NULL) already_citadel = 1;
489 if (already_citadel) return; /* Already set up this way. */
491 /* Otherwise, prompt the user to create an entry. */
492 snprintf(buf, sizeof buf,
493 "Setup can configure the 'xinetd' service to automatically\n"
494 "connect incoming telnet sessions to Citadel, bypassing the\n"
495 "host system's login prompt. Would you like to do this?\n"
500 fp = fopen(filename, "w");
502 "# description: telnet service for Citadel users\n"
507 " socket_type = stream\n"
510 " server = /usr/sbin/in.telnetd\n"
511 " server_args = -h -L %s/citadel\n"
512 " log_on_failure += USERID\n"
518 /* Now try to restart the service */
519 system("/etc/init.d/xinetd restart >/dev/null 2>&1");
525 * Offer to disable other MTA's
527 void disable_other_mta(char *mta) {
532 sprintf(buf, "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null", mta);
533 fp = popen(buf, "r");
534 if (fp == NULL) return;
536 while (fgets(buf, sizeof buf, fp) != NULL) {
540 if (lines == 0) return; /* Nothing to do. */
542 /* Offer to replace other MTA with the vastly superior Citadel :) */
543 snprintf(buf, sizeof buf,
544 "You appear to have the '%s' email program\n"
545 "running on your system. Would you like to disable it,\n"
546 "allowing Citadel to handle your system's Internet mail\n"
553 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);
555 sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
563 * Check to see if our server really works. Returns 0 on success.
565 int test_server(void) {
572 /* Generate a silly little cookie. We're going to write it out
573 * to the server and try to get it back. The cookie does not
574 * have to be secret ... just unique.
576 sprintf(cookie, "%ld.%d", time(NULL), getpid());
578 sprintf(cmd, "%s/sendcommand -h%s ECHO %s 2>&1",
583 fp = popen(cmd, "r");
584 if (fp == NULL) return(errno);
586 while (fgets(buf, sizeof buf, fp) != NULL) {
588 && (strstr(buf, cookie) != NULL) ) {
605 void set_str_val(int msgpos, char str[])
613 char tempfile[PATH_MAX];
616 strcpy(tempfile, tmpnam(NULL));
617 strcpy(setupmsg, "");
619 switch (setup_type) {
621 title(setup_titles[msgpos]);
622 printf("\n%s\n", setup_text[msgpos]);
623 printf("This is currently set to:\n%s\n", str);
624 printf("Enter new value or press return to leave unchanged:\n");
625 fgets(buf, sizeof buf, stdin);
626 buf[strlen(buf) - 1] = 0;
627 if (strlen(buf) != 0)
633 newtCenteredWindow(76, 10, setup_titles[msgpos]);
634 form = newtForm(NULL, NULL, 0);
635 for (i=0; i<num_tokens(setup_text[msgpos], '\n'); ++i) {
636 extract_token(buf, setup_text[msgpos], i, '\n');
637 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
639 newtFormAddComponent(form, newtEntry(1, 8, str, 74, &result,
640 NEWT_FLAG_RETURNEXIT));
645 newtFormDestroy(form);
651 void set_int_val(int msgpos, int *ip)
654 snprintf(buf, sizeof buf, "%d", (int) *ip);
655 set_str_val(msgpos, buf);
660 void set_char_val(int msgpos, char *ip)
663 snprintf(buf, sizeof buf, "%d", (int) *ip);
664 set_str_val(msgpos, buf);
665 *ip = (char) atoi(buf);
669 void set_long_val(int msgpos, long int *ip)
672 snprintf(buf, sizeof buf, "%ld", *ip);
673 set_str_val(msgpos, buf);
678 void edit_value(int curr)
682 char bbsuidname[SIZ];
687 set_str_val(curr, config.c_sysadm);
692 config.c_bbsuid = 0; /* XXX Windows hack, prob. insecure */
697 set_int_val(curr, &i);
701 strcpy(bbsuidname, pw->pw_name);
702 set_str_val(curr, bbsuidname);
703 pw = getpwnam(bbsuidname);
705 config.c_bbsuid = pw->pw_uid;
707 else if (atoi(bbsuidname) > 0) {
708 config.c_bbsuid = atoi(bbsuidname);
715 set_int_val(curr, &config.c_port_number);
723 * (re-)write the config data to disk
725 void write_config_to_disk(void)
730 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
731 display_error("setup: cannot open citadel.config");
734 fp = fdopen(fd, "wb");
736 display_error("setup: cannot open citadel.config");
739 fwrite((char *) &config, sizeof(struct config), 1, fp);
747 * Figure out what type of user interface we're going to use
749 int discover_ui(void)
755 newtDrawRootText(0, 0, "Citadel/UX Setup");
765 int main(int argc, char *argv[])
771 int old_setup_level = 0;
773 struct utsname my_utsname;
778 /* set an invalid setup type */
781 /* parse command line args */
782 for (a = 0; a < argc; ++a) {
783 if (!strncmp(argv[a], "-u", 2)) {
784 strcpy(aaa, argv[a]);
785 strcpy(aaa, &aaa[2]);
786 setup_type = atoi(aaa);
788 if (!strcmp(argv[a], "-i")) {
791 if (!strcmp(argv[a], "-q")) {
792 setup_type = UI_SILENT;
797 /* If a setup type was not specified, try to determine automatically
798 * the best one to use out of all available types.
800 if (setup_type < 0) {
801 setup_type = discover_ui();
803 if (info_only == 1) {
804 important_message("Citadel/UX Setup", CITADEL);
808 /* Get started in a valid setup directory. */
809 strcpy(setup_directory, BBSDIR);
810 set_str_val(0, setup_directory);
811 if (chdir(setup_directory) != 0) {
812 important_message("Citadel/UX Setup",
813 "The directory you specified does not exist.");
817 /* Determine our host name, in case we need to use it as a default */
820 /* See if we need to shut down the Citadel service. */
821 for (a=0; a<=3; ++a) {
822 progress("Shutting down the Citadel service...", a, 3);
823 if (a == 0) shutdown_service();
827 /* Make sure it's stopped. */
828 if (test_server() == 0) {
829 important_message("Citadel/UX Setup",
830 "The Citadel service is still running.\n"
831 "Please stop the service manually and run "
837 switch (setup_type) {
841 " *** Citadel/UX setup program ***\n\n");
847 * What we're going to try to do here is append a whole bunch of
848 * nulls to the citadel.config file, so we can keep the old config
849 * values if they exist, but if the file is missing or from an
850 * earlier version with a shorter config structure, when setup tries
851 * to read the old config parameters, they'll all come up zero.
852 * The length of the config file will be set to what it's supposed
853 * to be when we rewrite it, because we replace the old file with a
854 * completely new copy.
857 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
858 S_IRUSR | S_IWUSR)) == -1) {
859 display_error("setup: cannot append citadel.config");
862 fp = fdopen(a, "ab");
864 display_error("setup: cannot append citadel.config");
867 for (a = 0; a < sizeof(struct config); ++a)
871 /* now we re-open it, and read the old or blank configuration */
872 fp = fopen("citadel.config", "rb");
874 display_error("setup: cannot open citadel.config");
877 fread((char *) &config, sizeof(struct config), 1, fp);
880 /* set some sample/default values in place of blanks... */
881 if (strlen(config.c_nodename) == 0)
882 safestrncpy(config.c_nodename, my_utsname.nodename,
883 sizeof config.c_nodename);
884 strtok(config.c_nodename, ".");
885 if (strlen(config.c_fqdn) == 0) {
886 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
887 safestrncpy(config.c_fqdn, he->h_name,
888 sizeof config.c_fqdn);
890 safestrncpy(config.c_fqdn, my_utsname.nodename,
891 sizeof config.c_fqdn);
893 if (strlen(config.c_humannode) == 0)
894 strcpy(config.c_humannode, "My System");
895 if (strlen(config.c_phonenum) == 0)
896 strcpy(config.c_phonenum, "US 800 555 1212");
897 if (config.c_initax == 0) {
900 if (strlen(config.c_moreprompt) == 0)
901 strcpy(config.c_moreprompt, "<more>");
902 if (strlen(config.c_twitroom) == 0)
903 strcpy(config.c_twitroom, "Trashcan");
904 if (strlen(config.c_net_password) == 0)
905 strcpy(config.c_net_password, "netpassword");
906 if (strlen(config.c_baseroom) == 0)
907 strcpy(config.c_baseroom, "Lobby");
908 if (strlen(config.c_aideroom) == 0)
909 strcpy(config.c_aideroom, "Aide");
910 if (config.c_port_number == 0) {
911 config.c_port_number = 504;
913 if (config.c_sleeping == 0) {
914 config.c_sleeping = 900;
916 if (config.c_bbsuid == 0) {
917 pw = getpwnam("citadel");
919 config.c_bbsuid = pw->pw_uid;
921 if (config.c_bbsuid == 0) {
922 pw = getpwnam("bbs");
924 config.c_bbsuid = pw->pw_uid;
926 if (config.c_bbsuid == 0) {
927 pw = getpwnam("guest");
929 config.c_bbsuid = pw->pw_uid;
931 if (config.c_createax == 0) {
932 config.c_createax = 3;
935 * Negative values for maxsessions are not allowed.
937 if (config.c_maxsessions < 0) {
938 config.c_maxsessions = 0;
940 /* We need a system default message expiry policy, because this is
941 * the top level and there's no 'higher' policy to fall back on.
943 if (config.c_ep.expire_mode == 0) {
944 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
945 config.c_ep.expire_value = 150;
949 * Default port numbers for various services
951 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
952 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
953 if (config.c_imap_port == 0) config.c_imap_port = 143;
955 /* Go through a series of dialogs prompting for config info */
956 if (setup_type != UI_SILENT) {
957 for (curr = 1; curr <= MAXSETUP; ++curr) {
963 if (setuid(config.c_bbsuid) != 0) {
964 important_message("Citadel/UX Setup",
965 "Failed to change the user ID to your Citadel user.");
970 /***** begin version update section ***** */
971 /* take care of any updating that is necessary */
973 old_setup_level = config.c_setup_level;
975 if (old_setup_level == 0) {
979 if (old_setup_level < 555) {
980 important_message("Citadel/UX Setup",
981 "This Citadel/UX installation is too old "
985 write_config_to_disk();
987 old_setup_level = config.c_setup_level;
989 /* end of version update section */
992 config.c_setup_level = REV_LEVEL;
994 /******************************************/
996 write_config_to_disk();
1000 mkdir("userpics", 0700);
1001 mkdir("messages", 0700);
1002 mkdir("help", 0700);
1003 mkdir("images", 0700);
1004 mkdir("netconfigs", 0700);
1006 /* Delete files and directories used by older Citadel versions */
1007 system("exec /bin/rm -fr ./rooms ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
1008 unlink("citadel.log");
1011 check_services_entry(); /* Check /etc/services */
1013 check_inittab_entry(); /* Check /etc/inittab */
1014 check_xinetd_entry(); /* Check /etc/xinetd.d/telnet */
1016 /* Offer to disable other MTA's on the system. */
1017 disable_other_mta("sendmail");
1018 disable_other_mta("postfix");
1019 disable_other_mta("qmail");
1020 disable_other_mta("cyrus");
1021 disable_other_mta("cyrmaster");
1022 disable_other_mta("saslauthd");
1023 disable_other_mta("mta");
1024 disable_other_mta("courier-imap");
1025 disable_other_mta("courier-imap-ssl");
1026 disable_other_mta("courier-authdaemon");
1027 disable_other_mta("courier-pop3");
1028 disable_other_mta("courier-pop3d");
1029 disable_other_mta("courier-pop");
1030 disable_other_mta("vmailmgrd");
1031 disable_other_mta("imapd");
1034 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
1039 progress("Setting file permissions", 0, 4);
1040 chown(".", config.c_bbsuid, gid);
1041 progress("Setting file permissions", 1, 4);
1042 chown("citadel.config", config.c_bbsuid, gid);
1043 progress("Setting file permissions", 2, 4);
1044 snprintf(aaa, sizeof aaa,
1045 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
1046 (long)config.c_bbsuid, (long)gid);
1048 progress("Setting file permissions", 3, 4);
1049 chmod("citadel.config", S_IRUSR | S_IWUSR);
1050 progress("Setting file permissions", 4, 4);
1052 /* See if we can start the Citadel service. */
1053 if (strlen(init_entry) > 0) {
1054 for (a=0; a<=3; ++a) {
1055 progress("Starting the Citadel service...", a, 3);
1056 if (a == 0) start_the_service();
1059 if (test_server() == 0) {
1060 important_message("Setup finished",
1061 "Setup is finished. You may now log in.");
1064 important_message("Setup finished",
1065 "Setup is finished, but the Citadel service "
1066 "failed to start.\n"
1067 "Go back and check your configuration.");
1071 important_message("Setup finished",
1072 "Setup is finished. You may now start the server.");