4 * Citadel/UX setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
29 #ifndef DISABLE_CURSES
30 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
39 #define MAXSETUP 3 /* How many setup questions to ask */
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];
53 char *setup_titles[] =
56 "System Administrator",
64 "Enter the full pathname of the directory in which the BBS you are\n"
65 "creating or updating resides. If you specify a directory other than the\n"
66 "default, you will need to specify the -h flag to the server when you start\n"
69 "Enter the name of the system administrator (which is probably you).\n"
70 "When an account is created with this name, it will automatically be\n"
71 "assigned the highest access level.\n",
73 "You should create a user called 'bbs', 'guest', 'citadel', or something\n"
74 "similar, that will allow users a way into your BBS. The server will run\n"
75 "under this user ID. Please specify that (numeric) user ID here.\n",
77 "Specify the TCP port number on which your server will run. Normally, this\n"
78 "will be port 504, which is the official port assigned by the IANA for\n"
79 "Citadel servers. You'll only need to specify a different port number if\n"
80 "you run multiple BBS's on the same computer and there's something else\n"
81 "already using port 504.\n",
83 "Setup has detected that you currently have data files from a Citadel/UX\n"
84 "version 3.2x installation. The program 'conv_32_40' can upgrade your\n"
85 "files to version 4.0x format.\n"
86 " Setup will now exit. Please either run 'conv_32_40' or delete your data\n"
87 "files, and run setup again.\n"
95 * Do an "init q" to tell init to re-read its configuration file
104 * We can't guarantee that telinit or init will be in the right
105 * place, so we try a couple of different paths. The first one
106 * will work 99% of the time, though.
108 execlp("/sbin/telinit", "telinit", "q", NULL);
109 execlp("/sbin/init", "init", "q", NULL);
110 execlp("/usr/sbin/init", "init", "q", NULL);
111 execlp("/bin/init", "init", "q", NULL);
112 execlp("/usr/bin/init", "init", "q", NULL);
113 execlp("init", "init", "q", NULL);
116 * Didn't find it? Fail silently. Perhaps we're running on
117 * some sort of BSD system and there's no init at all. If so,
118 * the person installing Citadel probably knows how to handle
119 * this task manually.
124 while (waitpid(cpid, &status, 0) == -1) ;;
130 * Set an entry in inittab to the desired state
132 void set_init_entry(char *which_entry, char *new_state) {
133 char *inittab = NULL;
141 inittab = strdup("");
142 if (inittab == NULL) return;
144 fp = fopen("/etc/inittab", "r");
145 if (fp == NULL) return;
147 while(fgets(buf, sizeof buf, fp) != NULL) {
149 if (num_tokens(buf, ':') == 4) {
150 extract_token(entry, buf, 0, ':');
151 extract_token(levels, buf, 1, ':');
152 extract_token(state, buf, 2, ':');
153 extract_token(prog, buf, 3, ':'); /* includes 0x0a LF */
155 if (!strcmp(entry, which_entry)) {
156 strcpy(state, new_state);
157 sprintf(buf, "%s:%s:%s:%s",
158 entry, levels, state, prog);
162 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
163 if (inittab == NULL) {
168 strcat(inittab, buf);
171 fp = fopen("/etc/inittab", "w");
173 fwrite(inittab, strlen(inittab), 1, fp);
184 * Shut down the Citadel service if necessary, during setup.
186 void shutdown_service(void) {
189 char looking_for[SIZ];
194 strcpy(init_entry, "");
196 /* Determine the fully qualified path name of citserver */
197 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
199 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
200 * an entry is found which we believe starts citserver.
202 infp = fopen("/etc/inittab", "r");
206 while (fgets(buf, sizeof buf, infp) != NULL) {
207 buf[strlen(buf) - 1] = 0;
208 extract_token(entry, buf, 0, ':');
209 extract_token(prog, buf, 3, ':');
210 if (!strncasecmp(prog, looking_for,
211 strlen(looking_for))) {
213 strcpy(init_entry, entry);
219 /* Bail out if there's nothing to do. */
220 if (!have_entry) return;
222 set_init_entry(init_entry, "off");
227 * Start the Citadel service.
229 void start_the_service(void) {
230 if (strlen(init_entry) > 0) {
231 set_init_entry(init_entry, "respawn");
237 void cleanup(int exitcode)
239 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
240 if (setup_type == UI_CURSES) {
251 /* Gets a line from the terminal */
252 /* Where on the screen to start */
253 /* Pointer to string buffer */
254 /* Maximum length - if negative, no-show */
255 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
256 void getlin(int yp, int xp, char *string, int lim) {
267 for (a = 0; a < lim; ++a)
271 for (a = 0; a < lim; ++a)
274 printw("%s", string);
275 GLA:move(yp, xp + strlen(string));
283 if ((a == 8) && (strlen(string) == 0))
285 if ((a != 13) && (a != 8) && (strlen(string) == lim))
287 if ((a == 8) && (string[0] != 0)) {
288 string[strlen(string) - 1] = 0;
289 move(yp, xp + strlen(string));
293 if ((a == 13) || (a == 10)) {
296 for (a = 0; a < lim; ++a)
298 mvprintw(yp, xp, "%s", string);
315 void title(char *text)
317 if (setup_type == UI_TEXT) {
318 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
323 void hit_any_key(void)
327 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
328 if (setup_type == UI_CURSES) {
329 mvprintw(20, 0, "Press any key to continue... ");
335 if (setup_type == UI_TEXT) {
336 printf("Press return to continue...");
337 fgets(junk, 5, stdin);
341 int yesno(char *question)
346 switch (setup_type) {
350 printf("%s\nYes/No --> ", question);
351 fgets(buf, 4096, stdin);
352 answer = tolower(buf[0]);
355 else if (answer == 'n')
357 } while ((answer < 0) || (answer > 1));
360 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
365 mvprintw(1, 20, "Question");
367 mvprintw(10, 0, "%-80s", question);
368 mvprintw(20, 0, "%80s", "");
369 mvprintw(20, 0, "Yes/No -> ");
372 answer = tolower(answer);
375 else if (answer == 'n')
377 } while ((answer < 0) || (answer > 1));
386 void important_message(char *title, char *msgtext)
389 switch (setup_type) {
392 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");
393 printf(" %s \n\n%s\n\n", title, msgtext);
397 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
402 printw(" Important Message ");
405 printw("%s", msgtext);
414 void important_msgnum(int msgnum)
416 important_message("Important Message", setup_text[msgnum]);
419 void display_error(char *error_message)
421 important_message("Error", error_message);
424 void progress(char *text, long int curr, long int cmax)
426 static long dots_printed;
429 switch (setup_type) {
433 printf("%s\n", text);
434 printf("..........................");
435 printf("..........................");
436 printf("..........................\r");
439 } else if (curr == cmax) {
440 printf("\r%79s\n", "");
442 a = (curr * 100) / cmax;
445 while (dots_printed < a) {
453 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
458 printw("%s\n", text);
460 printf("..........................");
461 printf("..........................");
462 printf("..........................\r");
465 } else if (curr == cmax) {
469 a = (curr * 100) / cmax;
474 while (dots_printed < a) {
489 * check_services_entry() -- Make sure "citadel" is in /etc/services
492 void check_services_entry(void)
497 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
498 for (i=0; i<3; ++i) {
499 progress("Adding service entry...", i, 3);
501 sfp = fopen("/etc/services", "a");
503 display_error(strerror(errno));
505 fprintf(sfp, "%s 504/tcp\n",
517 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
520 void check_inittab_entry(void)
524 char looking_for[SIZ];
528 /* Determine the fully qualified path name of citserver */
529 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
531 /* If there's already an entry, then we have nothing left to do. */
532 if (strlen(init_entry) > 0) {
536 /* Otherwise, prompt the user to create an entry. */
537 snprintf(question, sizeof question,
538 "There is no '%s' entry in /etc/inittab.\n"
539 "Would you like to add one?",
541 if (yesno(question) == 0)
544 /* Generate a unique entry name for /etc/inittab */
545 snprintf(entryname, sizeof entryname, "c0");
548 if (entryname[1] > '9') {
551 if (entryname[0] > 'z') {
553 "Can't generate a unique entry name");
557 snprintf(buf, sizeof buf,
558 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
559 } while (system(buf) == 0);
561 /* Now write it out to /etc/inittab */
562 infp = fopen("/etc/inittab", "a");
564 display_error(strerror(errno));
566 fprintf(infp, "# Start the Citadel/UX server...\n");
567 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
568 entryname, looking_for, setup_directory);
570 strcpy(init_entry, entryname);
576 void set_str_val(int msgpos, char str[])
579 char tempfile[PATH_MAX];
582 strcpy(tempfile, tmpnam(NULL));
583 strcpy(setupmsg, "");
585 switch (setup_type) {
587 title(setup_titles[msgpos]);
588 printf("\n%s\n", setup_text[msgpos]);
589 printf("This is currently set to:\n%s\n", str);
590 printf("Enter new value or press return to leave unchanged:\n");
591 fgets(buf, 4096, stdin);
592 buf[strlen(buf) - 1] = 0;
593 if (strlen(buf) != 0)
596 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
599 move(1, ((80 - strlen(setup_titles[msgpos])) / 2));
601 printw("%s", setup_titles[msgpos]);
604 printw("%s", setup_text[msgpos]);
606 getlin(20, 0, str, 80);
612 void set_int_val(int msgpos, int *ip)
615 snprintf(buf, sizeof buf, "%d", (int) *ip);
616 set_str_val(msgpos, buf);
621 void set_char_val(int msgpos, char *ip)
624 snprintf(buf, sizeof buf, "%d", (int) *ip);
625 set_str_val(msgpos, buf);
626 *ip = (char) atoi(buf);
630 void set_long_val(int msgpos, long int *ip)
633 snprintf(buf, sizeof buf, "%ld", *ip);
634 set_str_val(msgpos, buf);
639 void edit_value(int curr)
646 set_str_val(curr, config.c_sysadm);
651 set_long_val(curr, &l);
656 set_int_val(curr, &config.c_port_number);
664 * (re-)write the config data to disk
666 void write_config_to_disk(void)
671 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
672 display_error("setup: cannot open citadel.config");
675 fp = fdopen(fd, "wb");
677 display_error("setup: cannot open citadel.config");
680 fwrite((char *) &config, sizeof(struct config), 1, fp);
688 * Figure out what type of user interface we're going to use
690 int discover_ui(void)
693 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
703 int main(int argc, char *argv[])
709 int old_setup_level = 0;
711 struct utsname my_utsname;
716 /* set an invalid setup type */
719 /* parse command line args */
720 for (a = 0; a < argc; ++a) {
721 if (!strncmp(argv[a], "-u", 2)) {
722 strcpy(aaa, argv[a]);
723 strcpy(aaa, &aaa[2]);
724 setup_type = atoi(aaa);
726 if (!strcmp(argv[a], "-i")) {
729 if (!strcmp(argv[a], "-q")) {
730 setup_type = UI_SILENT;
735 /* If a setup type was not specified, try to determine automatically
736 * the best one to use out of all available types.
738 if (setup_type < 0) {
739 setup_type = discover_ui();
741 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
742 if (setup_type == UI_CURSES) {
749 if (info_only == 1) {
750 important_message("Citadel/UX Setup", CITADEL);
754 /* Get started in a valid setup directory. */
755 strcpy(setup_directory, BBSDIR);
756 set_str_val(0, setup_directory);
757 if (chdir(setup_directory) != 0) {
758 important_message("Citadel/UX Setup",
759 "The directory you specified does not exist.");
763 /* Determine our host name, in case we need to use it as a default */
766 /* See if we need to shut down the Citadel service. */
767 for (a=0; a<=5; ++a) {
768 progress("Shutting down the Citadel service...", a, 5);
769 if (a == 0) shutdown_service();
774 switch (setup_type) {
778 " *** Citadel/UX setup program ***\n\n");
784 * What we're going to try to do here is append a whole bunch of
785 * nulls to the citadel.config file, so we can keep the old config
786 * values if they exist, but if the file is missing or from an
787 * earlier version with a shorter config structure, when setup tries
788 * to read the old config parameters, they'll all come up zero.
789 * The length of the config file will be set to what it's supposed
790 * to be when we rewrite it, because we replace the old file with a
791 * completely new copy.
794 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
795 S_IRUSR | S_IWUSR)) == -1) {
796 display_error("setup: cannot append citadel.config");
799 fp = fdopen(a, "ab");
801 display_error("setup: cannot append citadel.config");
804 for (a = 0; a < sizeof(struct config); ++a)
808 /* now we re-open it, and read the old or blank configuration */
809 fp = fopen("citadel.config", "rb");
811 display_error("setup: cannot open citadel.config");
814 fread((char *) &config, sizeof(struct config), 1, fp);
817 /* set some sample/default values in place of blanks... */
818 if (strlen(config.c_nodename) == 0)
819 safestrncpy(config.c_nodename, my_utsname.nodename,
820 sizeof config.c_nodename);
821 strtok(config.c_nodename, ".");
822 if (strlen(config.c_fqdn) == 0) {
823 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
824 safestrncpy(config.c_fqdn, he->h_name,
825 sizeof config.c_fqdn);
827 safestrncpy(config.c_fqdn, my_utsname.nodename,
828 sizeof config.c_fqdn);
830 if (strlen(config.c_humannode) == 0)
831 strcpy(config.c_humannode, "My System");
832 if (strlen(config.c_phonenum) == 0)
833 strcpy(config.c_phonenum, "US 800 555 1212");
834 if (config.c_initax == 0) {
837 if (strlen(config.c_moreprompt) == 0)
838 strcpy(config.c_moreprompt, "<more>");
839 if (strlen(config.c_twitroom) == 0)
840 strcpy(config.c_twitroom, "Trashcan");
841 if (strlen(config.c_bucket_dir) == 0)
842 strcpy(config.c_bucket_dir, "bitbucket");
843 if (strlen(config.c_net_password) == 0)
844 strcpy(config.c_net_password, "netpassword");
845 if (strlen(config.c_baseroom) == 0)
846 strcpy(config.c_baseroom, "Lobby");
847 if (strlen(config.c_aideroom) == 0)
848 strcpy(config.c_aideroom, "Aide");
849 if (config.c_port_number == 0) {
850 config.c_port_number = 504;
852 if (config.c_ipgm_secret == 0) {
854 config.c_ipgm_secret = rand();
856 if (config.c_sleeping == 0) {
857 config.c_sleeping = 900;
859 if (config.c_bbsuid == 0) {
860 pw = getpwnam("citadel");
862 config.c_bbsuid = pw->pw_uid;
864 if (config.c_bbsuid == 0) {
865 pw = getpwnam("bbs");
867 config.c_bbsuid = pw->pw_uid;
869 if (config.c_bbsuid == 0) {
870 pw = getpwnam("guest");
872 config.c_bbsuid = pw->pw_uid;
874 if (config.c_createax == 0) {
875 config.c_createax = 3;
878 * Negative values for maxsessions are not allowed.
880 if (config.c_maxsessions < 0) {
881 config.c_maxsessions = 0;
883 /* We need a system default message expiry policy, because this is
884 * the top level and there's no 'higher' policy to fall back on.
886 if (config.c_ep.expire_mode == 0) {
887 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
888 config.c_ep.expire_value = 150;
892 * Default port numbers for various services
894 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
895 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
896 if (config.c_imap_port == 0) config.c_imap_port = 143;
898 /* Go through a series of dialogs prompting for config info */
899 if (setup_type != UI_SILENT) {
900 for (curr = 1; curr <= MAXSETUP; ++curr) {
906 if (setuid(config.c_bbsuid) != 0) {
907 important_message("Citadel/UX Setup",
908 "Failed to change the user ID to your BBS user.");
913 /***** begin version update section ***** */
914 /* take care of any updating that is necessary */
916 old_setup_level = config.c_setup_level;
918 if (old_setup_level == 0)
921 if (old_setup_level < 323) {
922 important_message("Citadel/UX Setup",
923 "This Citadel/UX installation is too old "
927 write_config_to_disk();
929 if ((config.c_setup_level / 10) == 32) {
930 important_msgnum(31);
933 if (config.c_setup_level < 400) {
934 config.c_setup_level = 400;
936 /* end of 3.23 -> 4.00 update section */
938 /* end of 4.00 -> 4.02 update section */
940 old_setup_level = config.c_setup_level;
942 /* end of version update section */
945 config.c_setup_level = REV_LEVEL;
947 /******************************************/
949 write_config_to_disk();
953 mkdir("userpics", 0700);
954 mkdir("messages", 0700);
956 mkdir("images", 0700);
957 mkdir("netconfigs", 0700);
958 mkdir(config.c_bucket_dir, 0700);
960 /* Delete a bunch of old files from Citadel v4; don't need anymore */
961 system("rm -fr ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
963 check_services_entry(); /* Check /etc/services */
964 check_inittab_entry(); /* Check /etc/inittab */
966 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
971 progress("Setting file permissions", 0, 5);
972 chown(".", config.c_bbsuid, gid);
973 progress("Setting file permissions", 1, 5);
974 chown("citadel.config", config.c_bbsuid, gid);
975 progress("Setting file permissions", 2, 5);
976 snprintf(aaa, sizeof aaa,
977 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
978 (long)config.c_bbsuid, (long)gid);
980 progress("Setting file permissions", 3, 5);
981 chmod("citadel.config", S_IRUSR | S_IWUSR);
982 progress("Setting file permissions", 4, 5);
984 /* See if we can start the Citadel service. */
985 if (strlen(init_entry) > 0) {
986 for (a=0; a<=5; ++a) {
987 progress("Starting the Citadel service...", a, 5);
988 if (a == 0) start_the_service();
991 important_message("Setup finished",
992 "Setup is finished. You may now log in.");
995 important_message("Setup finished",
996 "Setup is finished. You may now start the server.");