2 * Citadel/UX setup program
5 * *** YOU MUST EDIT sysconfig.h >BEFORE< COMPILING SETUP ***
14 #include <sys/types.h>
16 #include <sys/utsname.h>
37 #define UI_TEXT 0 /* Default setup type -- text only */
38 #define UI_DIALOG 1 /* Use the 'dialog' program */
39 #define UI_CURSES 2 /* Use curses */
41 #define SERVICE_NAME "citadel"
42 #define PROTO_NAME "tcp"
45 char setup_directory[128];
48 char *setup_titles[] =
51 "System Administrator",
53 "Name of bit bucket subdirectory",
62 "Enter the full pathname of the directory in which the BBS you are",
63 "creating or updating resides. If you specify a directory other than the",
64 "default, you will need to specify the -h flag to the server when you start",
68 "Enter the name of the system administrator (which is probably you).",
69 "When an account is created with this name, it will automatically be",
70 "assigned the highest access level.",
73 "You should create a user called 'bbs', 'guest', 'citadel', or something",
74 "similar, that will allow users a way into your BBS. The server will run",
75 "under this user ID. Please specify that (numeric) user ID here.",
78 "Select the name of a subdirectory (relative to the main",
79 "Citadel directory - do not type an absolute pathname!) in",
80 "which to place arriving file transfers that otherwise",
84 "Specify the TCP port number on which your server will run. Normally, this",
85 "will be port 504, which is the official port assigned by the IANA for",
86 "Citadel servers. You'll only need to specify a different port number if",
87 "you run multiple BBS's on the same computer and there's something else",
88 "already using port 504.",
118 "Setup has detected that you currently have data files from a Citadel/UX",
119 "version 3.2x installation. The program 'conv_32_40' can upgrade your",
120 "files to version 4.0x format.",
121 " Setup will now exit. Please either run 'conv_32_40' or delete your data",
122 "files, and run setup again.",
128 struct config config;
131 void cleanup(int exitcode)
134 if (setup_type == UI_CURSES) {
141 /* Do an 'init q' if we need to. When we hit the right one, init
142 * will take over and setup won't come back because we didn't do a
143 * fork(). If init isn't found, we fall through the bottom of the
144 * loop and setup exits quietly.
147 execlp("/sbin/init", "init", "q", NULL);
148 execlp("/usr/sbin/init", "init", "q", NULL);
149 execlp("/bin/init", "init", "q", NULL);
150 execlp("/usr/bin/init", "init", "q", NULL);
151 execlp("init", "init", "q", NULL);
157 /* Gets a line from the terminal */
158 /* Where on the screen to start */
159 /* Pointer to string buffer */
160 /* Maximum length - if negative, no-show */
162 void getlin(int yp, int xp, char *string, int lim) {
173 for (a = 0; a < lim; ++a)
177 for (a = 0; a < lim; ++a)
180 printw("%s", string);
181 GLA:move(yp, xp + strlen(string));
189 if ((a == 8) && (strlen(string) == 0))
191 if ((a != 13) && (a != 8) && (strlen(string) == lim))
193 if ((a == 8) && (string[0] != 0)) {
194 string[strlen(string) - 1] = 0;
195 move(yp, xp + strlen(string));
199 if ((a == 13) || (a == 10)) {
202 for (a = 0; a < lim; ++a)
204 mvprintw(yp, xp, "%s", string);
221 void title(char *text)
223 if (setup_type == UI_TEXT) {
224 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
229 void hit_any_key(void)
234 if (setup_type == UI_CURSES) {
235 mvprintw(20, 0, "Press any key to continue... ");
241 printf("Press return to continue...");
242 fgets(junk, 5, stdin);
245 int yesno(char *question)
250 switch (setup_type) {
254 printf("%s\nYes/No --> ", question);
255 fgets(buf, 4096, stdin);
256 answer = tolower(buf[0]);
259 else if (answer == 'n')
261 } while ((answer < 0) || (answer > 1));
265 sprintf(buf, "dialog --yesno \"%s\" 7 80", question);
266 answer = ((system(buf) == 0) ? 1 : 0);
273 mvprintw(1, 20, "Question");
275 mvprintw(10, 0, "%-80s", question);
276 mvprintw(20, 0, "%80s", "");
277 mvprintw(20, 0, "Yes/No -> ");
280 answer = tolower(answer);
283 else if (answer == 'n')
285 } while ((answer < 0) || (answer > 1));
295 void dump_access_levels(void)
298 for (a = 0; a <= 6; ++a)
299 printf("%d %s\n", a, axdefs[a]);
302 void get_setup_msg(char *dispbuf, int msgnum)
308 while (atol(setup_text[a]) != msgnum)
313 strcat(dispbuf, setup_text[a++]);
314 strcat(dispbuf, "\n");
315 } while (atol(setup_text[a]) != (msgnum + 1));
318 void print_setup(int msgnum)
322 get_setup_msg(dispbuf, msgnum);
323 printf("\n\n%s\n\n", dispbuf);
327 void important_message(char *title, char *msgtext)
331 switch (setup_type) {
334 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");
335 printf(" %s \n\n%s\n\n", title, msgtext);
340 sprintf(buf, "dialog --title \"%s\" --msgbox \"\n%s\" 20 80",
349 printw(" Important Message ");
352 printw("%s", msgtext);
361 void important_msgnum(int msgnum)
365 get_setup_msg(dispbuf, msgnum);
366 important_message("Important Message", dispbuf);
369 void display_error(char *error_message)
371 important_message("Error", error_message);
374 void progress(char *text, long int curr, long int cmax)
376 static long dots_printed;
379 static FILE *gauge = NULL;
382 switch (setup_type) {
386 printf("%s\n", text);
387 printf("..........................");
388 printf("..........................");
389 printf("..........................\r");
392 } else if (curr == cmax) {
393 printf("\r%79s\n", "");
395 a = (curr * 100) / cmax;
398 while (dots_printed < a) {
411 printw("%s\n", text);
413 printf("..........................");
414 printf("..........................");
415 printf("..........................\r");
418 } else if (curr == cmax) {
422 a = (curr * 100) / cmax;
427 while (dots_printed < a) {
437 if ((curr == 0) && (gauge == NULL)) {
438 sprintf(gcmd, "dialog --guage \"%s\" 7 80 0",
440 gauge = (FILE *) popen(gcmd, "w");
442 } else if (curr == cmax) {
443 fprintf(gauge, "100\n");
447 a = (curr * 100) / cmax;
449 fprintf(gauge, "%ld\n", a);
461 * check_services_entry() -- Make sure "citadel" is in /etc/services
464 void check_services_entry(void)
470 "There is no '%s' entry in /etc/services. Would you like to add one?",
473 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
474 if (yesno(question) == 1) {
475 sfp = fopen("/etc/services", "a");
477 display_error(strerror(errno));
479 fprintf(sfp, "%s 504/tcp\n",
489 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
492 void check_inittab_entry(void)
496 char looking_for[256];
502 /* Determine the fully qualified path name of citserver */
503 sprintf(looking_for, "%s/citserver ", BBSDIR);
505 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
506 * an entry is found which we believe starts citserver.
508 infp = fopen("/etc/inittab", "r");
512 while (fgets(buf, 256, infp) != NULL) {
513 buf[strlen(buf) - 1] = 0;
514 ptr = strtok(buf, ":");
515 ptr = strtok(NULL, ":");
516 ptr = strtok(NULL, ":");
517 ptr = strtok(NULL, ":");
519 if (!strncmp(ptr, looking_for, strlen(looking_for))) {
527 /* If there's already an entry, then we have nothing left to do. */
531 /* Otherwise, prompt the user to create an entry. */
533 "There is no '%s' entry in /etc/inittab.\nWould you like to add one?",
535 if (yesno(question) == 0)
538 /* Generate a unique entry name for /etc/inittab */
539 sprintf(entryname, "c0");
542 if (entryname[1] > '9') {
545 if (entryname[0] > 'z') {
547 "Can't generate a unique entry name");
552 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
553 } while (system(buf) == 0);
555 /* Now write it out to /etc/inittab */
556 infp = fopen("/etc/inittab", "a");
558 display_error(strerror(errno));
560 fprintf(infp, "# Start the Citadel/UX server...\n");
561 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
562 entryname, looking_for, setup_directory);
570 void set_str_val(int msgpos, char str[])
577 sprintf(tempfile, tmpnam(NULL));
579 switch (setup_type) {
581 title(setup_titles[msgpos]);
584 dump_access_levels();
585 printf("This is currently set to:\n%s\n", str);
586 printf("Enter new value or press return to leave unchanged:\n");
587 fgets(buf, 4096, stdin);
588 buf[strlen(buf) - 1] = 0;
589 if (strlen(buf) != 0)
593 get_setup_msg(setupmsg, msgpos);
595 "dialog --title \"%s\" --inputbox \"\n%s\n\" 20 80 \"%s\" 2>%s",
596 setup_titles[msgpos],
599 if (system(buf) == 0) {
600 fp = fopen(tempfile, "rb");
601 fgets(str, 4095, fp);
604 if (str[strlen(str) - 1] == 10)
605 str[strlen(str) - 1] = 0;
611 move(1, ((80 - strlen(setup_titles[msgpos])) / 2));
613 printw("%s", setup_titles[msgpos]);
616 get_setup_msg(setupmsg, msgpos);
617 printw("%s", setupmsg);
619 getlin(20, 0, str, 80);
625 void set_int_val(int msgpos, int *ip)
628 sprintf(buf, "%d", (int) *ip);
629 set_str_val(msgpos, buf);
634 void set_char_val(int msgpos, char *ip)
637 sprintf(buf, "%d", (int) *ip);
638 set_str_val(msgpos, buf);
639 *ip = (char) atoi(buf);
643 void set_long_val(int msgpos, long int *ip)
646 sprintf(buf, "%ld", *ip);
647 set_str_val(msgpos, buf);
652 void edit_value(int curr)
659 set_str_val(curr, config.c_sysadm);
663 set_int_val(curr, &config.c_bbsuid);
667 set_str_val(curr, config.c_bucket_dir);
668 config.c_bucket_dir[14] = 0;
669 for (a = 0; a < strlen(config.c_bucket_dir); ++a)
670 if (!isalpha(config.c_bucket_dir[a]))
671 strcpy(&config.c_bucket_dir[a],
672 &config.c_bucket_dir[a + 1]);
676 set_int_val(curr, &config.c_port_number);
684 * (re-)write the config data to disk
686 void write_config_to_disk(void)
691 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
692 display_error("setup: cannot open citadel.config");
695 fp = fdopen(fd, "wb");
697 display_error("setup: cannot open citadel.config");
700 fwrite((char *) &config, sizeof(struct config), 1, fp);
708 * Figure out what type of user interface we're going to use
710 int discover_ui(void)
717 if (system("dialog -h </dev/null 2>&1 |grep Savio") == 0) {
727 int main(int argc, char *argv[])
733 int old_setup_level = 0;
735 struct utsname my_utsname;
740 /* set an invalid setup type */
743 /* parse command line args */
744 for (a = 0; a < argc; ++a) {
745 if (!strncmp(argv[a], "-u", 2)) {
746 strcpy(aaa, argv[a]);
747 strcpy(aaa, &aaa[2]);
748 setup_type = atoi(aaa);
750 if (!strcmp(argv[a], "-i")) {
756 /* If a setup type was not specified, try to determine automatically
757 * the best one to use out of all available types.
759 if (setup_type < 0) {
760 setup_type = discover_ui();
763 if (setup_type == UI_CURSES) {
770 if (info_only == 1) {
771 important_message("Citadel/UX Setup", CITADEL);
774 /* Get started in a valid setup directory. */
775 strcpy(setup_directory, BBSDIR);
776 set_str_val(0, setup_directory);
777 if (chdir(setup_directory) != 0) {
778 important_message("Citadel/UX Setup",
779 "The directory you specified does not exist.");
782 /* Determine our host name, in case we need to use it as a default */
786 switch (setup_type) {
789 printf("\n\n\n *** Citadel/UX setup program ***\n\n");
793 system("exec clear");
799 * What we're going to try to do here is append a whole bunch of
800 * nulls to the citadel.config file, so we can keep the old config
801 * values if they exist, but if the file is missing or from an
802 * earlier version with a shorter config structure, when setup tries
803 * to read the old config parameters, they'll all come up zero.
804 * The length of the config file will be set to what it's supposed
805 * to be when we rewrite it, because we replace the old file with a
806 * completely new copy. (Neat, eh?)
809 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
810 S_IRUSR | S_IWUSR)) == -1) {
811 display_error("setup: cannot append citadel.config");
814 fp = fdopen(a, "ab");
816 display_error("setup: cannot append citadel.config");
819 for (a = 0; a < sizeof(struct config); ++a)
823 /* now we re-open it, and read the old or blank configuration */
824 fp = fopen("citadel.config", "rb");
826 display_error("setup: cannot open citadel.config");
829 fread((char *) &config, sizeof(struct config), 1, fp);
833 /* set some sample/default values in place of blanks... */
834 if (strlen(config.c_nodename) == 0)
835 safestrncpy(config.c_nodename, my_utsname.nodename,
836 sizeof config.c_nodename);
837 strtok(config.c_nodename, ".");
838 if (strlen(config.c_fqdn) == 0) {
839 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
840 safestrncpy(config.c_fqdn, he->h_name,
841 sizeof config.c_fqdn);
843 safestrncpy(config.c_fqdn, my_utsname.nodename,
844 sizeof config.c_fqdn);
846 if (strlen(config.c_humannode) == 0)
847 strcpy(config.c_humannode, "My System");
848 if (strlen(config.c_phonenum) == 0)
849 strcpy(config.c_phonenum, "US 800 555 1212");
850 if (config.c_initax == 0)
852 if (strlen(config.c_moreprompt) == 0)
853 strcpy(config.c_moreprompt, "<more>");
854 if (strlen(config.c_twitroom) == 0)
855 strcpy(config.c_twitroom, "Trashcan");
856 if (strlen(config.c_bucket_dir) == 0)
857 strcpy(config.c_bucket_dir, "bitbucket");
858 if (strlen(config.c_net_password) == 0)
859 strcpy(config.c_net_password, "netpassword");
860 if (config.c_port_number == 0) {
861 config.c_port_number = 504;
863 if (config.c_ipgm_secret == 0) {
865 config.c_ipgm_secret = rand();
867 if (config.c_sleeping == 0) {
868 config.c_sleeping = 900;
870 if (config.c_bbsuid == 0) {
871 pw = getpwnam("citadel");
873 config.c_bbsuid = pw->pw_uid;
875 if (config.c_bbsuid == 0) {
876 pw = getpwnam("bbs");
878 config.c_bbsuid = pw->pw_uid;
880 if (config.c_bbsuid == 0) {
881 pw = getpwnam("guest");
883 config.c_bbsuid = pw->pw_uid;
885 if (config.c_createax == 0) {
886 config.c_createax = 3;
889 * Negative values for maxsessions are not allowed.
891 if (config.c_maxsessions < 0) {
892 config.c_maxsessions = 0;
894 /* We need a system default message expiry policy, because this is
895 * the top level and there's no 'higher' policy to fall back on.
897 if (config.c_ep.expire_mode == 0) {
898 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
899 config.c_ep.expire_value = 150;
903 /* Default maximum message length is 'unlimited' (max int)
904 * and the minimum is 8192
906 if (config.c_maxmsglen <= 0)
907 config.c_maxmsglen = INT_MAX;
908 if (config.c_maxmsglen < 8192)
909 config.c_maxmsglen = 8192;
912 /* Go through a series of dialogs prompting for config info */
913 for (curr = 1; curr <= MAXSETUP; ++curr) {
918 if (setuid(config.c_bbsuid) != 0) {
919 important_message("Citadel/UX Setup",
920 "Failed to change the user ID to your BBS user.");
925 /***** begin version update section ***** */
926 /* take care of any updating that is necessary */
928 old_setup_level = config.c_setup_level;
930 if (old_setup_level == 0)
933 if (old_setup_level < 323) {
934 important_message("Citadel/UX Setup",
935 "This Citadel/UX installation is too old to be upgraded.");
938 write_config_to_disk();
940 if ((config.c_setup_level / 10) == 32) {
941 important_msgnum(31);
944 if (config.c_setup_level < 400) {
945 config.c_setup_level = 400;
947 /* end of 3.23 -> 4.00 update section */
949 /* end of 4.00 -> 4.02 update section */
951 old_setup_level = config.c_setup_level;
953 /* end of version update section */
956 config.c_setup_level = REV_LEVEL;
958 /******************************************/
960 write_config_to_disk();
962 system("mkdir info 2>/dev/null"); /* Create these */
963 system("mkdir bio 2>/dev/null");
964 system("mkdir userpics 2>/dev/null");
965 system("mkdir messages 2>/dev/null");
966 system("mkdir help 2>/dev/null");
967 system("mkdir images 2>/dev/null");
968 sprintf(aaa, "mkdir %s 2>/dev/null", config.c_bucket_dir);
971 /* Delete a bunch of old files from Citadel v4; don't need anymore */
972 system("rm -fr ./chatpipes ./expressmsgs sessions 2>/dev/null");
974 check_services_entry(); /* Check /etc/services */
975 check_inittab_entry(); /* Check /etc/inittab */
977 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
982 progress("Setting file permissions", 0, 5);
983 chown(".", config.c_bbsuid, gid);
984 progress("Setting file permissions", 1, 5);
985 chown("citadel.config", config.c_bbsuid, gid);
986 progress("Setting file permissions", 2, 5);
987 sprintf(aaa, "find . | grep -v chkpwd | xargs chown %d:%d 2>/dev/null",
988 config.c_bbsuid, gid);
990 progress("Setting file permissions", 3, 5);
991 chmod("citadel.config", S_IRUSR | S_IWUSR);
992 progress("Setting file permissions", 4, 5);
994 important_message("Setup finished",
995 "Setup is finished. You may now start the Citadel server.");