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);
623 set_int_val(curr, &i);
627 strcpy(bbsuidname, pw->pw_name);
628 set_str_val(curr, bbsuidname);
629 pw = getpwnam(bbsuidname);
631 config.c_bbsuid = pw->pw_uid;
633 else if (atoi(bbsuidname) > 0) {
634 config.c_bbsuid = atoi(bbsuidname);
640 set_int_val(curr, &config.c_port_number);
648 * (re-)write the config data to disk
650 void write_config_to_disk(void)
655 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
656 display_error("setup: cannot open citadel.config");
659 fp = fdopen(fd, "wb");
661 display_error("setup: cannot open citadel.config");
664 fwrite((char *) &config, sizeof(struct config), 1, fp);
672 * Figure out what type of user interface we're going to use
674 int discover_ui(void)
677 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
687 int main(int argc, char *argv[])
693 int old_setup_level = 0;
695 struct utsname my_utsname;
700 /* set an invalid setup type */
703 /* parse command line args */
704 for (a = 0; a < argc; ++a) {
705 if (!strncmp(argv[a], "-u", 2)) {
706 strcpy(aaa, argv[a]);
707 strcpy(aaa, &aaa[2]);
708 setup_type = atoi(aaa);
710 if (!strcmp(argv[a], "-i")) {
713 if (!strcmp(argv[a], "-q")) {
714 setup_type = UI_SILENT;
719 /* If a setup type was not specified, try to determine automatically
720 * the best one to use out of all available types.
722 if (setup_type < 0) {
723 setup_type = discover_ui();
725 #if defined(HAVE_CURSES_H) && !defined(DISABLE_CURSES)
726 if (setup_type == UI_CURSES) {
733 if (info_only == 1) {
734 important_message("Citadel/UX Setup", CITADEL);
738 /* Get started in a valid setup directory. */
739 strcpy(setup_directory, BBSDIR);
740 set_str_val(0, setup_directory);
741 if (chdir(setup_directory) != 0) {
742 important_message("Citadel/UX Setup",
743 "The directory you specified does not exist.");
747 /* Determine our host name, in case we need to use it as a default */
750 /* See if we need to shut down the Citadel service. */
751 for (a=0; a<=5; ++a) {
752 progress("Shutting down the Citadel service...", a, 5);
753 if (a == 0) shutdown_service();
758 switch (setup_type) {
762 " *** Citadel/UX setup program ***\n\n");
768 * What we're going to try to do here is append a whole bunch of
769 * nulls to the citadel.config file, so we can keep the old config
770 * values if they exist, but if the file is missing or from an
771 * earlier version with a shorter config structure, when setup tries
772 * to read the old config parameters, they'll all come up zero.
773 * The length of the config file will be set to what it's supposed
774 * to be when we rewrite it, because we replace the old file with a
775 * completely new copy.
778 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
779 S_IRUSR | S_IWUSR)) == -1) {
780 display_error("setup: cannot append citadel.config");
783 fp = fdopen(a, "ab");
785 display_error("setup: cannot append citadel.config");
788 for (a = 0; a < sizeof(struct config); ++a)
792 /* now we re-open it, and read the old or blank configuration */
793 fp = fopen("citadel.config", "rb");
795 display_error("setup: cannot open citadel.config");
798 fread((char *) &config, sizeof(struct config), 1, fp);
801 /* set some sample/default values in place of blanks... */
802 if (strlen(config.c_nodename) == 0)
803 safestrncpy(config.c_nodename, my_utsname.nodename,
804 sizeof config.c_nodename);
805 strtok(config.c_nodename, ".");
806 if (strlen(config.c_fqdn) == 0) {
807 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
808 safestrncpy(config.c_fqdn, he->h_name,
809 sizeof config.c_fqdn);
811 safestrncpy(config.c_fqdn, my_utsname.nodename,
812 sizeof config.c_fqdn);
814 if (strlen(config.c_humannode) == 0)
815 strcpy(config.c_humannode, "My System");
816 if (strlen(config.c_phonenum) == 0)
817 strcpy(config.c_phonenum, "US 800 555 1212");
818 if (config.c_initax == 0) {
821 if (strlen(config.c_moreprompt) == 0)
822 strcpy(config.c_moreprompt, "<more>");
823 if (strlen(config.c_twitroom) == 0)
824 strcpy(config.c_twitroom, "Trashcan");
825 if (strlen(config.c_bucket_dir) == 0)
826 strcpy(config.c_bucket_dir, "bitbucket");
827 if (strlen(config.c_net_password) == 0)
828 strcpy(config.c_net_password, "netpassword");
829 if (strlen(config.c_baseroom) == 0)
830 strcpy(config.c_baseroom, "Lobby");
831 if (strlen(config.c_aideroom) == 0)
832 strcpy(config.c_aideroom, "Aide");
833 if (config.c_port_number == 0) {
834 config.c_port_number = 504;
836 if (config.c_ipgm_secret == 0) {
838 config.c_ipgm_secret = rand();
840 if (config.c_sleeping == 0) {
841 config.c_sleeping = 900;
843 if (config.c_bbsuid == 0) {
844 pw = getpwnam("citadel");
846 config.c_bbsuid = pw->pw_uid;
848 if (config.c_bbsuid == 0) {
849 pw = getpwnam("bbs");
851 config.c_bbsuid = pw->pw_uid;
853 if (config.c_bbsuid == 0) {
854 pw = getpwnam("guest");
856 config.c_bbsuid = pw->pw_uid;
858 if (config.c_createax == 0) {
859 config.c_createax = 3;
862 * Negative values for maxsessions are not allowed.
864 if (config.c_maxsessions < 0) {
865 config.c_maxsessions = 0;
867 /* We need a system default message expiry policy, because this is
868 * the top level and there's no 'higher' policy to fall back on.
870 if (config.c_ep.expire_mode == 0) {
871 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
872 config.c_ep.expire_value = 150;
876 * Default port numbers for various services
878 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
879 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
880 if (config.c_imap_port == 0) config.c_imap_port = 143;
882 /* Go through a series of dialogs prompting for config info */
883 if (setup_type != UI_SILENT) {
884 for (curr = 1; curr <= MAXSETUP; ++curr) {
890 if (setuid(config.c_bbsuid) != 0) {
891 important_message("Citadel/UX Setup",
892 "Failed to change the user ID to your Citadel user.");
897 /***** begin version update section ***** */
898 /* take care of any updating that is necessary */
900 old_setup_level = config.c_setup_level;
902 if (old_setup_level == 0)
905 if (old_setup_level < 323) {
906 important_message("Citadel/UX Setup",
907 "This Citadel/UX installation is too old "
911 write_config_to_disk();
913 if ((config.c_setup_level / 10) == 32) {
914 important_msgnum(31);
917 if (config.c_setup_level < 400) {
918 config.c_setup_level = 400;
920 /* end of 3.23 -> 4.00 update section */
922 /* end of 4.00 -> 4.02 update section */
924 old_setup_level = config.c_setup_level;
926 /* end of version update section */
929 config.c_setup_level = REV_LEVEL;
931 /******************************************/
933 write_config_to_disk();
937 mkdir("userpics", 0700);
938 mkdir("messages", 0700);
940 mkdir("images", 0700);
941 mkdir("netconfigs", 0700);
942 mkdir(config.c_bucket_dir, 0700);
944 /* Delete a bunch of old files from Citadel v4; don't need anymore */
945 system("rm -fr ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
947 /* Delete the old citadel.log file; this facility has been removed */
948 unlink("citadel.log");
950 check_services_entry(); /* Check /etc/services */
951 check_inittab_entry(); /* Check /etc/inittab */
953 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
958 progress("Setting file permissions", 0, 5);
959 chown(".", config.c_bbsuid, gid);
960 progress("Setting file permissions", 1, 5);
961 chown("citadel.config", config.c_bbsuid, gid);
962 progress("Setting file permissions", 2, 5);
963 snprintf(aaa, sizeof aaa,
964 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
965 (long)config.c_bbsuid, (long)gid);
967 progress("Setting file permissions", 3, 5);
968 chmod("citadel.config", S_IRUSR | S_IWUSR);
969 progress("Setting file permissions", 4, 5);
971 /* See if we can start the Citadel service. */
972 if (strlen(init_entry) > 0) {
973 for (a=0; a<=5; ++a) {
974 progress("Starting the Citadel service...", a, 5);
975 if (a == 0) start_the_service();
978 important_message("Setup finished",
979 "Setup is finished. You may now log in.");
982 important_message("Setup finished",
983 "Setup is finished. You may now start the server.");