4 * Citadel/UX setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
29 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
41 #define UI_TEXT 0 /* Default setup type -- text only */
42 #define UI_DIALOG 1 /* Use the 'dialog' program (REMOVED) */
43 #define UI_CURSES 2 /* Use curses */
44 #define UI_SILENT 3 /* Silent running, for use in scripts */
46 #define SERVICE_NAME "citadel"
47 #define PROTO_NAME "tcp"
50 char setup_directory[SIZ];
54 char *setup_titles[] =
57 "System Administrator",
65 "Enter the full pathname of the directory in which the BBS you are\n"
66 "creating or updating resides. If you specify a directory other than the\n"
67 "default, you will need to specify the -h flag to the server when you start\n"
70 "Enter the name of the system administrator (which is probably you).\n"
71 "When an account is created with this name, it will automatically be\n"
72 "assigned the highest access level.\n",
74 "You should create a user called 'bbs', 'guest', 'citadel', or something\n"
75 "similar, that will allow users a way into your BBS. The server will run\n"
76 "under this user ID. Please specify that (numeric) user ID here.\n",
78 "Specify the TCP port number on which your server will run. Normally, this\n"
79 "will be port 504, which is the official port assigned by the IANA for\n"
80 "Citadel servers. You'll only need to specify a different port number if\n"
81 "you run multiple BBS's on the same computer and there's something else\n"
82 "already using port 504.\n",
84 "Setup has detected that you currently have data files from a Citadel/UX\n"
85 "version 3.2x installation. The program 'conv_32_40' can upgrade your\n"
86 "files to version 4.0x format.\n"
87 " Setup will now exit. Please either run 'conv_32_40' or delete your data\n"
88 "files, and run setup again.\n"
96 * Do an "init q" to tell init to re-read its configuration file
105 * We can't guarantee that telinit or init will be in the right
106 * place, so we try a couple of different paths. The first one
107 * will work 99% of the time, though.
109 execlp("/sbin/telinit", "telinit", "q", NULL);
110 execlp("/sbin/init", "init", "q", NULL);
111 execlp("/usr/sbin/init", "init", "q", NULL);
112 execlp("/bin/init", "init", "q", NULL);
113 execlp("/usr/bin/init", "init", "q", NULL);
114 execlp("init", "init", "q", NULL);
117 * Didn't find it? Fail silently. Perhaps we're running on
118 * some sort of BSD system and there's no init at all. If so,
119 * the person installing Citadel probably knows how to handle
120 * this task manually.
125 while (waitpid(cpid, &status, 0) == -1) ;;
131 * Set an entry in inittab to the desired state
133 void set_init_entry(char *which_entry, char *new_state) {
134 char *inittab = NULL;
142 inittab = strdup("");
143 if (inittab == NULL) return;
145 fp = fopen("/etc/inittab", "r");
146 if (fp == NULL) return;
148 while(fgets(buf, sizeof buf, fp) != NULL) {
150 if (num_tokens(buf, ':') == 4) {
153 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
154 if (inittab == NULL) {
159 strcat(inittab, buf);
162 fp = fopen("/etc/inittab", "w");
164 fwrite(inittab, strlen(inittab), 1, fp);
175 * Shut down the Citadel service if necessary, during setup.
177 void shutdown_service(void) {
180 char looking_for[SIZ];
185 strcpy(init_entry, "");
187 /* Determine the fully qualified path name of citserver */
188 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
190 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
191 * an entry is found which we believe starts citserver.
193 infp = fopen("/etc/inittab", "r");
197 while (fgets(buf, sizeof buf, infp) != NULL) {
198 buf[strlen(buf) - 1] = 0;
199 extract_token(entry, buf, 0, ':');
200 extract_token(prog, buf, 3, ':');
201 if (!strncasecmp(prog, looking_for,
202 strlen(looking_for))) {
204 strcpy(init_entry, entry);
210 /* Bail out if there's nothing to do. */
211 if (!have_entry) return;
213 set_init_entry(init_entry, "off");
217 void cleanup(int exitcode)
220 if (setup_type == UI_CURSES) {
233 /* Gets a line from the terminal */
234 /* Where on the screen to start */
235 /* Pointer to string buffer */
236 /* Maximum length - if negative, no-show */
238 void getlin(int yp, int xp, char *string, int lim) {
249 for (a = 0; a < lim; ++a)
253 for (a = 0; a < lim; ++a)
256 printw("%s", string);
257 GLA:move(yp, xp + strlen(string));
265 if ((a == 8) && (strlen(string) == 0))
267 if ((a != 13) && (a != 8) && (strlen(string) == lim))
269 if ((a == 8) && (string[0] != 0)) {
270 string[strlen(string) - 1] = 0;
271 move(yp, xp + strlen(string));
275 if ((a == 13) || (a == 10)) {
278 for (a = 0; a < lim; ++a)
280 mvprintw(yp, xp, "%s", string);
297 void title(char *text)
299 if (setup_type == UI_TEXT) {
300 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
305 void hit_any_key(void)
310 if (setup_type == UI_CURSES) {
311 mvprintw(20, 0, "Press any key to continue... ");
317 if (setup_type == UI_TEXT) {
318 printf("Press return to continue...");
319 fgets(junk, 5, stdin);
323 int yesno(char *question)
328 switch (setup_type) {
332 printf("%s\nYes/No --> ", question);
333 fgets(buf, 4096, stdin);
334 answer = tolower(buf[0]);
337 else if (answer == 'n')
339 } while ((answer < 0) || (answer > 1));
347 mvprintw(1, 20, "Question");
349 mvprintw(10, 0, "%-80s", question);
350 mvprintw(20, 0, "%80s", "");
351 mvprintw(20, 0, "Yes/No -> ");
354 answer = tolower(answer);
357 else if (answer == 'n')
359 } while ((answer < 0) || (answer > 1));
368 void important_message(char *title, char *msgtext)
371 switch (setup_type) {
374 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");
375 printf(" %s \n\n%s\n\n", title, msgtext);
384 printw(" Important Message ");
387 printw("%s", msgtext);
396 void important_msgnum(int msgnum)
398 important_message("Important Message", setup_text[msgnum]);
401 void display_error(char *error_message)
403 important_message("Error", error_message);
406 void progress(char *text, long int curr, long int cmax)
408 static long dots_printed;
411 switch (setup_type) {
415 printf("%s\n", text);
416 printf("..........................");
417 printf("..........................");
418 printf("..........................\r");
421 } else if (curr == cmax) {
422 printf("\r%79s\n", "");
424 a = (curr * 100) / cmax;
427 while (dots_printed < a) {
440 printw("%s\n", text);
442 printf("..........................");
443 printf("..........................");
444 printf("..........................\r");
447 } else if (curr == cmax) {
451 a = (curr * 100) / cmax;
456 while (dots_printed < a) {
471 * check_services_entry() -- Make sure "citadel" is in /etc/services
474 void check_services_entry(void)
479 snprintf(question, sizeof question,
480 "There is no '%s' entry in /etc/services. Would you like to add one?",
483 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
484 if (yesno(question) == 1) {
485 sfp = fopen("/etc/services", "a");
487 display_error(strerror(errno));
489 fprintf(sfp, "%s 504/tcp\n",
499 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
502 void check_inittab_entry(void)
506 char looking_for[SIZ];
512 /* Determine the fully qualified path name of citserver */
513 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
515 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
516 * an entry is found which we believe starts citserver.
518 infp = fopen("/etc/inittab", "r");
522 while (fgets(buf, sizeof buf, infp) != NULL) {
523 buf[strlen(buf) - 1] = 0;
524 ptr = strtok(buf, ":");
525 ptr = strtok(NULL, ":");
526 ptr = strtok(NULL, ":");
527 ptr = strtok(NULL, ":");
529 if (!strncmp(ptr, looking_for,
530 strlen(looking_for))) {
538 /* If there's already an entry, then we have nothing left to do. */
542 /* Otherwise, prompt the user to create an entry. */
543 snprintf(question, sizeof question,
544 "There is no '%s' entry in /etc/inittab.\n"
545 "Would you like to add one?",
547 if (yesno(question) == 0)
550 /* Generate a unique entry name for /etc/inittab */
551 snprintf(entryname, sizeof entryname, "c0");
554 if (entryname[1] > '9') {
557 if (entryname[0] > 'z') {
559 "Can't generate a unique entry name");
563 snprintf(buf, sizeof buf,
564 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
565 } while (system(buf) == 0);
567 /* Now write it out to /etc/inittab */
568 infp = fopen("/etc/inittab", "a");
570 display_error(strerror(errno));
572 fprintf(infp, "# Start the Citadel/UX server...\n");
573 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
574 entryname, looking_for, setup_directory);
582 void set_str_val(int msgpos, char str[])
588 strcpy(tempfile, tmpnam(NULL));
589 strcpy(setupmsg, "");
591 switch (setup_type) {
593 title(setup_titles[msgpos]);
594 printf("\n%s\n", setup_text[msgpos]);
595 printf("This is currently set to:\n%s\n", str);
596 printf("Enter new value or press return to leave unchanged:\n");
597 fgets(buf, 4096, stdin);
598 buf[strlen(buf) - 1] = 0;
599 if (strlen(buf) != 0)
605 move(1, ((80 - strlen(setup_titles[msgpos])) / 2));
607 printw("%s", setup_titles[msgpos]);
610 printw("%s", setup_text[msgpos]);
612 getlin(20, 0, str, 80);
618 void set_int_val(int msgpos, int *ip)
621 snprintf(buf, sizeof buf, "%d", (int) *ip);
622 set_str_val(msgpos, buf);
627 void set_char_val(int msgpos, char *ip)
630 snprintf(buf, sizeof buf, "%d", (int) *ip);
631 set_str_val(msgpos, buf);
632 *ip = (char) atoi(buf);
636 void set_long_val(int msgpos, long int *ip)
639 snprintf(buf, sizeof buf, "%ld", *ip);
640 set_str_val(msgpos, buf);
645 void edit_value(int curr)
652 set_str_val(curr, config.c_sysadm);
657 set_long_val(curr, &l);
662 set_int_val(curr, &config.c_port_number);
670 * (re-)write the config data to disk
672 void write_config_to_disk(void)
677 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
678 display_error("setup: cannot open citadel.config");
681 fp = fdopen(fd, "wb");
683 display_error("setup: cannot open citadel.config");
686 fwrite((char *) &config, sizeof(struct config), 1, fp);
694 * Figure out what type of user interface we're going to use
696 int discover_ui(void)
709 int main(int argc, char *argv[])
715 int old_setup_level = 0;
717 struct utsname my_utsname;
722 /* set an invalid setup type */
725 /* parse command line args */
726 for (a = 0; a < argc; ++a) {
727 if (!strncmp(argv[a], "-u", 2)) {
728 strcpy(aaa, argv[a]);
729 strcpy(aaa, &aaa[2]);
730 setup_type = atoi(aaa);
732 if (!strcmp(argv[a], "-i")) {
735 if (!strcmp(argv[a], "-q")) {
736 setup_type = UI_SILENT;
741 /* If a setup type was not specified, try to determine automatically
742 * the best one to use out of all available types.
744 if (setup_type < 0) {
745 setup_type = discover_ui();
748 if (setup_type == UI_CURSES) {
755 if (info_only == 1) {
756 important_message("Citadel/UX Setup", CITADEL);
760 /* Get started in a valid setup directory. */
761 strcpy(setup_directory, BBSDIR);
762 set_str_val(0, setup_directory);
763 if (chdir(setup_directory) != 0) {
764 important_message("Citadel/UX Setup",
765 "The directory you specified does not exist.");
769 /* Determine our host name, in case we need to use it as a default */
772 /* See if we need to shut down the Citadel service. */
776 switch (setup_type) {
780 " *** Citadel/UX setup program ***\n\n");
786 * What we're going to try to do here is append a whole bunch of
787 * nulls to the citadel.config file, so we can keep the old config
788 * values if they exist, but if the file is missing or from an
789 * earlier version with a shorter config structure, when setup tries
790 * to read the old config parameters, they'll all come up zero.
791 * The length of the config file will be set to what it's supposed
792 * to be when we rewrite it, because we replace the old file with a
793 * completely new copy. (Neat, eh?)
796 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
797 S_IRUSR | S_IWUSR)) == -1) {
798 display_error("setup: cannot append citadel.config");
801 fp = fdopen(a, "ab");
803 display_error("setup: cannot append citadel.config");
806 for (a = 0; a < sizeof(struct config); ++a)
810 /* now we re-open it, and read the old or blank configuration */
811 fp = fopen("citadel.config", "rb");
813 display_error("setup: cannot open citadel.config");
816 fread((char *) &config, sizeof(struct config), 1, fp);
820 /* set some sample/default values in place of blanks... */
821 if (strlen(config.c_nodename) == 0)
822 safestrncpy(config.c_nodename, my_utsname.nodename,
823 sizeof config.c_nodename);
824 strtok(config.c_nodename, ".");
825 if (strlen(config.c_fqdn) == 0) {
826 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
827 safestrncpy(config.c_fqdn, he->h_name,
828 sizeof config.c_fqdn);
830 safestrncpy(config.c_fqdn, my_utsname.nodename,
831 sizeof config.c_fqdn);
833 if (strlen(config.c_humannode) == 0)
834 strcpy(config.c_humannode, "My System");
835 if (strlen(config.c_phonenum) == 0)
836 strcpy(config.c_phonenum, "US 800 555 1212");
837 if (config.c_initax == 0) {
840 if (strlen(config.c_moreprompt) == 0)
841 strcpy(config.c_moreprompt, "<more>");
842 if (strlen(config.c_twitroom) == 0)
843 strcpy(config.c_twitroom, "Trashcan");
844 if (strlen(config.c_bucket_dir) == 0)
845 strcpy(config.c_bucket_dir, "bitbucket");
846 if (strlen(config.c_net_password) == 0)
847 strcpy(config.c_net_password, "netpassword");
848 if (strlen(config.c_baseroom) == 0)
849 strcpy(config.c_baseroom, "Lobby");
850 if (strlen(config.c_aideroom) == 0)
851 strcpy(config.c_aideroom, "Aide");
852 if (config.c_port_number == 0) {
853 config.c_port_number = 504;
855 if (config.c_ipgm_secret == 0) {
857 config.c_ipgm_secret = rand();
859 if (config.c_sleeping == 0) {
860 config.c_sleeping = 900;
862 if (config.c_bbsuid == 0) {
863 pw = getpwnam("citadel");
865 config.c_bbsuid = pw->pw_uid;
867 if (config.c_bbsuid == 0) {
868 pw = getpwnam("bbs");
870 config.c_bbsuid = pw->pw_uid;
872 if (config.c_bbsuid == 0) {
873 pw = getpwnam("guest");
875 config.c_bbsuid = pw->pw_uid;
877 if (config.c_createax == 0) {
878 config.c_createax = 3;
881 * Negative values for maxsessions are not allowed.
883 if (config.c_maxsessions < 0) {
884 config.c_maxsessions = 0;
886 /* We need a system default message expiry policy, because this is
887 * the top level and there's no 'higher' policy to fall back on.
889 if (config.c_ep.expire_mode == 0) {
890 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
891 config.c_ep.expire_value = 150;
895 * Default port numbers for various services
897 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
898 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
899 if (config.c_imap_port == 0) config.c_imap_port = 143;
902 /* Go through a series of dialogs prompting for config info */
903 if (setup_type != UI_SILENT) {
904 for (curr = 1; curr <= MAXSETUP; ++curr) {
910 if (setuid(config.c_bbsuid) != 0) {
911 important_message("Citadel/UX Setup",
912 "Failed to change the user ID to your BBS user.");
917 /***** begin version update section ***** */
918 /* take care of any updating that is necessary */
920 old_setup_level = config.c_setup_level;
922 if (old_setup_level == 0)
925 if (old_setup_level < 323) {
926 important_message("Citadel/UX Setup",
927 "This Citadel/UX installation is too old "
931 write_config_to_disk();
933 if ((config.c_setup_level / 10) == 32) {
934 important_msgnum(31);
937 if (config.c_setup_level < 400) {
938 config.c_setup_level = 400;
940 /* end of 3.23 -> 4.00 update section */
942 /* end of 4.00 -> 4.02 update section */
944 old_setup_level = config.c_setup_level;
946 /* end of version update section */
949 config.c_setup_level = REV_LEVEL;
951 /******************************************/
953 write_config_to_disk();
957 mkdir("userpics", 0700);
958 mkdir("messages", 0700);
960 mkdir("images", 0700);
961 mkdir("netconfigs", 0700);
962 mkdir(config.c_bucket_dir, 0700);
964 /* Delete a bunch of old files from Citadel v4; don't need anymore */
965 system("rm -fr ./chatpipes ./expressmsgs sessions 2>/dev/null");
967 check_services_entry(); /* Check /etc/services */
968 check_inittab_entry(); /* Check /etc/inittab */
970 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
975 progress("Setting file permissions", 0, 5);
976 chown(".", config.c_bbsuid, gid);
977 progress("Setting file permissions", 1, 5);
978 chown("citadel.config", config.c_bbsuid, gid);
979 progress("Setting file permissions", 2, 5);
980 snprintf(aaa, sizeof aaa,
981 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
982 (long)config.c_bbsuid, (long)gid);
984 progress("Setting file permissions", 3, 5);
985 chmod("citadel.config", S_IRUSR | S_IWUSR);
986 progress("Setting file permissions", 4, 5);
988 important_message("Setup finished",
989 "Setup is finished. You may now start the Citadel server.");