4 * Citadel/UX setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
28 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
40 #define UI_TEXT 0 /* Default setup type -- text only */
41 #define UI_DIALOG 1 /* Use the 'dialog' program (REMOVED) */
42 #define UI_CURSES 2 /* Use curses */
43 #define UI_SILENT 3 /* Silent running, for use in scripts */
45 #define SERVICE_NAME "citadel"
46 #define PROTO_NAME "tcp"
49 char setup_directory[128];
52 char *setup_titles[] =
55 "System Administrator",
57 "Name of bit bucket subdirectory",
66 "Enter the full pathname of the directory in which the BBS you are",
67 "creating or updating resides. If you specify a directory other than the",
68 "default, you will need to specify the -h flag to the server when you start",
72 "Enter the name of the system administrator (which is probably you).",
73 "When an account is created with this name, it will automatically be",
74 "assigned the highest access level.",
77 "You should create a user called 'bbs', 'guest', 'citadel', or something",
78 "similar, that will allow users a way into your BBS. The server will run",
79 "under this user ID. Please specify that (numeric) user ID here.",
82 "Select the name of a subdirectory (relative to the main",
83 "Citadel directory - do not type an absolute pathname!) in",
84 "which to place arriving file transfers that otherwise",
88 "Specify the TCP port number on which your server will run. Normally, this",
89 "will be port 504, which is the official port assigned by the IANA for",
90 "Citadel servers. You'll only need to specify a different port number if",
91 "you run multiple BBS's on the same computer and there's something else",
92 "already using port 504.",
122 "Setup has detected that you currently have data files from a Citadel/UX",
123 "version 3.2x installation. The program 'conv_32_40' can upgrade your",
124 "files to version 4.0x format.",
125 " Setup will now exit. Please either run 'conv_32_40' or delete your data",
126 "files, and run setup again.",
132 struct config config;
135 void cleanup(int exitcode)
138 if (setup_type == UI_CURSES) {
145 /* Do an 'init q' if we need to. When we hit the right one, init
146 * will take over and setup won't come back because we didn't do a
147 * fork(). If init isn't found, we fall through the bottom of the
148 * loop and setup exits quietly.
151 execlp("/sbin/init", "init", "q", NULL);
152 execlp("/usr/sbin/init", "init", "q", NULL);
153 execlp("/bin/init", "init", "q", NULL);
154 execlp("/usr/bin/init", "init", "q", NULL);
155 execlp("init", "init", "q", NULL);
161 /* Gets a line from the terminal */
162 /* Where on the screen to start */
163 /* Pointer to string buffer */
164 /* Maximum length - if negative, no-show */
166 void getlin(int yp, int xp, char *string, int lim) {
177 for (a = 0; a < lim; ++a)
181 for (a = 0; a < lim; ++a)
184 printw("%s", string);
185 GLA:move(yp, xp + strlen(string));
193 if ((a == 8) && (strlen(string) == 0))
195 if ((a != 13) && (a != 8) && (strlen(string) == lim))
197 if ((a == 8) && (string[0] != 0)) {
198 string[strlen(string) - 1] = 0;
199 move(yp, xp + strlen(string));
203 if ((a == 13) || (a == 10)) {
206 for (a = 0; a < lim; ++a)
208 mvprintw(yp, xp, "%s", string);
225 void title(char *text)
227 if (setup_type == UI_TEXT) {
228 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
233 void hit_any_key(void)
238 if (setup_type == UI_CURSES) {
239 mvprintw(20, 0, "Press any key to continue... ");
245 if (setup_type == UI_TEXT) {
246 printf("Press return to continue...");
247 fgets(junk, 5, stdin);
251 int yesno(char *question)
256 switch (setup_type) {
260 printf("%s\nYes/No --> ", question);
261 fgets(buf, 4096, stdin);
262 answer = tolower(buf[0]);
265 else if (answer == 'n')
267 } while ((answer < 0) || (answer > 1));
275 mvprintw(1, 20, "Question");
277 mvprintw(10, 0, "%-80s", question);
278 mvprintw(20, 0, "%80s", "");
279 mvprintw(20, 0, "Yes/No -> ");
282 answer = tolower(answer);
285 else if (answer == 'n')
287 } while ((answer < 0) || (answer > 1));
297 void get_setup_msg(char *dispbuf, int msgnum)
303 while (atol(setup_text[a]) != msgnum)
308 strcat(dispbuf, setup_text[a++]);
309 strcat(dispbuf, "\n");
310 } while (atol(setup_text[a]) != (msgnum + 1));
313 void print_setup(int msgnum)
317 get_setup_msg(dispbuf, msgnum);
318 printf("\n\n%s\n\n", dispbuf);
322 void important_message(char *title, char *msgtext)
325 switch (setup_type) {
328 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");
329 printf(" %s \n\n%s\n\n", title, msgtext);
338 printw(" Important Message ");
341 printw("%s", msgtext);
350 void important_msgnum(int msgnum)
354 get_setup_msg(dispbuf, msgnum);
355 important_message("Important Message", dispbuf);
358 void display_error(char *error_message)
360 important_message("Error", error_message);
363 void progress(char *text, long int curr, long int cmax)
365 static long dots_printed;
368 switch (setup_type) {
372 printf("%s\n", text);
373 printf("..........................");
374 printf("..........................");
375 printf("..........................\r");
378 } else if (curr == cmax) {
379 printf("\r%79s\n", "");
381 a = (curr * 100) / cmax;
384 while (dots_printed < a) {
397 printw("%s\n", text);
399 printf("..........................");
400 printf("..........................");
401 printf("..........................\r");
404 } else if (curr == cmax) {
408 a = (curr * 100) / cmax;
413 while (dots_printed < a) {
428 * check_services_entry() -- Make sure "citadel" is in /etc/services
431 void check_services_entry(void)
436 snprintf(question, sizeof question,
437 "There is no '%s' entry in /etc/services. Would you like to add one?",
440 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
441 if (yesno(question) == 1) {
442 sfp = fopen("/etc/services", "a");
444 display_error(strerror(errno));
446 fprintf(sfp, "%s 504/tcp\n",
456 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
459 void check_inittab_entry(void)
463 char looking_for[SIZ];
469 /* Determine the fully qualified path name of citserver */
470 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
472 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
473 * an entry is found which we believe starts citserver.
475 infp = fopen("/etc/inittab", "r");
479 while (fgets(buf, sizeof buf, infp) != NULL) {
480 buf[strlen(buf) - 1] = 0;
481 ptr = strtok(buf, ":");
482 ptr = strtok(NULL, ":");
483 ptr = strtok(NULL, ":");
484 ptr = strtok(NULL, ":");
486 if (!strncmp(ptr, looking_for,
487 strlen(looking_for))) {
495 /* If there's already an entry, then we have nothing left to do. */
499 /* Otherwise, prompt the user to create an entry. */
500 snprintf(question, sizeof question,
501 "There is no '%s' entry in /etc/inittab.\n"
502 "Would you like to add one?",
504 if (yesno(question) == 0)
507 /* Generate a unique entry name for /etc/inittab */
508 snprintf(entryname, sizeof entryname, "c0");
511 if (entryname[1] > '9') {
514 if (entryname[0] > 'z') {
516 "Can't generate a unique entry name");
520 snprintf(buf, sizeof buf,
521 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
522 } while (system(buf) == 0);
524 /* Now write it out to /etc/inittab */
525 infp = fopen("/etc/inittab", "a");
527 display_error(strerror(errno));
529 fprintf(infp, "# Start the Citadel/UX server...\n");
530 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
531 entryname, looking_for, setup_directory);
539 void set_str_val(int msgpos, char str[])
545 strcpy(tempfile, tmpnam(NULL));
546 strcpy(setupmsg, "");
548 switch (setup_type) {
550 title(setup_titles[msgpos]);
552 printf("This is currently set to:\n%s\n", str);
553 printf("Enter new value or press return to leave unchanged:\n");
554 fgets(buf, 4096, stdin);
555 buf[strlen(buf) - 1] = 0;
556 if (strlen(buf) != 0)
562 move(1, ((80 - strlen(setup_titles[msgpos])) / 2));
564 printw("%s", setup_titles[msgpos]);
567 get_setup_msg(setupmsg, msgpos);
568 printw("%s", setupmsg);
570 getlin(20, 0, str, 80);
576 void set_int_val(int msgpos, int *ip)
579 snprintf(buf, sizeof buf, "%d", (int) *ip);
580 set_str_val(msgpos, buf);
585 void set_char_val(int msgpos, char *ip)
588 snprintf(buf, sizeof buf, "%d", (int) *ip);
589 set_str_val(msgpos, buf);
590 *ip = (char) atoi(buf);
594 void set_long_val(int msgpos, long int *ip)
597 snprintf(buf, sizeof buf, "%ld", *ip);
598 set_str_val(msgpos, buf);
603 void edit_value(int curr)
611 set_str_val(curr, config.c_sysadm);
616 set_long_val(curr, &l);
621 set_str_val(curr, config.c_bucket_dir);
622 config.c_bucket_dir[14] = 0;
623 for (a = 0; a < strlen(config.c_bucket_dir); ++a)
624 if (!isalpha(config.c_bucket_dir[a]))
625 strcpy(&config.c_bucket_dir[a],
626 &config.c_bucket_dir[a + 1]);
630 set_int_val(curr, &config.c_port_number);
638 * (re-)write the config data to disk
640 void write_config_to_disk(void)
645 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
646 display_error("setup: cannot open citadel.config");
649 fp = fdopen(fd, "wb");
651 display_error("setup: cannot open citadel.config");
654 fwrite((char *) &config, sizeof(struct config), 1, fp);
662 * Figure out what type of user interface we're going to use
664 int discover_ui(void)
677 int main(int argc, char *argv[])
683 int old_setup_level = 0;
685 struct utsname my_utsname;
690 /* set an invalid setup type */
693 /* parse command line args */
694 for (a = 0; a < argc; ++a) {
695 if (!strncmp(argv[a], "-u", 2)) {
696 strcpy(aaa, argv[a]);
697 strcpy(aaa, &aaa[2]);
698 setup_type = atoi(aaa);
700 if (!strcmp(argv[a], "-i")) {
703 if (!strcmp(argv[a], "-q")) {
704 setup_type = UI_SILENT;
709 /* If a setup type was not specified, try to determine automatically
710 * the best one to use out of all available types.
712 if (setup_type < 0) {
713 setup_type = discover_ui();
716 if (setup_type == UI_CURSES) {
723 if (info_only == 1) {
724 important_message("Citadel/UX Setup", CITADEL);
727 /* Get started in a valid setup directory. */
728 strcpy(setup_directory, BBSDIR);
729 set_str_val(0, setup_directory);
730 if (chdir(setup_directory) != 0) {
731 important_message("Citadel/UX Setup",
732 "The directory you specified does not exist.");
735 /* Determine our host name, in case we need to use it as a default */
739 switch (setup_type) {
743 " *** Citadel/UX setup program ***\n\n");
749 * What we're going to try to do here is append a whole bunch of
750 * nulls to the citadel.config file, so we can keep the old config
751 * values if they exist, but if the file is missing or from an
752 * earlier version with a shorter config structure, when setup tries
753 * to read the old config parameters, they'll all come up zero.
754 * The length of the config file will be set to what it's supposed
755 * to be when we rewrite it, because we replace the old file with a
756 * completely new copy. (Neat, eh?)
759 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
760 S_IRUSR | S_IWUSR)) == -1) {
761 display_error("setup: cannot append citadel.config");
764 fp = fdopen(a, "ab");
766 display_error("setup: cannot append citadel.config");
769 for (a = 0; a < sizeof(struct config); ++a)
773 /* now we re-open it, and read the old or blank configuration */
774 fp = fopen("citadel.config", "rb");
776 display_error("setup: cannot open citadel.config");
779 fread((char *) &config, sizeof(struct config), 1, fp);
783 /* set some sample/default values in place of blanks... */
784 if (strlen(config.c_nodename) == 0)
785 safestrncpy(config.c_nodename, my_utsname.nodename,
786 sizeof config.c_nodename);
787 strtok(config.c_nodename, ".");
788 if (strlen(config.c_fqdn) == 0) {
789 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
790 safestrncpy(config.c_fqdn, he->h_name,
791 sizeof config.c_fqdn);
793 safestrncpy(config.c_fqdn, my_utsname.nodename,
794 sizeof config.c_fqdn);
796 if (strlen(config.c_humannode) == 0)
797 strcpy(config.c_humannode, "My System");
798 if (strlen(config.c_phonenum) == 0)
799 strcpy(config.c_phonenum, "US 800 555 1212");
800 if (config.c_initax == 0) {
803 if (strlen(config.c_moreprompt) == 0)
804 strcpy(config.c_moreprompt, "<more>");
805 if (strlen(config.c_twitroom) == 0)
806 strcpy(config.c_twitroom, "Trashcan");
807 if (strlen(config.c_bucket_dir) == 0)
808 strcpy(config.c_bucket_dir, "bitbucket");
809 if (strlen(config.c_net_password) == 0)
810 strcpy(config.c_net_password, "netpassword");
811 if (strlen(config.c_baseroom) == 0)
812 strcpy(config.c_baseroom, "Lobby");
813 if (strlen(config.c_aideroom) == 0)
814 strcpy(config.c_aideroom, "Aide");
815 if (config.c_port_number == 0) {
816 config.c_port_number = 504;
818 if (config.c_ipgm_secret == 0) {
820 config.c_ipgm_secret = rand();
822 if (config.c_sleeping == 0) {
823 config.c_sleeping = 900;
825 if (config.c_bbsuid == 0) {
826 pw = getpwnam("citadel");
828 config.c_bbsuid = pw->pw_uid;
830 if (config.c_bbsuid == 0) {
831 pw = getpwnam("bbs");
833 config.c_bbsuid = pw->pw_uid;
835 if (config.c_bbsuid == 0) {
836 pw = getpwnam("guest");
838 config.c_bbsuid = pw->pw_uid;
840 if (config.c_createax == 0) {
841 config.c_createax = 3;
844 * Negative values for maxsessions are not allowed.
846 if (config.c_maxsessions < 0) {
847 config.c_maxsessions = 0;
849 /* We need a system default message expiry policy, because this is
850 * the top level and there's no 'higher' policy to fall back on.
852 if (config.c_ep.expire_mode == 0) {
853 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
854 config.c_ep.expire_value = 150;
858 * Default port numbers for various services
860 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
861 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
862 if (config.c_imap_port == 0) config.c_imap_port = 143;
865 /* Go through a series of dialogs prompting for config info */
866 if (setup_type != UI_SILENT) {
867 for (curr = 1; curr <= MAXSETUP; ++curr) {
873 if (setuid(config.c_bbsuid) != 0) {
874 important_message("Citadel/UX Setup",
875 "Failed to change the user ID to your BBS user.");
880 /***** begin version update section ***** */
881 /* take care of any updating that is necessary */
883 old_setup_level = config.c_setup_level;
885 if (old_setup_level == 0)
888 if (old_setup_level < 323) {
889 important_message("Citadel/UX Setup",
890 "This Citadel/UX installation is too old "
894 write_config_to_disk();
896 if ((config.c_setup_level / 10) == 32) {
897 important_msgnum(31);
900 if (config.c_setup_level < 400) {
901 config.c_setup_level = 400;
903 /* end of 3.23 -> 4.00 update section */
905 /* end of 4.00 -> 4.02 update section */
907 old_setup_level = config.c_setup_level;
909 /* end of version update section */
912 config.c_setup_level = REV_LEVEL;
914 /******************************************/
916 write_config_to_disk();
920 mkdir("userpics", 0700);
921 mkdir("messages", 0700);
923 mkdir("images", 0700);
924 mkdir("netconfigs", 0700);
925 mkdir(config.c_bucket_dir, 0700);
927 /* Delete a bunch of old files from Citadel v4; don't need anymore */
928 system("rm -fr ./chatpipes ./expressmsgs sessions 2>/dev/null");
930 check_services_entry(); /* Check /etc/services */
931 check_inittab_entry(); /* Check /etc/inittab */
933 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
938 progress("Setting file permissions", 0, 5);
939 chown(".", config.c_bbsuid, gid);
940 progress("Setting file permissions", 1, 5);
941 chown("citadel.config", config.c_bbsuid, gid);
942 progress("Setting file permissions", 2, 5);
943 snprintf(aaa, sizeof aaa,
944 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
945 (long)config.c_bbsuid, (long)gid);
947 progress("Setting file permissions", 3, 5);
948 chmod("citadel.config", S_IRUSR | S_IWUSR);
949 progress("Setting file permissions", 4, 5);
951 important_message("Setup finished",
952 "Setup is finished. You may now start the Citadel server.");