4 * Citadel/UX setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
30 #ifndef DISABLE_CURSES
31 #if defined(HAVE_CURSES_H) || defined(HAVE_NCURSES_H)
40 #define MAXSETUP 3 /* How many setup questions to ask */
42 #define UI_TEXT 0 /* Default setup type -- text only */
43 #define UI_DIALOG 1 /* Use the 'dialog' program (REMOVED) */
44 #define UI_CURSES 2 /* Use curses */
45 #define UI_SILENT 3 /* Silent running, for use in scripts */
47 #define SERVICE_NAME "citadel"
48 #define PROTO_NAME "tcp"
51 char setup_directory[SIZ];
54 char *setup_titles[] =
56 "Citadel Home Directory",
57 "System Administrator",
65 "Enter the full pathname of the directory in which the Citadel installation\n"
66 "you are creating or updating resides. If you specify a directory other\n"
67 "than the default, you will need to specify the -h flag to the server when\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 "Citadel needs to run under its own user ID. This would typically be\n"
75 "called \"citadel\", but if you are running Citadel as a public BBS, you\n"
76 "might also call it \"bbs\" or \"guest\". The server will run under this\n"
77 "user ID. Please specify that user ID here. You may specify either a\n"
78 "user name or a numeric UID.\n",
80 "Specify the TCP port number on which your server will run. Normally, this\n"
81 "will be port 504, which is the official port assigned by the IANA for\n"
82 "Citadel servers. You'll only need to specify a different port number if\n"
83 "you run multiple instances of Citadel on the same computer and there's\n"
84 "something else already using port 504.\n",
86 "Setup has detected that you currently have data files from a Citadel/UX\n"
87 "version 3.2x installation. The program 'conv_32_40' can upgrade your\n"
88 "files to version 4.0x format.\n"
89 " Setup will now exit. Please either run 'conv_32_40' or delete your data\n"
90 "files, and run setup again.\n"
98 * Set an entry in inittab to the desired state
100 void set_init_entry(char *which_entry, char *new_state) {
101 char *inittab = NULL;
109 inittab = strdup("");
110 if (inittab == NULL) return;
112 fp = fopen("/etc/inittab", "r");
113 if (fp == NULL) return;
115 while(fgets(buf, sizeof buf, fp) != NULL) {
117 if (num_tokens(buf, ':') == 4) {
118 extract_token(entry, buf, 0, ':');
119 extract_token(levels, buf, 1, ':');
120 extract_token(state, buf, 2, ':');
121 extract_token(prog, buf, 3, ':'); /* includes 0x0a LF */
123 if (!strcmp(entry, which_entry)) {
124 strcpy(state, new_state);
125 sprintf(buf, "%s:%s:%s:%s",
126 entry, levels, state, prog);
130 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
131 if (inittab == NULL) {
136 strcat(inittab, buf);
139 fp = fopen("/etc/inittab", "w");
141 fwrite(inittab, strlen(inittab), 1, fp);
143 kill(1, SIGHUP); /* Tell init to re-read /etc/inittab */
152 * Shut down the Citadel service if necessary, during setup.
154 void shutdown_service(void) {
157 char looking_for[SIZ];
162 strcpy(init_entry, "");
164 /* Determine the fully qualified path name of citserver */
165 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
167 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
168 * an entry is found which we believe starts citserver.
170 infp = fopen("/etc/inittab", "r");
174 while (fgets(buf, sizeof buf, infp) != NULL) {
175 buf[strlen(buf) - 1] = 0;
176 extract_token(entry, buf, 0, ':');
177 extract_token(prog, buf, 3, ':');
178 if (!strncasecmp(prog, looking_for,
179 strlen(looking_for))) {
181 strcpy(init_entry, entry);
187 /* Bail out if there's nothing to do. */
188 if (!have_entry) return;
190 set_init_entry(init_entry, "off");
195 * Start the Citadel service.
197 void start_the_service(void) {
198 if (strlen(init_entry) > 0) {
199 set_init_entry(init_entry, "respawn");
205 void cleanup(int exitcode)
207 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
208 if (setup_type == UI_CURSES) {
219 /* Gets a line from the terminal */
220 /* Where on the screen to start */
221 /* Pointer to string buffer */
222 /* Maximum length - if negative, no-show */
223 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
224 void getlin(int yp, int xp, char *string, int lim) {
235 for (a = 0; a < lim; ++a)
239 for (a = 0; a < lim; ++a)
242 printw("%s", string);
243 GLA:move(yp, xp + strlen(string));
251 if ((a == 8) && (strlen(string) == 0))
253 if ((a != 13) && (a != 8) && (strlen(string) == lim))
255 if ((a == 8) && (string[0] != 0)) {
256 string[strlen(string) - 1] = 0;
257 move(yp, xp + strlen(string));
261 if ((a == 13) || (a == 10)) {
264 for (a = 0; a < lim; ++a)
266 mvprintw(yp, xp, "%s", string);
283 void title(char *text)
285 if (setup_type == UI_TEXT) {
286 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
291 void hit_any_key(void)
295 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
296 if (setup_type == UI_CURSES) {
297 mvprintw(20, 0, "Press any key to continue... ");
303 if (setup_type == UI_TEXT) {
304 printf("Press return to continue...");
305 fgets(junk, 5, stdin);
309 int yesno(char *question)
314 switch (setup_type) {
318 printf("%s\nYes/No --> ", question);
319 fgets(buf, 4096, stdin);
320 answer = tolower(buf[0]);
323 else if (answer == 'n')
325 } while ((answer < 0) || (answer > 1));
328 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
333 mvprintw(1, 20, "Question");
335 mvprintw(10, 0, "%-80s", question);
336 mvprintw(20, 0, "%80s", "");
337 mvprintw(20, 0, "Yes/No -> ");
340 answer = tolower(answer);
343 else if (answer == 'n')
345 } while ((answer < 0) || (answer > 1));
354 void important_message(char *title, char *msgtext)
357 switch (setup_type) {
360 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");
361 printf(" %s \n\n%s\n\n", title, msgtext);
365 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
370 printw(" Important Message ");
373 printw("%s", msgtext);
382 void important_msgnum(int msgnum)
384 important_message("Important Message", setup_text[msgnum]);
387 void display_error(char *error_message)
389 important_message("Error", error_message);
392 void progress(char *text, long int curr, long int cmax)
394 static long dots_printed;
397 switch (setup_type) {
401 printf("%s\n", text);
402 printf("..........................");
403 printf("..........................");
404 printf("..........................\r");
407 } else if (curr == cmax) {
408 printf("\r%79s\n", "");
410 a = (curr * 100) / cmax;
413 while (dots_printed < a) {
421 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
426 printw("%s\n", text);
428 printf("..........................");
429 printf("..........................");
430 printf("..........................\r");
433 } else if (curr == cmax) {
437 a = (curr * 100) / cmax;
442 while (dots_printed < a) {
457 * check_services_entry() -- Make sure "citadel" is in /etc/services
460 void check_services_entry(void)
465 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
466 for (i=0; i<3; ++i) {
467 progress("Adding service entry...", i, 3);
469 sfp = fopen("/etc/services", "a");
471 display_error(strerror(errno));
473 fprintf(sfp, "%s 504/tcp\n",
485 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
488 void check_inittab_entry(void)
492 char looking_for[SIZ];
496 /* Determine the fully qualified path name of citserver */
497 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
499 /* If there's already an entry, then we have nothing left to do. */
500 if (strlen(init_entry) > 0) {
504 /* Otherwise, prompt the user to create an entry. */
505 snprintf(question, sizeof question,
506 "There is no '%s' entry in /etc/inittab.\n"
507 "Would you like to add one?",
509 if (yesno(question) == 0)
512 /* Generate a unique entry name for /etc/inittab */
513 snprintf(entryname, sizeof entryname, "c0");
516 if (entryname[1] > '9') {
519 if (entryname[0] > 'z') {
521 "Can't generate a unique entry name");
525 snprintf(buf, sizeof buf,
526 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
527 } while (system(buf) == 0);
529 /* Now write it out to /etc/inittab */
530 infp = fopen("/etc/inittab", "a");
532 display_error(strerror(errno));
534 fprintf(infp, "# Start the Citadel/UX server...\n");
535 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
536 entryname, looking_for, setup_directory);
538 strcpy(init_entry, entryname);
544 void set_str_val(int msgpos, char str[])
547 char tempfile[PATH_MAX];
550 strcpy(tempfile, tmpnam(NULL));
551 strcpy(setupmsg, "");
553 switch (setup_type) {
555 title(setup_titles[msgpos]);
556 printf("\n%s\n", setup_text[msgpos]);
557 printf("This is currently set to:\n%s\n", str);
558 printf("Enter new value or press return to leave unchanged:\n");
559 fgets(buf, 4096, stdin);
560 buf[strlen(buf) - 1] = 0;
561 if (strlen(buf) != 0)
564 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
567 move(1, ((80 - strlen(setup_titles[msgpos])) / 2));
569 printw("%s", setup_titles[msgpos]);
572 printw("%s", setup_text[msgpos]);
574 getlin(20, 0, str, 80);
580 void set_int_val(int msgpos, int *ip)
583 snprintf(buf, sizeof buf, "%d", (int) *ip);
584 set_str_val(msgpos, buf);
589 void set_char_val(int msgpos, char *ip)
592 snprintf(buf, sizeof buf, "%d", (int) *ip);
593 set_str_val(msgpos, buf);
594 *ip = (char) atoi(buf);
598 void set_long_val(int msgpos, long int *ip)
601 snprintf(buf, sizeof buf, "%ld", *ip);
602 set_str_val(msgpos, buf);
607 void edit_value(int curr)
611 char bbsuidname[SIZ];
616 set_str_val(curr, config.c_sysadm);
621 config.c_bbsuid = 0; /* XXX Windows hack, prob. insecure */
626 set_int_val(curr, &i);
630 strcpy(bbsuidname, pw->pw_name);
631 set_str_val(curr, bbsuidname);
632 pw = getpwnam(bbsuidname);
634 config.c_bbsuid = pw->pw_uid;
636 else if (atoi(bbsuidname) > 0) {
637 config.c_bbsuid = atoi(bbsuidname);
644 set_int_val(curr, &config.c_port_number);
652 * (re-)write the config data to disk
654 void write_config_to_disk(void)
659 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
660 display_error("setup: cannot open citadel.config");
663 fp = fdopen(fd, "wb");
665 display_error("setup: cannot open citadel.config");
668 fwrite((char *) &config, sizeof(struct config), 1, fp);
676 * Figure out what type of user interface we're going to use
678 int discover_ui(void)
681 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
691 int main(int argc, char *argv[])
697 int old_setup_level = 0;
699 struct utsname my_utsname;
704 /* set an invalid setup type */
707 /* parse command line args */
708 for (a = 0; a < argc; ++a) {
709 if (!strncmp(argv[a], "-u", 2)) {
710 strcpy(aaa, argv[a]);
711 strcpy(aaa, &aaa[2]);
712 setup_type = atoi(aaa);
714 if (!strcmp(argv[a], "-i")) {
717 if (!strcmp(argv[a], "-q")) {
718 setup_type = UI_SILENT;
723 /* If a setup type was not specified, try to determine automatically
724 * the best one to use out of all available types.
726 if (setup_type < 0) {
727 setup_type = discover_ui();
729 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
730 if (setup_type == UI_CURSES) {
737 if (info_only == 1) {
738 important_message("Citadel/UX Setup", CITADEL);
742 /* Get started in a valid setup directory. */
743 strcpy(setup_directory, BBSDIR);
744 set_str_val(0, setup_directory);
745 if (chdir(setup_directory) != 0) {
746 important_message("Citadel/UX Setup",
747 "The directory you specified does not exist.");
751 /* Determine our host name, in case we need to use it as a default */
754 /* See if we need to shut down the Citadel service. */
755 for (a=0; a<=5; ++a) {
756 progress("Shutting down the Citadel service...", a, 5);
757 if (a == 0) shutdown_service();
762 switch (setup_type) {
766 " *** Citadel/UX setup program ***\n\n");
772 * What we're going to try to do here is append a whole bunch of
773 * nulls to the citadel.config file, so we can keep the old config
774 * values if they exist, but if the file is missing or from an
775 * earlier version with a shorter config structure, when setup tries
776 * to read the old config parameters, they'll all come up zero.
777 * The length of the config file will be set to what it's supposed
778 * to be when we rewrite it, because we replace the old file with a
779 * completely new copy.
782 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
783 S_IRUSR | S_IWUSR)) == -1) {
784 display_error("setup: cannot append citadel.config");
787 fp = fdopen(a, "ab");
789 display_error("setup: cannot append citadel.config");
792 for (a = 0; a < sizeof(struct config); ++a)
796 /* now we re-open it, and read the old or blank configuration */
797 fp = fopen("citadel.config", "rb");
799 display_error("setup: cannot open citadel.config");
802 fread((char *) &config, sizeof(struct config), 1, fp);
805 /* set some sample/default values in place of blanks... */
806 if (strlen(config.c_nodename) == 0)
807 safestrncpy(config.c_nodename, my_utsname.nodename,
808 sizeof config.c_nodename);
809 strtok(config.c_nodename, ".");
810 if (strlen(config.c_fqdn) == 0) {
811 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
812 safestrncpy(config.c_fqdn, he->h_name,
813 sizeof config.c_fqdn);
815 safestrncpy(config.c_fqdn, my_utsname.nodename,
816 sizeof config.c_fqdn);
818 if (strlen(config.c_humannode) == 0)
819 strcpy(config.c_humannode, "My System");
820 if (strlen(config.c_phonenum) == 0)
821 strcpy(config.c_phonenum, "US 800 555 1212");
822 if (config.c_initax == 0) {
825 if (strlen(config.c_moreprompt) == 0)
826 strcpy(config.c_moreprompt, "<more>");
827 if (strlen(config.c_twitroom) == 0)
828 strcpy(config.c_twitroom, "Trashcan");
829 if (strlen(config.c_bucket_dir) == 0)
830 strcpy(config.c_bucket_dir, "bitbucket");
831 if (strlen(config.c_net_password) == 0)
832 strcpy(config.c_net_password, "netpassword");
833 if (strlen(config.c_baseroom) == 0)
834 strcpy(config.c_baseroom, "Lobby");
835 if (strlen(config.c_aideroom) == 0)
836 strcpy(config.c_aideroom, "Aide");
837 if (config.c_port_number == 0) {
838 config.c_port_number = 504;
840 if (config.c_ipgm_secret == 0) {
842 config.c_ipgm_secret = rand();
844 if (config.c_sleeping == 0) {
845 config.c_sleeping = 900;
847 if (config.c_bbsuid == 0) {
848 pw = getpwnam("citadel");
850 config.c_bbsuid = pw->pw_uid;
852 if (config.c_bbsuid == 0) {
853 pw = getpwnam("bbs");
855 config.c_bbsuid = pw->pw_uid;
857 if (config.c_bbsuid == 0) {
858 pw = getpwnam("guest");
860 config.c_bbsuid = pw->pw_uid;
862 if (config.c_createax == 0) {
863 config.c_createax = 3;
866 * Negative values for maxsessions are not allowed.
868 if (config.c_maxsessions < 0) {
869 config.c_maxsessions = 0;
871 /* We need a system default message expiry policy, because this is
872 * the top level and there's no 'higher' policy to fall back on.
874 if (config.c_ep.expire_mode == 0) {
875 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
876 config.c_ep.expire_value = 150;
880 * Default port numbers for various services
882 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
883 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
884 if (config.c_imap_port == 0) config.c_imap_port = 143;
886 /* Go through a series of dialogs prompting for config info */
887 if (setup_type != UI_SILENT) {
888 for (curr = 1; curr <= MAXSETUP; ++curr) {
894 if (setuid(config.c_bbsuid) != 0) {
895 important_message("Citadel/UX Setup",
896 "Failed to change the user ID to your Citadel user.");
901 /***** begin version update section ***** */
902 /* take care of any updating that is necessary */
904 old_setup_level = config.c_setup_level;
906 if (old_setup_level == 0)
909 if (old_setup_level < 323) {
910 important_message("Citadel/UX Setup",
911 "This Citadel/UX installation is too old "
915 write_config_to_disk();
917 if ((config.c_setup_level / 10) == 32) {
918 important_msgnum(31);
921 if (config.c_setup_level < 400) {
922 config.c_setup_level = 400;
924 /* end of 3.23 -> 4.00 update section */
926 /* end of 4.00 -> 4.02 update section */
928 old_setup_level = config.c_setup_level;
930 /* end of version update section */
933 config.c_setup_level = REV_LEVEL;
935 /******************************************/
937 write_config_to_disk();
941 mkdir("userpics", 0700);
942 mkdir("messages", 0700);
944 mkdir("images", 0700);
945 mkdir("netconfigs", 0700);
946 mkdir(config.c_bucket_dir, 0700);
948 /* Delete a bunch of old files from Citadel v4; don't need anymore */
949 system("rm -fr ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
951 /* Delete the old citadel.log file; this facility has been removed */
952 unlink("citadel.log");
954 check_services_entry(); /* Check /etc/services */
956 check_inittab_entry(); /* Check /etc/inittab */
959 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
964 progress("Setting file permissions", 0, 5);
965 chown(".", config.c_bbsuid, gid);
966 progress("Setting file permissions", 1, 5);
967 chown("citadel.config", config.c_bbsuid, gid);
968 progress("Setting file permissions", 2, 5);
969 snprintf(aaa, sizeof aaa,
970 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
971 (long)config.c_bbsuid, (long)gid);
973 progress("Setting file permissions", 3, 5);
974 chmod("citadel.config", S_IRUSR | S_IWUSR);
975 progress("Setting file permissions", 4, 5);
977 /* See if we can start the Citadel service. */
978 if (strlen(init_entry) > 0) {
979 for (a=0; a<=5; ++a) {
980 progress("Starting the Citadel service...", a, 5);
981 if (a == 0) start_the_service();
984 important_message("Setup finished",
985 "Setup is finished. You may now log in.");
988 important_message("Setup finished",
989 "Setup is finished. You may now start the server.");