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)
43 #define UI_TEXT 0 /* Default setup type -- text only */
44 #define UI_DIALOG 1 /* Use the 'dialog' program (REMOVED) */
45 #define UI_CURSES 2 /* Use curses */
46 #define UI_SILENT 3 /* Silent running, for use in scripts */
48 #define SERVICE_NAME "citadel"
49 #define PROTO_NAME "tcp"
52 char setup_directory[128];
55 char *setup_titles[] =
58 "System Administrator",
60 "Name of bit bucket subdirectory",
69 "Enter the full pathname of the directory in which the BBS you are",
70 "creating or updating resides. If you specify a directory other than the",
71 "default, you will need to specify the -h flag to the server when you start",
75 "Enter the name of the system administrator (which is probably you).",
76 "When an account is created with this name, it will automatically be",
77 "assigned the highest access level.",
80 "You should create a user called 'bbs', 'guest', 'citadel', or something",
81 "similar, that will allow users a way into your BBS. The server will run",
82 "under this user ID. Please specify that (numeric) user ID here.",
85 "Select the name of a subdirectory (relative to the main",
86 "Citadel directory - do not type an absolute pathname!) in",
87 "which to place arriving file transfers that otherwise",
91 "Specify the TCP port number on which your server will run. Normally, this",
92 "will be port 504, which is the official port assigned by the IANA for",
93 "Citadel servers. You'll only need to specify a different port number if",
94 "you run multiple BBS's on the same computer and there's something else",
95 "already using port 504.",
125 "Setup has detected that you currently have data files from a Citadel/UX",
126 "version 3.2x installation. The program 'conv_32_40' can upgrade your",
127 "files to version 4.0x format.",
128 " Setup will now exit. Please either run 'conv_32_40' or delete your data",
129 "files, and run setup again.",
135 struct config config;
138 void cleanup(int exitcode)
141 if (setup_type == UI_CURSES) {
148 /* Do an 'init q' if we need to. When we hit the right one, init
149 * will take over and setup won't come back because we didn't do a
150 * fork(). If init isn't found, we fall through the bottom of the
151 * loop and setup exits quietly.
154 execlp("/sbin/init", "init", "q", NULL);
155 execlp("/usr/sbin/init", "init", "q", NULL);
156 execlp("/bin/init", "init", "q", NULL);
157 execlp("/usr/bin/init", "init", "q", NULL);
158 execlp("init", "init", "q", NULL);
164 /* Gets a line from the terminal */
165 /* Where on the screen to start */
166 /* Pointer to string buffer */
167 /* Maximum length - if negative, no-show */
169 void getlin(int yp, int xp, char *string, int lim) {
180 for (a = 0; a < lim; ++a)
184 for (a = 0; a < lim; ++a)
187 printw("%s", string);
188 GLA:move(yp, xp + strlen(string));
196 if ((a == 8) && (strlen(string) == 0))
198 if ((a != 13) && (a != 8) && (strlen(string) == lim))
200 if ((a == 8) && (string[0] != 0)) {
201 string[strlen(string) - 1] = 0;
202 move(yp, xp + strlen(string));
206 if ((a == 13) || (a == 10)) {
209 for (a = 0; a < lim; ++a)
211 mvprintw(yp, xp, "%s", string);
228 void title(char *text)
230 if (setup_type == UI_TEXT) {
231 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
236 void hit_any_key(void)
241 if (setup_type == UI_CURSES) {
242 mvprintw(20, 0, "Press any key to continue... ");
248 if (setup_type == UI_TEXT) {
249 printf("Press return to continue...");
250 fgets(junk, 5, stdin);
254 int yesno(char *question)
259 switch (setup_type) {
263 printf("%s\nYes/No --> ", question);
264 fgets(buf, 4096, stdin);
265 answer = tolower(buf[0]);
268 else if (answer == 'n')
270 } while ((answer < 0) || (answer > 1));
278 mvprintw(1, 20, "Question");
280 mvprintw(10, 0, "%-80s", question);
281 mvprintw(20, 0, "%80s", "");
282 mvprintw(20, 0, "Yes/No -> ");
285 answer = tolower(answer);
288 else if (answer == 'n')
290 } while ((answer < 0) || (answer > 1));
300 void get_setup_msg(char *dispbuf, int msgnum)
306 while (atol(setup_text[a]) != msgnum)
311 strcat(dispbuf, setup_text[a++]);
312 strcat(dispbuf, "\n");
313 } while (atol(setup_text[a]) != (msgnum + 1));
316 void print_setup(int msgnum)
320 get_setup_msg(dispbuf, msgnum);
321 printf("\n\n%s\n\n", dispbuf);
325 void important_message(char *title, char *msgtext)
328 switch (setup_type) {
331 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");
332 printf(" %s \n\n%s\n\n", title, msgtext);
341 printw(" Important Message ");
344 printw("%s", msgtext);
353 void important_msgnum(int msgnum)
357 get_setup_msg(dispbuf, msgnum);
358 important_message("Important Message", dispbuf);
361 void display_error(char *error_message)
363 important_message("Error", error_message);
366 void progress(char *text, long int curr, long int cmax)
368 static long dots_printed;
371 switch (setup_type) {
375 printf("%s\n", text);
376 printf("..........................");
377 printf("..........................");
378 printf("..........................\r");
381 } else if (curr == cmax) {
382 printf("\r%79s\n", "");
384 a = (curr * 100) / cmax;
387 while (dots_printed < a) {
400 printw("%s\n", text);
402 printf("..........................");
403 printf("..........................");
404 printf("..........................\r");
407 } else if (curr == cmax) {
411 a = (curr * 100) / cmax;
416 while (dots_printed < a) {
431 * check_services_entry() -- Make sure "citadel" is in /etc/services
434 void check_services_entry(void)
439 snprintf(question, sizeof question,
440 "There is no '%s' entry in /etc/services. Would you like to add one?",
443 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
444 if (yesno(question) == 1) {
445 sfp = fopen("/etc/services", "a");
447 display_error(strerror(errno));
449 fprintf(sfp, "%s 504/tcp\n",
459 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
462 void check_inittab_entry(void)
466 char looking_for[SIZ];
472 /* Determine the fully qualified path name of citserver */
473 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
475 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
476 * an entry is found which we believe starts citserver.
478 infp = fopen("/etc/inittab", "r");
482 while (fgets(buf, sizeof buf, infp) != NULL) {
483 buf[strlen(buf) - 1] = 0;
484 ptr = strtok(buf, ":");
485 ptr = strtok(NULL, ":");
486 ptr = strtok(NULL, ":");
487 ptr = strtok(NULL, ":");
489 if (!strncmp(ptr, looking_for,
490 strlen(looking_for))) {
498 /* If there's already an entry, then we have nothing left to do. */
502 /* Otherwise, prompt the user to create an entry. */
503 snprintf(question, sizeof question,
504 "There is no '%s' entry in /etc/inittab.\n"
505 "Would you like to add one?",
507 if (yesno(question) == 0)
510 /* Generate a unique entry name for /etc/inittab */
511 snprintf(entryname, sizeof entryname, "c0");
514 if (entryname[1] > '9') {
517 if (entryname[0] > 'z') {
519 "Can't generate a unique entry name");
523 snprintf(buf, sizeof buf,
524 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
525 } while (system(buf) == 0);
527 /* Now write it out to /etc/inittab */
528 infp = fopen("/etc/inittab", "a");
530 display_error(strerror(errno));
532 fprintf(infp, "# Start the Citadel/UX server...\n");
533 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
534 entryname, looking_for, setup_directory);
542 void set_str_val(int msgpos, char str[])
548 strcpy(tempfile, tmpnam(NULL));
549 strcpy(setupmsg, "");
551 switch (setup_type) {
553 title(setup_titles[msgpos]);
555 printf("This is currently set to:\n%s\n", str);
556 printf("Enter new value or press return to leave unchanged:\n");
557 fgets(buf, 4096, stdin);
558 buf[strlen(buf) - 1] = 0;
559 if (strlen(buf) != 0)
565 move(1, ((80 - strlen(setup_titles[msgpos])) / 2));
567 printw("%s", setup_titles[msgpos]);
570 get_setup_msg(setupmsg, msgpos);
571 printw("%s", setupmsg);
573 getlin(20, 0, str, 80);
579 void set_int_val(int msgpos, int *ip)
582 snprintf(buf, sizeof buf, "%d", (int) *ip);
583 set_str_val(msgpos, buf);
588 void set_char_val(int msgpos, char *ip)
591 snprintf(buf, sizeof buf, "%d", (int) *ip);
592 set_str_val(msgpos, buf);
593 *ip = (char) atoi(buf);
597 void set_long_val(int msgpos, long int *ip)
600 snprintf(buf, sizeof buf, "%ld", *ip);
601 set_str_val(msgpos, buf);
606 void edit_value(int curr)
614 set_str_val(curr, config.c_sysadm);
619 set_long_val(curr, &l);
624 set_str_val(curr, config.c_bucket_dir);
625 config.c_bucket_dir[14] = 0;
626 for (a = 0; a < strlen(config.c_bucket_dir); ++a)
627 if (!isalpha(config.c_bucket_dir[a]))
628 strcpy(&config.c_bucket_dir[a],
629 &config.c_bucket_dir[a + 1]);
633 set_int_val(curr, &config.c_port_number);
641 * (re-)write the config data to disk
643 void write_config_to_disk(void)
648 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
649 display_error("setup: cannot open citadel.config");
652 fp = fdopen(fd, "wb");
654 display_error("setup: cannot open citadel.config");
657 fwrite((char *) &config, sizeof(struct config), 1, fp);
665 * Figure out what type of user interface we're going to use
667 int discover_ui(void)
680 int main(int argc, char *argv[])
686 int old_setup_level = 0;
688 struct utsname my_utsname;
693 /* set an invalid setup type */
696 /* parse command line args */
697 for (a = 0; a < argc; ++a) {
698 if (!strncmp(argv[a], "-u", 2)) {
699 strcpy(aaa, argv[a]);
700 strcpy(aaa, &aaa[2]);
701 setup_type = atoi(aaa);
703 if (!strcmp(argv[a], "-i")) {
706 if (!strcmp(argv[a], "-q")) {
707 setup_type = UI_SILENT;
712 /* If a setup type was not specified, try to determine automatically
713 * the best one to use out of all available types.
715 if (setup_type < 0) {
716 setup_type = discover_ui();
719 if (setup_type == UI_CURSES) {
726 if (info_only == 1) {
727 important_message("Citadel/UX Setup", CITADEL);
730 /* Get started in a valid setup directory. */
731 strcpy(setup_directory, BBSDIR);
732 set_str_val(0, setup_directory);
733 if (chdir(setup_directory) != 0) {
734 important_message("Citadel/UX Setup",
735 "The directory you specified does not exist.");
738 /* Determine our host name, in case we need to use it as a default */
742 switch (setup_type) {
746 " *** Citadel/UX setup program ***\n\n");
752 * What we're going to try to do here is append a whole bunch of
753 * nulls to the citadel.config file, so we can keep the old config
754 * values if they exist, but if the file is missing or from an
755 * earlier version with a shorter config structure, when setup tries
756 * to read the old config parameters, they'll all come up zero.
757 * The length of the config file will be set to what it's supposed
758 * to be when we rewrite it, because we replace the old file with a
759 * completely new copy. (Neat, eh?)
762 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
763 S_IRUSR | S_IWUSR)) == -1) {
764 display_error("setup: cannot append citadel.config");
767 fp = fdopen(a, "ab");
769 display_error("setup: cannot append citadel.config");
772 for (a = 0; a < sizeof(struct config); ++a)
776 /* now we re-open it, and read the old or blank configuration */
777 fp = fopen("citadel.config", "rb");
779 display_error("setup: cannot open citadel.config");
782 fread((char *) &config, sizeof(struct config), 1, fp);
786 /* set some sample/default values in place of blanks... */
787 if (strlen(config.c_nodename) == 0)
788 safestrncpy(config.c_nodename, my_utsname.nodename,
789 sizeof config.c_nodename);
790 strtok(config.c_nodename, ".");
791 if (strlen(config.c_fqdn) == 0) {
792 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
793 safestrncpy(config.c_fqdn, he->h_name,
794 sizeof config.c_fqdn);
796 safestrncpy(config.c_fqdn, my_utsname.nodename,
797 sizeof config.c_fqdn);
799 if (strlen(config.c_humannode) == 0)
800 strcpy(config.c_humannode, "My System");
801 if (strlen(config.c_phonenum) == 0)
802 strcpy(config.c_phonenum, "US 800 555 1212");
803 if (config.c_initax == 0) {
806 if (strlen(config.c_moreprompt) == 0)
807 strcpy(config.c_moreprompt, "<more>");
808 if (strlen(config.c_twitroom) == 0)
809 strcpy(config.c_twitroom, "Trashcan");
810 if (strlen(config.c_bucket_dir) == 0)
811 strcpy(config.c_bucket_dir, "bitbucket");
812 if (strlen(config.c_net_password) == 0)
813 strcpy(config.c_net_password, "netpassword");
814 if (config.c_port_number == 0) {
815 config.c_port_number = 504;
817 if (config.c_ipgm_secret == 0) {
819 config.c_ipgm_secret = rand();
821 if (config.c_sleeping == 0) {
822 config.c_sleeping = 900;
824 if (config.c_bbsuid == 0) {
825 pw = getpwnam("citadel");
827 config.c_bbsuid = pw->pw_uid;
829 if (config.c_bbsuid == 0) {
830 pw = getpwnam("bbs");
832 config.c_bbsuid = pw->pw_uid;
834 if (config.c_bbsuid == 0) {
835 pw = getpwnam("guest");
837 config.c_bbsuid = pw->pw_uid;
839 if (config.c_createax == 0) {
840 config.c_createax = 3;
843 * Negative values for maxsessions are not allowed.
845 if (config.c_maxsessions < 0) {
846 config.c_maxsessions = 0;
848 /* We need a system default message expiry policy, because this is
849 * the top level and there's no 'higher' policy to fall back on.
851 if (config.c_ep.expire_mode == 0) {
852 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
853 config.c_ep.expire_value = 150;
857 * Default port numbers for various services
859 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
860 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
861 if (config.c_imap_port == 0) config.c_imap_port = 143;
864 /* Go through a series of dialogs prompting for config info */
865 if (setup_type != UI_SILENT) {
866 for (curr = 1; curr <= MAXSETUP; ++curr) {
872 if (setuid(config.c_bbsuid) != 0) {
873 important_message("Citadel/UX Setup",
874 "Failed to change the user ID to your BBS user.");
879 /***** begin version update section ***** */
880 /* take care of any updating that is necessary */
882 old_setup_level = config.c_setup_level;
884 if (old_setup_level == 0)
887 if (old_setup_level < 323) {
888 important_message("Citadel/UX Setup",
889 "This Citadel/UX installation is too old "
893 write_config_to_disk();
895 if ((config.c_setup_level / 10) == 32) {
896 important_msgnum(31);
899 if (config.c_setup_level < 400) {
900 config.c_setup_level = 400;
902 /* end of 3.23 -> 4.00 update section */
904 /* end of 4.00 -> 4.02 update section */
906 old_setup_level = config.c_setup_level;
908 /* end of version update section */
911 config.c_setup_level = REV_LEVEL;
913 /******************************************/
915 write_config_to_disk();
919 mkdir("userpics", 0700);
920 mkdir("messages", 0700);
922 mkdir("images", 0700);
923 mkdir("netconfigs", 0700);
924 mkdir(config.c_bucket_dir, 0700);
926 /* Delete a bunch of old files from Citadel v4; don't need anymore */
927 system("rm -fr ./chatpipes ./expressmsgs sessions 2>/dev/null");
929 check_services_entry(); /* Check /etc/services */
930 check_inittab_entry(); /* Check /etc/inittab */
932 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
937 progress("Setting file permissions", 0, 5);
938 chown(".", config.c_bbsuid, gid);
939 progress("Setting file permissions", 1, 5);
940 chown("citadel.config", config.c_bbsuid, gid);
941 progress("Setting file permissions", 2, 5);
942 snprintf(aaa, sizeof aaa,
943 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
944 (long)config.c_bbsuid, (long)gid);
946 progress("Setting file permissions", 3, 5);
947 chmod("citadel.config", S_IRUSR | S_IWUSR);
948 progress("Setting file permissions", 4, 5);
950 important_message("Setup finished",
951 "Setup is finished. You may now start the Citadel server.");