2 * Citadel setup utility
4 * Copyright (c) 1987-2019 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
15 #define SHOW_ME_VAPPEND_PRINTF
22 #include <sys/types.h>
31 #include <sys/socket.h>
34 #include <libcitadel.h>
38 #include "citadel_dirs.h"
46 #define _(string) gettext(string)
48 #define _(string) (string)
51 #define SERVICE_NAME "citadel"
52 #define PROTO_NAME "tcp"
53 #define NSSCONF "/etc/nsswitch.conf"
55 typedef enum _SetupStep {
71 ///"CREATE_XINETD_ENTRY";
72 /* Environment variables, don't translate! */
73 const char *EnvNames [eMaxQuestions] = {
88 int setup_type = (-1);
93 int serv_sock = (-1) ;
95 const char *setup_titles[eMaxQuestions];
96 const char *setup_text[eMaxQuestions];
110 setlocale(LC_MESSAGES, getenv("LANG"));
111 bindtextdomain("citadel-setup", LOCALEDIR"/locale");
112 textdomain("citadel-setup");
113 bind_textdomain_codeset("citadel-setup","UTF8");
116 setup_titles[eCitadelHomeDir] = _("Citadel Home Directory");
118 setup_text[eCitadelHomeDir] = _(
119 "Enter the full pathname of the directory in which the Citadel\n"
120 "installation you are creating or updating resides. If you\n"
121 "specify a directory other than the default, you will need to\n"
122 "specify the -h flag to the server when you start it up.\n");
124 setup_text[eCitadelHomeDir] = _(
125 "Enter the subdirectory name for an alternate installation of "
126 "Citadel. To do a default installation just leave it blank."
127 "If you specify a directory other than the default, you will need to\n"
128 "specify the -h flag to the server when you start it up.\n"
129 "note that it may not have a leading /");
131 setup_titles[eSysAdminName] = _("Citadel administrator username:");
132 setup_text[eSysAdminName] = _(
133 "Please enter the name of the Citadel user account that should be granted "
134 "administrative privileges once created. If using internal authentication "
135 "this user account will be created if it does not exist. For external "
136 "authentication this user account has to exist.");
138 setup_titles[eSysAdminPW] = _("Administrator password:");
139 setup_text[eSysAdminPW] = _(
140 "Enter a password for the system administrator. When setup\n"
141 "completes it will attempt to create the administrator user\n"
142 "and set the password specified here.\n");
144 setup_titles[eUID] = _("Citadel User ID:");
145 setup_text[eUID] = _(
146 "Citadel needs to run under its own user ID. This would\n"
147 "typically be called \"citadel\", but if you are running Citadel\n"
148 "as a public site, you might also call it \"bbs\" or \"guest\".\n"
149 "The server will run under this user ID. Please specify that\n"
150 "user ID here. You may specify either a user name or a numeric\n"
153 setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:");
154 setup_text[eIP_ADDR] = _(
155 "Please specify the IP address which the server should be listening to. "
156 "You can name a specific IPv4 or IPv6 address, or you can specify\n"
157 "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n"
158 "for \"any IPv4 address\". If you leave this blank, Citadel will\n"
159 "listen on all addresses. "
160 "This can usually be left to the default unless multiple instances of Citadel "
161 "are running on the same computer.");
163 setup_titles[eCTDL_Port] = _("Server port number:");
164 setup_text[eCTDL_Port] = _(
165 "Specify the TCP port number on which your server will run.\n"
166 "Normally, this will be port 504, which is the official port\n"
167 "assigned by the IANA for Citadel servers. You will only need\n"
168 "to specify a different port number if you run multiple instances\n"
169 "of Citadel on the same computer and there is something else\n"
170 "already using port 504.\n");
172 setup_titles[eAuthType] = _("Authentication method to use:");
173 setup_text[eAuthType] = _(
174 "Please choose the user authentication mode. By default Citadel will use its "
175 "own internal user accounts database. If you choose Host, Citadel users will "
176 "have accounts on the host system, authenticated via /etc/passwd or a PAM "
177 "source. LDAP chooses an RFC 2307 compliant directory server, the last option "
178 "chooses the nonstandard MS Active Directory LDAP scheme."
180 "Do not change this option unless you are sure it is required, since changing "
181 "back requires a full reinstall of Citadel."
183 " 0. Self contained authentication\n"
184 " 1. Host system integrated authentication\n"
185 " 2. External LDAP - RFC 2307 POSIX schema\n"
186 " 3. External LDAP - MS Active Directory schema\n"
188 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
190 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
192 setup_titles[eLDAP_Host] = _("LDAP host:");
193 setup_text[eLDAP_Host] = _(
194 "Please enter the host name or IP address of your LDAP server.\n");
196 setup_titles[eLDAP_Port] = _("LDAP port number:");
197 setup_text[eLDAP_Port] = _(
198 "Please enter the port number of the LDAP service (usually 389).\n");
200 setup_titles[eLDAP_Base_DN] = _("LDAP base DN:");
201 setup_text[eLDAP_Base_DN] = _(
202 "Please enter the Base DN to search for authentication\n"
203 "(for example: dc=example,dc=com)\n");
205 setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:");
206 setup_text[eLDAP_Bind_DN] = _(
207 "Please enter the DN of an account to use for binding to the LDAP server for "
208 "performing queries. The account does not require any other privileges. If "
209 "your LDAP server allows anonymous queries, you can leave this blank.\n");
211 setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:");
212 setup_text[eLDAP_Bind_PW] = _(
213 "If you entered a Bind DN in the previous question, you must now enter\n"
214 "the password associated with that account. Otherwise, you can leave this\n"
218 // Debug loading of locales... Strace does a better job though.
219 printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale"));
220 printf("Text domain: %s\n", textdomain("citadel-setup"));
221 printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8"));
224 for (i = 0; i < eMaxQuestions; i++)
225 printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
232 void title(const char *text) {
233 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
237 int yesno(const char *question, int default_value) {
242 printf("%s\n%s [%s] --> ", question, _("Yes/No"), ( default_value ? _("Yes") : _("No") ));
243 if (fgets(buf, sizeof buf, stdin)) {
244 answer = tolower(buf[0]);
245 if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
246 answer = default_value;
248 else if (answer == 'y') {
251 else if (answer == 'n') {
255 } while ((answer < 0) || (answer > 1));
260 void important_message(const char *title, const char *msgtext) {
263 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");
264 printf(" %s \n\n%s\n\n", title, msgtext);
265 printf("%s", _("Press return to continue..."));
266 if (fgets(buf, sizeof buf, stdin)) {
272 void important_msgnum(int msgnum) {
273 important_message(_("Important Message"), setup_text[msgnum]);
277 void display_error(char *error_message_format, ...) {
282 va_start(arg_ptr, error_message_format);
283 StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
286 important_message(_("Error"), ChrPtr(Msg));
291 void progress(char *text, long int curr, long int cmax) {
292 static long dots_printed = 0L;
296 printf("%s\n", text);
297 printf("....................................................");
298 printf("..........................\r");
300 } else if (curr == cmax) {
301 printf("\r%79s\n", "");
303 a = (curr * 100) / cmax;
306 while (dots_printed < a) {
315 int uds_connectsock(char *sockpath) {
317 struct sockaddr_un addr;
319 memset(&addr, 0, sizeof(addr));
320 addr.sun_family = AF_UNIX;
321 strcpy(addr.sun_path, sockpath);
323 s = socket(AF_UNIX, SOCK_STREAM, 0);
328 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
338 * input binary data from socket
340 void serv_read(char *buf, int bytes) {
344 while (len < bytes) {
345 rlen = read(serv_sock, &buf[len], bytes - len);
355 * send binary to server
357 void serv_write(char *buf, int nbytes) {
358 int bytes_written = 0;
360 while (bytes_written < nbytes) {
361 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
365 bytes_written = bytes_written + retval;
371 * input string from socket - implemented in terms of serv_read()
373 void serv_gets(char *buf) {
376 /* Read one character at a time.
379 serv_read(&buf[i], 1);
380 if (buf[i] == '\n' || i == (SIZ-1))
384 /* If we got a long line, discard characters until the newline.
387 while (buf[i] != '\n') {
388 serv_read(&buf[i], 1);
392 /* Strip all trailing nonprintables (crlf)
399 * send line to server - implemented in terms of serv_write()
401 void serv_puts(char *buf) {
402 serv_write(buf, strlen(buf));
408 * Convenience functions to get/set system configuration entries
410 void getconf_str(char *buf, char *key) {
414 sprintf(cmd, "CONF GETVAL|%s", key);
418 extract_token(buf, &ret[4], 0, '|', SIZ);
426 int getconf_int(char *key) {
428 getconf_str(buf, key);
433 void setconf_str(char *key, char *val) {
436 sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
442 void setconf_int(char *key, int val) {
445 sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
452 * On systems which use xinetd, see if we can offer to install Citadel as
453 * the default telnet target.
455 void check_xinetd_entry(void)
457 char *filename = "/etc/xinetd.d/telnet";
460 int already_citadel = 0;
463 fp = fopen(filename, "r+");
464 if (fp == NULL) return; /* Not there. Oh well... */
466 while (fgets(buf, sizeof buf, fp) != NULL) {
467 if (strstr(buf, "/citadel") != NULL) {
472 if (already_citadel) return; /* Already set up this way. */
474 /* Otherwise, prompt the user to create an entry. */
475 if (getenv("CREATE_XINETD_ENTRY") != NULL) {
476 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
481 snprintf(buf, sizeof buf,
482 _("Setup can configure the \"xinetd\" service to automatically\n"
483 "connect incoming telnet sessions to Citadel, bypassing the\n"
484 "host system login: prompt. Would you like to do this?\n"
487 if (yesno(buf, 1) == 0) {
492 fp = fopen(filename, "w");
494 "# description: telnet service for Citadel users\n"
499 " socket_type = stream\n"
502 " server = /usr/sbin/in.telnetd\n"
503 " server_args = -h -L %s/citadel\n"
504 " log_on_failure += USERID\n"
510 /* Now try to restart the service. This will not have the intended effect on Solaris, but who the hell uses Solaris anymore? */
511 rv = system("systemctl restart xinetd >/dev/null 2>&1");
513 rv = system("service xinetd restart >/dev/null 2>&1");
516 display_error(_("failed to restart xinetd.\n"));
521 void disable_other_mtas(void)
523 if ((getenv("ACT_AS_MTA") == NULL) || (getenv("ACT_AS_MTA") && strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
524 /* Offer to disable other MTA's on the system. */
525 /* FIXME this has to be rewritten to work in the new systemd-based world. */
530 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
535 strcpy(setupmsg, "");
538 printf("\n%s\n", prompt_text);
539 printf("%s\n%s\n", _("This is currently set to:"), Target);
540 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
541 if (fgets(buf, sizeof buf, stdin)) {
542 buf[strlen(buf) - 1] = 0;
544 if (!IsEmptyStr(buf)) {
550 void set_bool_val(int msgpos, int *ip, char *DefValue) {
551 title(setup_titles[msgpos]);
552 *ip = yesno(setup_text[msgpos], *ip);
556 void set_str_val(int msgpos, char *Target, char *DefValue)
558 strprompt(setup_titles[msgpos],
566 /* like set_str_val() but for numeric values */
567 void set_int_val(int msgpos, int *target, char *default_value)
570 sprintf(buf, "%d", *target);
572 set_str_val(msgpos, buf, default_value);
573 } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
578 void edit_value(int curr)
580 struct passwd *pw = NULL;
581 char ctdluidname[256];
583 char *default_value = NULL;
589 if (default_value == NULL) {
596 getconf_str(admin_name, "c_sysadm");
597 set_str_val(curr, admin_name, default_value);
598 setconf_str("c_sysadm", admin_name);
602 set_str_val(curr, admin_pass, default_value);
606 ctdluid = getconf_int("c_ctdluid");
607 pw = getpwuid(ctdluid);
609 set_int_val(curr, &ctdluid, default_value);
612 strcpy(ctdluidname, pw->pw_name);
613 set_str_val(curr, ctdluidname, default_value);
614 pw = getpwnam(ctdluidname);
616 ctdluid = pw->pw_uid;
618 else if (atoi(ctdluidname) > 0) {
619 ctdluid = atoi(ctdluidname);
622 setconf_int("c_ctdluid", ctdluid);
626 getconf_str(buf, "c_ip_addr");
627 set_str_val(curr, buf, default_value);
628 setconf_str("c_ip_addr", buf);
632 portnum = getconf_int("c_port_number");
633 set_int_val(curr, &portnum, default_value);
634 setconf_int("c_port_number", portnum);
638 auth = getconf_int("c_auth_mode");
639 set_int_val(curr, &auth, default_value);
640 setconf_int("c_auth_mode", auth);
644 getconf_str(buf, "c_ldap_host");
645 if (IsEmptyStr(buf)) {
646 strcpy(buf, "localhost");
648 set_str_val(curr, buf, default_value);
649 setconf_str("c_ldap_host", buf);
653 lportnum = getconf_int("c_ldap_port");
657 set_int_val(curr, &lportnum, default_value);
658 setconf_int("c_ldap_port", lportnum);
662 getconf_str(buf, "c_ldap_base_dn");
663 set_str_val(curr, buf, default_value);
664 setconf_str("c_ldap_base_dn", buf);
668 getconf_str(buf, "c_ldap_bind_dn");
669 set_str_val(curr, buf, default_value);
670 setconf_str("c_ldap_bind_dn", buf);
674 getconf_str(buf, "c_ldap_bind_pw");
675 set_str_val(curr, buf, default_value);
676 setconf_str("c_ldap_bind_pw", buf);
683 * Strip "db" entries out of /etc/nsswitch.conf
692 int file_changed = 0;
693 char new_filename[64];
696 fp_read = fopen(NSSCONF, "r");
697 if (fp_read == NULL) {
701 strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
702 fd_write = mkstemp(new_filename);
708 while (fgets(buf, sizeof buf, fp_read) != NULL) {
710 for (i=0; buf_nc[i]; ++i) {
711 if (buf_nc[i] == '#') {
716 for (i=0; i<strlen(buf_nc); ++i) {
717 if (!strncasecmp(&buf_nc[i], "db", 2)) {
719 if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
721 strcpy(&buf_nc[i], &buf_nc[i+2]);
722 strcpy(&buf[i], &buf[i+2]);
724 strcpy(&buf_nc[i], &buf_nc[i+1]);
725 strcpy(&buf[i], &buf[i+1]);
731 long buflen = strlen(buf);
732 if (write(fd_write, buf, buflen) != buflen) {
735 unlink(new_filename);
743 unlink(new_filename);
747 snprintf(question, sizeof question,
750 "/etc/nsswitch.conf is configured to use the 'db' module for\n"
751 "one or more services. This is not necessary on most systems,\n"
752 "and it is known to crash the Citadel server when delivering\n"
753 "mail to the Internet.\n"
755 "Do you want this module to be automatically disabled?\n"
760 if (yesno(question, 1)) {
761 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
764 fprintf(stderr, "failed to edit %s.\n", NSSCONF);
766 chmod(NSSCONF, 0644);
768 unlink(new_filename);
773 * Messages that are no longer in use.
774 * We keep them here so we don't lose the translations if we need them later.
777 important_message(_("Setup finished"),
778 _("Setup of the Citadel server is complete.\n"
779 "If you will be using WebCit, please run its\n"
780 "setup program now; otherwise, run './citadel'\n"
782 important_message(_("Setup failed"),
783 _("Setup is finished, but the Citadel server failed to start.\n"
784 "Go back and check your configuration.\n");
785 important_message(_("Setup finished"),
786 _("Setup is finished. You may now start the server."));
790 int main(int argc, char *argv[])
798 char relhome[PATH_MAX]="";
799 char ctdldir[PATH_MAX]=CTDLDIR;
802 char *activity = NULL;
804 /* Keep a mild groove on */
805 program_title = _("Citadel setup program");
807 /* set an invalid setup type */
810 /* parse command line args */
811 for (a = 0; a < argc; ++a) {
812 if (!strncmp(argv[a], "-u", 2)) {
813 strcpy(aaa, argv[a]);
814 strcpy(aaa, &aaa[2]);
815 setup_type = atoi(aaa);
817 else if (!strncmp(argv[a], "-h", 2)) {
818 relh=argv[a][2]!='/';
820 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
822 safestrncpy(relhome, &argv[a][2], sizeof relhome);
828 calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
831 enable_home = ( relh | home );
833 if (chdir(ctdl_run_dir) != 0) {
834 display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir);
840 * Connect to the running Citadel server.
842 char *connectingmsg = _("Connecting to Citadel server");
843 for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) { /* wait for server to start up */
844 progress(connectingmsg, i, 30);
845 serv_sock = uds_connectsock(file_citadel_admin_socket);
848 progress(connectingmsg, 30, 30);
853 _("Setup could not connect to a running Citadel server."),
854 strerror(errno), file_citadel_admin_socket
860 * read the server greeting
864 display_error("%s\n", buf);
869 * Are we connected to the correct Citadel server?
874 display_error("%s\n", buf);
878 while (serv_gets(buf), strcmp(buf, "000")) {
880 if (atoi(buf) != REV_LEVEL) {
881 display_error("%s\n",
882 _("Your setup program and Citadel server are from different versions.")
890 printf("\n\n\n *** %s ***\n\n", program_title);
892 /* Go through a series of dialogs prompting for config info */
893 for (curr = 1; curr < eMaxQuestions; ++curr) {
896 if ( (curr == eAuthType)
897 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
898 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
900 curr += 5; /* skip LDAP questions if we're not authenticating against LDAP */
903 if (curr == eSysAdminName) {
904 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
905 /* for native auth mode, fetch the admin's existing pw */
906 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
910 extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
914 ++curr; /* skip the password question for non-native auth modes */
919 if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
925 if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
926 display_error("%s\n", _("failed to create directories"));
929 activity = _("Reconfiguring Citadel server");
930 progress(activity, 0, 5);
931 sleep(1); /* Let the message appear briefly */
934 * Create the administrator account. It's ok if the command fails if this user already exists.
936 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
937 progress(activity, 1, 5);
938 snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
940 progress(activity, 2, 5);
943 progress(activity, 3, 5);
946 * Assign the desired password and access level to the administrator account.
948 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
949 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
951 progress(activity, 4, 5);
954 int admin_flags = extract_int(&buf[4], 2);
955 int admin_times_called = extract_int(&buf[4], 3);
956 int admin_msgs_posted = extract_int(&buf[4], 4);
957 snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
958 admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
964 progress(activity, 5, 5);
966 check_xinetd_entry(); /* Check /etc/xinetd.d/telnet */
967 disable_other_mtas(); /* Offer to disable other MTAs */
968 fixnss(); /* Check for the 'db' nss and offer to disable it */
973 activity = _("Restarting Citadel server to apply changes");
974 progress(activity, 0, 51);
978 long original_start_time = extract_long(&buf[4], 3);
980 progress(activity, 1, 51);
982 progress(activity, 2, 51);
985 display_error("%s\n", buf);
992 for (i=3; i<=6; ++i) { /* wait for server to shut down */
993 progress(activity, i, 51);
997 for (i=7; ((i<=48) && (serv_sock < 0)) ; ++i) { /* wait for server to start up */
998 progress(activity, i, 51);
999 serv_sock = uds_connectsock(file_citadel_admin_socket);
1003 progress(activity, 49, 51);
1006 progress(activity, 50, 51);
1009 long new_start_time = extract_long(&buf[4], 3);
1012 progress(activity, 51, 51);
1014 if ((original_start_time == new_start_time) || (new_start_time <= 0)) {
1015 display_error("%s\n", _("Setup failed to restart Citadel server. Please restart it manually."));