4 * Citadel/UX setup utility
14 #include <sys/types.h>
16 #include <sys/utsname.h>
35 #define MAXSETUP 3 /* How many setup questions to ask */
37 #define UI_TEXT 0 /* Default setup type -- text only */
38 #define UI_SILENT 3 /* Silent running, for use in scripts */
39 #define UI_NEWT 4 /* Use the "newt" window library */
41 #define SERVICE_NAME "citadel"
42 #define PROTO_NAME "tcp"
45 char setup_directory[SIZ];
48 char *setup_titles[] =
50 "Citadel Home Directory",
51 "System Administrator",
59 "Enter the full pathname of the directory in which the Citadel installation\n"
60 "you are creating or updating resides. If you specify a directory other\n"
61 "than the default, you will need to specify the -h flag to the server when\n"
64 "Enter the name of the system administrator (which is probably you).\n"
65 "When an account is created with this name, it will automatically be\n"
66 "assigned the highest access level.\n",
68 "Citadel needs to run under its own user ID. This would typically be\n"
69 "called \"citadel\", but if you are running Citadel as a public BBS, you\n"
70 "might also call it \"bbs\" or \"guest\". The server will run under this\n"
71 "user ID. Please specify that user ID here. You may specify either a\n"
72 "user name or a numeric UID.\n",
74 "Specify the TCP port number on which your server will run. Normally, this\n"
75 "will be port 504, which is the official port assigned by the IANA for\n"
76 "Citadel servers. You'll only need to specify a different port number if\n"
77 "you run multiple instances of Citadel on the same computer and there's\n"
78 "something else already using port 504.\n",
80 "Setup has detected that you currently have data files from a Citadel/UX\n"
81 "version 3.2x installation. The program 'conv_32_40' can upgrade your\n"
82 "files to version 4.0x format.\n"
83 " Setup will now exit. Please either run 'conv_32_40' or delete your data\n"
84 "files, and run setup again.\n"
92 * Set an entry in inittab to the desired state
94 void set_init_entry(char *which_entry, char *new_state) {
103 inittab = strdup("");
104 if (inittab == NULL) return;
106 fp = fopen("/etc/inittab", "r");
107 if (fp == NULL) return;
109 while(fgets(buf, sizeof buf, fp) != NULL) {
111 if (num_tokens(buf, ':') == 4) {
112 extract_token(entry, buf, 0, ':');
113 extract_token(levels, buf, 1, ':');
114 extract_token(state, buf, 2, ':');
115 extract_token(prog, buf, 3, ':'); /* includes 0x0a LF */
117 if (!strcmp(entry, which_entry)) {
118 strcpy(state, new_state);
119 sprintf(buf, "%s:%s:%s:%s",
120 entry, levels, state, prog);
124 inittab = realloc(inittab, strlen(inittab) + strlen(buf) + 2);
125 if (inittab == NULL) {
130 strcat(inittab, buf);
133 fp = fopen("/etc/inittab", "w");
135 fwrite(inittab, strlen(inittab), 1, fp);
137 kill(1, SIGHUP); /* Tell init to re-read /etc/inittab */
146 * Shut down the Citadel service if necessary, during setup.
148 void shutdown_service(void) {
151 char looking_for[SIZ];
156 strcpy(init_entry, "");
158 /* Determine the fully qualified path name of citserver */
159 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
161 /* Pound through /etc/inittab line by line. Set have_entry to 1 if
162 * an entry is found which we believe starts citserver.
164 infp = fopen("/etc/inittab", "r");
168 while (fgets(buf, sizeof buf, infp) != NULL) {
169 buf[strlen(buf) - 1] = 0;
170 extract_token(entry, buf, 0, ':');
171 extract_token(prog, buf, 3, ':');
172 if (!strncasecmp(prog, looking_for,
173 strlen(looking_for))) {
175 strcpy(init_entry, entry);
181 /* Bail out if there's nothing to do. */
182 if (!have_entry) return;
184 set_init_entry(init_entry, "off");
189 * Start the Citadel service.
191 void start_the_service(void) {
192 if (strlen(init_entry) > 0) {
193 set_init_entry(init_entry, "respawn");
199 void cleanup(int exitcode)
211 void title(char *text)
213 if (setup_type == UI_TEXT) {
214 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
220 int yesno(char *question)
223 newtComponent form = NULL;
224 newtComponent yesbutton = NULL;
225 newtComponent nobutton = NULL;
232 switch (setup_type) {
236 printf("%s\nYes/No --> ", question);
237 fgets(buf, sizeof buf, stdin);
238 answer = tolower(buf[0]);
241 else if (answer == 'n')
243 } while ((answer < 0) || (answer > 1));
248 newtCenteredWindow(76, 10, "Question");
249 form = newtForm(NULL, NULL, 0);
250 for (i=0; i<num_tokens(question, '\n'); ++i) {
251 extract_token(buf, question, i, '\n');
252 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
254 yesbutton = newtButton(10, 5, "Yes");
255 nobutton = newtButton(60, 5, "No");
256 newtFormAddComponent(form, yesbutton);
257 newtFormAddComponent(form, nobutton);
258 if (newtRunForm(form) == yesbutton) {
265 newtFormDestroy(form);
275 void important_message(char *title, char *msgtext)
278 newtComponent form = NULL;
283 switch (setup_type) {
286 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");
287 printf(" %s \n\n%s\n\n", title, msgtext);
288 printf("Press return to continue...");
289 fgets(buf, sizeof buf, stdin);
294 newtCenteredWindow(76, 10, title);
295 form = newtForm(NULL, NULL, 0);
296 for (i=0; i<num_tokens(msgtext, '\n'); ++i) {
297 extract_token(buf, msgtext, i, '\n');
298 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
300 newtFormAddComponent(form, newtButton(35, 5, "OK"));
303 newtFormDestroy(form);
310 void important_msgnum(int msgnum)
312 important_message("Important Message", setup_text[msgnum]);
315 void display_error(char *error_message)
317 important_message("Error", error_message);
320 void progress(char *text, long int curr, long int cmax)
324 /* These variables are static because progress() gets called
325 * multiple times during the course of whatever operation is
326 * being performed. This makes setup non-threadsafe, but who
329 static newtComponent form = NULL;
330 static newtComponent scale = NULL;
332 static long dots_printed = 0L;
335 switch (setup_type) {
339 printf("%s\n", text);
340 printf("..........................");
341 printf("..........................");
342 printf("..........................\r");
345 } else if (curr == cmax) {
346 printf("\r%79s\n", "");
348 a = (curr * 100) / cmax;
351 while (dots_printed < a) {
362 newtCenteredWindow(76, 8, text);
363 form = newtForm(NULL, NULL, 0);
364 scale = newtScale(1, 3, 74, cmax);
365 newtFormAddComponent(form, scale);
369 if ((curr > 0) && (curr <= cmax)) {
370 newtScaleSet(scale, curr);
374 newtFormDestroy(form);
387 * check_services_entry() -- Make sure "citadel" is in /etc/services
390 void check_services_entry(void)
395 if (getservbyname(SERVICE_NAME, PROTO_NAME) == NULL) {
396 for (i=0; i<3; ++i) {
397 progress("Adding service entry...", i, 3);
399 sfp = fopen("/etc/services", "a");
401 display_error(strerror(errno));
403 fprintf(sfp, "%s 504/tcp\n",
415 * check_inittab_entry() -- Make sure "citadel" is in /etc/inittab
418 void check_inittab_entry(void)
422 char looking_for[SIZ];
426 /* Determine the fully qualified path name of citserver */
427 snprintf(looking_for, sizeof looking_for, "%s/citserver ", BBSDIR);
429 /* If there's already an entry, then we have nothing left to do. */
430 if (strlen(init_entry) > 0) {
434 /* Otherwise, prompt the user to create an entry. */
435 snprintf(question, sizeof question,
436 "There is no '%s' entry in /etc/inittab.\n"
437 "Would you like to add one?",
439 if (yesno(question) == 0)
442 /* Generate a unique entry name for /etc/inittab */
443 snprintf(entryname, sizeof entryname, "c0");
446 if (entryname[1] > '9') {
449 if (entryname[0] > 'z') {
451 "Can't generate a unique entry name");
455 snprintf(buf, sizeof buf,
456 "grep %s: /etc/inittab >/dev/null 2>&1", entryname);
457 } while (system(buf) == 0);
459 /* Now write it out to /etc/inittab */
460 infp = fopen("/etc/inittab", "a");
462 display_error(strerror(errno));
464 fprintf(infp, "# Start the Citadel/UX server...\n");
465 fprintf(infp, "%s:2345:respawn:%s -h%s\n",
466 entryname, looking_for, setup_directory);
468 strcpy(init_entry, entryname);
474 void set_str_val(int msgpos, char str[])
481 char tempfile[PATH_MAX];
485 strcpy(tempfile, tmpnam(NULL));
486 strcpy(setupmsg, "");
488 switch (setup_type) {
490 title(setup_titles[msgpos]);
491 printf("\n%s\n", setup_text[msgpos]);
492 printf("This is currently set to:\n%s\n", str);
493 printf("Enter new value or press return to leave unchanged:\n");
494 fgets(buf, sizeof buf, stdin);
495 buf[strlen(buf) - 1] = 0;
496 if (strlen(buf) != 0)
502 newtCenteredWindow(76, 10, setup_titles[msgpos]);
503 form = newtForm(NULL, NULL, 0);
504 for (i=0; i<num_tokens(setup_text[msgpos], '\n'); ++i) {
505 extract_token(buf, setup_text[msgpos], i, '\n');
506 newtFormAddComponent(form, newtLabel(1, 1+i, buf));
508 newtFormAddComponent(form, newtEntry(1, 8, str, 74, &result,
509 NEWT_FLAG_RETURNEXIT));
514 newtFormDestroy(form);
520 void set_int_val(int msgpos, int *ip)
523 snprintf(buf, sizeof buf, "%d", (int) *ip);
524 set_str_val(msgpos, buf);
529 void set_char_val(int msgpos, char *ip)
532 snprintf(buf, sizeof buf, "%d", (int) *ip);
533 set_str_val(msgpos, buf);
534 *ip = (char) atoi(buf);
538 void set_long_val(int msgpos, long int *ip)
541 snprintf(buf, sizeof buf, "%ld", *ip);
542 set_str_val(msgpos, buf);
547 void edit_value(int curr)
551 char bbsuidname[SIZ];
556 set_str_val(curr, config.c_sysadm);
561 config.c_bbsuid = 0; /* XXX Windows hack, prob. insecure */
566 set_int_val(curr, &i);
570 strcpy(bbsuidname, pw->pw_name);
571 set_str_val(curr, bbsuidname);
572 pw = getpwnam(bbsuidname);
574 config.c_bbsuid = pw->pw_uid;
576 else if (atoi(bbsuidname) > 0) {
577 config.c_bbsuid = atoi(bbsuidname);
584 set_int_val(curr, &config.c_port_number);
592 * (re-)write the config data to disk
594 void write_config_to_disk(void)
599 if ((fd = creat("citadel.config", S_IRUSR | S_IWUSR)) == -1) {
600 display_error("setup: cannot open citadel.config");
603 fp = fdopen(fd, "wb");
605 display_error("setup: cannot open citadel.config");
608 fwrite((char *) &config, sizeof(struct config), 1, fp);
616 * Figure out what type of user interface we're going to use
618 int discover_ui(void)
624 newtDrawRootText(0, 0, "Citadel/UX Setup");
634 int main(int argc, char *argv[])
640 int old_setup_level = 0;
642 struct utsname my_utsname;
647 /* set an invalid setup type */
650 /* parse command line args */
651 for (a = 0; a < argc; ++a) {
652 if (!strncmp(argv[a], "-u", 2)) {
653 strcpy(aaa, argv[a]);
654 strcpy(aaa, &aaa[2]);
655 setup_type = atoi(aaa);
657 if (!strcmp(argv[a], "-i")) {
660 if (!strcmp(argv[a], "-q")) {
661 setup_type = UI_SILENT;
666 /* If a setup type was not specified, try to determine automatically
667 * the best one to use out of all available types.
669 if (setup_type < 0) {
670 setup_type = discover_ui();
672 if (info_only == 1) {
673 important_message("Citadel/UX Setup", CITADEL);
677 /* Get started in a valid setup directory. */
678 strcpy(setup_directory, BBSDIR);
679 set_str_val(0, setup_directory);
680 if (chdir(setup_directory) != 0) {
681 important_message("Citadel/UX Setup",
682 "The directory you specified does not exist.");
686 /* Determine our host name, in case we need to use it as a default */
689 /* See if we need to shut down the Citadel service. */
690 for (a=0; a<=5; ++a) {
691 progress("Shutting down the Citadel service...", a, 5);
692 if (a == 0) shutdown_service();
697 switch (setup_type) {
701 " *** Citadel/UX setup program ***\n\n");
707 * What we're going to try to do here is append a whole bunch of
708 * nulls to the citadel.config file, so we can keep the old config
709 * values if they exist, but if the file is missing or from an
710 * earlier version with a shorter config structure, when setup tries
711 * to read the old config parameters, they'll all come up zero.
712 * The length of the config file will be set to what it's supposed
713 * to be when we rewrite it, because we replace the old file with a
714 * completely new copy.
717 if ((a = open("citadel.config", O_WRONLY | O_CREAT | O_APPEND,
718 S_IRUSR | S_IWUSR)) == -1) {
719 display_error("setup: cannot append citadel.config");
722 fp = fdopen(a, "ab");
724 display_error("setup: cannot append citadel.config");
727 for (a = 0; a < sizeof(struct config); ++a)
731 /* now we re-open it, and read the old or blank configuration */
732 fp = fopen("citadel.config", "rb");
734 display_error("setup: cannot open citadel.config");
737 fread((char *) &config, sizeof(struct config), 1, fp);
740 /* set some sample/default values in place of blanks... */
741 if (strlen(config.c_nodename) == 0)
742 safestrncpy(config.c_nodename, my_utsname.nodename,
743 sizeof config.c_nodename);
744 strtok(config.c_nodename, ".");
745 if (strlen(config.c_fqdn) == 0) {
746 if ((he = gethostbyname(my_utsname.nodename)) != NULL)
747 safestrncpy(config.c_fqdn, he->h_name,
748 sizeof config.c_fqdn);
750 safestrncpy(config.c_fqdn, my_utsname.nodename,
751 sizeof config.c_fqdn);
753 if (strlen(config.c_humannode) == 0)
754 strcpy(config.c_humannode, "My System");
755 if (strlen(config.c_phonenum) == 0)
756 strcpy(config.c_phonenum, "US 800 555 1212");
757 if (config.c_initax == 0) {
760 if (strlen(config.c_moreprompt) == 0)
761 strcpy(config.c_moreprompt, "<more>");
762 if (strlen(config.c_twitroom) == 0)
763 strcpy(config.c_twitroom, "Trashcan");
764 if (strlen(config.c_bucket_dir) == 0)
765 strcpy(config.c_bucket_dir, "bitbucket");
766 if (strlen(config.c_net_password) == 0)
767 strcpy(config.c_net_password, "netpassword");
768 if (strlen(config.c_baseroom) == 0)
769 strcpy(config.c_baseroom, "Lobby");
770 if (strlen(config.c_aideroom) == 0)
771 strcpy(config.c_aideroom, "Aide");
772 if (config.c_port_number == 0) {
773 config.c_port_number = 504;
775 if (config.c_ipgm_secret == 0) {
777 config.c_ipgm_secret = rand();
779 if (config.c_sleeping == 0) {
780 config.c_sleeping = 900;
782 if (config.c_bbsuid == 0) {
783 pw = getpwnam("citadel");
785 config.c_bbsuid = pw->pw_uid;
787 if (config.c_bbsuid == 0) {
788 pw = getpwnam("bbs");
790 config.c_bbsuid = pw->pw_uid;
792 if (config.c_bbsuid == 0) {
793 pw = getpwnam("guest");
795 config.c_bbsuid = pw->pw_uid;
797 if (config.c_createax == 0) {
798 config.c_createax = 3;
801 * Negative values for maxsessions are not allowed.
803 if (config.c_maxsessions < 0) {
804 config.c_maxsessions = 0;
806 /* We need a system default message expiry policy, because this is
807 * the top level and there's no 'higher' policy to fall back on.
809 if (config.c_ep.expire_mode == 0) {
810 config.c_ep.expire_mode = EXPIRE_NUMMSGS;
811 config.c_ep.expire_value = 150;
815 * Default port numbers for various services
817 if (config.c_smtp_port == 0) config.c_smtp_port = 25;
818 if (config.c_pop3_port == 0) config.c_pop3_port = 110;
819 if (config.c_imap_port == 0) config.c_imap_port = 143;
821 /* Go through a series of dialogs prompting for config info */
822 if (setup_type != UI_SILENT) {
823 for (curr = 1; curr <= MAXSETUP; ++curr) {
829 if (setuid(config.c_bbsuid) != 0) {
830 important_message("Citadel/UX Setup",
831 "Failed to change the user ID to your Citadel user.");
836 /***** begin version update section ***** */
837 /* take care of any updating that is necessary */
839 old_setup_level = config.c_setup_level;
841 if (old_setup_level == 0)
844 if (old_setup_level < 323) {
845 important_message("Citadel/UX Setup",
846 "This Citadel/UX installation is too old "
850 write_config_to_disk();
852 if ((config.c_setup_level / 10) == 32) {
853 important_msgnum(31);
856 if (config.c_setup_level < 400) {
857 config.c_setup_level = 400;
859 /* end of 3.23 -> 4.00 update section */
861 /* end of 4.00 -> 4.02 update section */
863 old_setup_level = config.c_setup_level;
865 /* end of version update section */
868 config.c_setup_level = REV_LEVEL;
870 /******************************************/
872 write_config_to_disk();
876 mkdir("userpics", 0700);
877 mkdir("messages", 0700);
879 mkdir("images", 0700);
880 mkdir("netconfigs", 0700);
881 mkdir(config.c_bucket_dir, 0700);
883 /* Delete a bunch of old files from Citadel v4; don't need anymore */
884 system("rm -fr ./chatpipes ./expressmsgs ./sessions 2>/dev/null");
886 /* Delete the old citadel.log file; this facility has been removed */
887 unlink("citadel.log");
889 check_services_entry(); /* Check /etc/services */
891 check_inittab_entry(); /* Check /etc/inittab */
894 if ((pw = getpwuid(config.c_bbsuid)) == NULL)
899 progress("Setting file permissions", 0, 5);
900 chown(".", config.c_bbsuid, gid);
901 progress("Setting file permissions", 1, 5);
902 chown("citadel.config", config.c_bbsuid, gid);
903 progress("Setting file permissions", 2, 5);
904 snprintf(aaa, sizeof aaa,
905 "find . | grep -v chkpwd | xargs chown %ld:%ld 2>/dev/null",
906 (long)config.c_bbsuid, (long)gid);
908 progress("Setting file permissions", 3, 5);
909 chmod("citadel.config", S_IRUSR | S_IWUSR);
910 progress("Setting file permissions", 4, 5);
912 /* See if we can start the Citadel service. */
913 if (strlen(init_entry) > 0) {
914 for (a=0; a<=5; ++a) {
915 progress("Starting the Citadel service...", a, 5);
916 if (a == 0) start_the_service();
919 important_message("Setup finished",
920 "Setup is finished. You may now log in.");
923 important_message("Setup finished",
924 "Setup is finished. You may now start the server.");