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 /");
132 setup_titles[eSysAdminName] = _("Citadel administrator username:");
133 setup_text[eSysAdminName] = _(
134 "Please enter the name of the Citadel user account that should be granted "
135 "administrative privileges once created. If using internal authentication "
136 "this user account will be created if it does not exist. For external "
137 "authentication this user account has to exist.");
140 setup_titles[eSysAdminPW] = _("Administrator password:");
141 setup_text[eSysAdminPW] = _(
142 "Enter a password for the system administrator. When setup\n"
143 "completes it will attempt to create the administrator user\n"
144 "and set the password specified here.\n");
146 setup_titles[eUID] = _("Citadel User ID:");
147 setup_text[eUID] = _(
148 "Citadel needs to run under its own user ID. This would\n"
149 "typically be called \"citadel\", but if you are running Citadel\n"
150 "as a public site, you might also call it \"bbs\" or \"guest\".\n"
151 "The server will run under this user ID. Please specify that\n"
152 "user ID here. You may specify either a user name or a numeric\n"
155 setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:");
156 setup_text[eIP_ADDR] = _(
157 "Please specify the IP address which the server should be listening to. "
158 "You can name a specific IPv4 or IPv6 address, or you can specify\n"
159 "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n"
160 "for \"any IPv4 address\". If you leave this blank, Citadel will\n"
161 "listen on all addresses. "
162 "This can usually be left to the default unless multiple instances of Citadel "
163 "are running on the same computer.");
165 setup_titles[eCTDL_Port] = _("Server port number:");
166 setup_text[eCTDL_Port] = _(
167 "Specify the TCP port number on which your server will run.\n"
168 "Normally, this will be port 504, which is the official port\n"
169 "assigned by the IANA for Citadel servers. You will only need\n"
170 "to specify a different port number if you run multiple instances\n"
171 "of Citadel on the same computer and there is something else\n"
172 "already using port 504.\n");
174 setup_titles[eAuthType] = _("Authentication method to use:");
175 setup_text[eAuthType] = _(
176 "Please choose the user authentication mode. By default Citadel will use its "
177 "own internal user accounts database. If you choose Host, Citadel users will "
178 "have accounts on the host system, authenticated via /etc/passwd or a PAM "
179 "source. LDAP chooses an RFC 2307 compliant directory server, the last option "
180 "chooses the nonstandard MS Active Directory LDAP scheme."
182 "Do not change this option unless you are sure it is required, since changing "
183 "back requires a full reinstall of Citadel."
185 " 0. Self contained authentication\n"
186 " 1. Host system integrated authentication\n"
187 " 2. External LDAP - RFC 2307 POSIX schema\n"
188 " 3. External LDAP - MS Active Directory schema\n"
190 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
192 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
194 setup_titles[eLDAP_Host] = _("LDAP host:");
195 setup_text[eLDAP_Host] = _(
196 "Please enter the host name or IP address of your LDAP server.\n");
198 setup_titles[eLDAP_Port] = _("LDAP port number:");
199 setup_text[eLDAP_Port] = _(
200 "Please enter the port number of the LDAP service (usually 389).\n");
202 setup_titles[eLDAP_Base_DN] = _("LDAP base DN:");
203 setup_text[eLDAP_Base_DN] = _(
204 "Please enter the Base DN to search for authentication\n"
205 "(for example: dc=example,dc=com)\n");
207 setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:");
208 setup_text[eLDAP_Bind_DN] = _(
209 "Please enter the DN of an account to use for binding to the LDAP server for "
210 "performing queries. The account does not require any other privileges. If "
211 "your LDAP server allows anonymous queries, you can leave this blank.\n");
213 setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:");
214 setup_text[eLDAP_Bind_PW] = _(
215 "If you entered a Bind DN in the previous question, you must now enter\n"
216 "the password associated with that account. Otherwise, you can leave this\n"
220 // Debug loading of locales... Strace does a better job though.
221 printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale"));
222 printf("Text domain: %s\n", textdomain("citadel-setup"));
223 printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8"));
226 for (i = 0; i < eMaxQuestions; i++)
227 printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
234 void title(const char *text) {
235 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
239 int yesno(const char *question, int default_value) {
244 printf("%s\n%s [%s] --> ", question, _("Yes/No"), ( default_value ? _("Yes") : _("No") ));
245 if (fgets(buf, sizeof buf, stdin)) {
246 answer = tolower(buf[0]);
247 if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
248 answer = default_value;
250 else if (answer == 'y') {
253 else if (answer == 'n') {
257 } while ((answer < 0) || (answer > 1));
262 void important_message(const char *title, const char *msgtext) {
265 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");
266 printf(" %s \n\n%s\n\n", title, msgtext);
267 printf("%s", _("Press return to continue..."));
268 if (fgets(buf, sizeof buf, stdin)) {
274 void important_msgnum(int msgnum) {
275 important_message(_("Important Message"), setup_text[msgnum]);
279 void display_error(char *error_message_format, ...) {
284 va_start(arg_ptr, error_message_format);
285 StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
288 important_message(_("Error"), ChrPtr(Msg));
293 void progress(char *text, long int curr, long int cmax) {
294 static long dots_printed = 0L;
298 printf("%s\n", text);
299 printf("....................................................");
300 printf("..........................\r");
302 } else if (curr == cmax) {
303 printf("\r%79s\n", "");
305 a = (curr * 100) / cmax;
308 while (dots_printed < a) {
317 int uds_connectsock(char *sockpath) {
319 struct sockaddr_un addr;
321 memset(&addr, 0, sizeof(addr));
322 addr.sun_family = AF_UNIX;
323 strcpy(addr.sun_path, sockpath);
325 s = socket(AF_UNIX, SOCK_STREAM, 0);
330 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
340 * input binary data from socket
342 void serv_read(char *buf, int bytes) {
346 while (len < bytes) {
347 rlen = read(serv_sock, &buf[len], bytes - len);
357 * send binary to server
359 void serv_write(char *buf, int nbytes) {
360 int bytes_written = 0;
362 while (bytes_written < nbytes) {
363 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
367 bytes_written = bytes_written + retval;
373 * input string from socket - implemented in terms of serv_read()
375 void serv_gets(char *buf) {
378 /* Read one character at a time.
381 serv_read(&buf[i], 1);
382 if (buf[i] == '\n' || i == (SIZ-1))
386 /* If we got a long line, discard characters until the newline.
389 while (buf[i] != '\n') {
390 serv_read(&buf[i], 1);
394 /* Strip all trailing nonprintables (crlf)
401 * send line to server - implemented in terms of serv_write()
403 void serv_puts(char *buf) {
404 serv_write(buf, strlen(buf));
410 * Convenience functions to get/set system configuration entries
412 void getconf_str(char *buf, char *key) {
416 sprintf(cmd, "CONF GETVAL|%s", key);
420 extract_token(buf, &ret[4], 0, '|', SIZ);
428 int getconf_int(char *key) {
430 getconf_str(buf, key);
435 void setconf_str(char *key, char *val) {
438 sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
444 void setconf_int(char *key, int val) {
447 sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
454 * On systems which use xinetd, see if we can offer to install Citadel as
455 * the default telnet target.
457 void check_xinetd_entry(void)
459 char *filename = "/etc/xinetd.d/telnet";
462 int already_citadel = 0;
465 fp = fopen(filename, "r+");
466 if (fp == NULL) return; /* Not there. Oh well... */
468 while (fgets(buf, sizeof buf, fp) != NULL) {
469 if (strstr(buf, "/citadel") != NULL) {
474 if (already_citadel) return; /* Already set up this way. */
476 /* Otherwise, prompt the user to create an entry. */
477 if (getenv("CREATE_XINETD_ENTRY") != NULL) {
478 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
483 snprintf(buf, sizeof buf,
484 _("Setup can configure the \"xinetd\" service to automatically\n"
485 "connect incoming telnet sessions to Citadel, bypassing the\n"
486 "host system login: prompt. Would you like to do this?\n"
489 if (yesno(buf, 1) == 0) {
494 fp = fopen(filename, "w");
496 "# description: telnet service for Citadel users\n"
501 " socket_type = stream\n"
504 " server = /usr/sbin/in.telnetd\n"
505 " server_args = -h -L %s/citadel\n"
506 " log_on_failure += USERID\n"
512 /* Now try to restart the service. This will not have the intended effect on Solaris, but who the hell uses Solaris anymore? */
513 rv = system("systemctl restart xinetd >/dev/null 2>&1");
515 rv = system("service xinetd restart >/dev/null 2>&1");
518 display_error(_("failed to restart xinetd.\n"));
523 void disable_other_mtas(void)
525 if ((getenv("ACT_AS_MTA") == NULL) || (getenv("ACT_AS_MTA") && strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
526 /* Offer to disable other MTA's on the system. */
527 /* FIXME this has to be rewritten to work in the new systemd-based world. */
532 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
537 strcpy(setupmsg, "");
540 printf("\n%s\n", prompt_text);
541 printf("%s\n%s\n", _("This is currently set to:"), Target);
542 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
543 if (fgets(buf, sizeof buf, stdin)) {
544 buf[strlen(buf) - 1] = 0;
546 if (!IsEmptyStr(buf)) {
552 void set_bool_val(int msgpos, int *ip, char *DefValue) {
553 title(setup_titles[msgpos]);
554 *ip = yesno(setup_text[msgpos], *ip);
558 void set_str_val(int msgpos, char *Target, char *DefValue)
560 strprompt(setup_titles[msgpos],
568 /* like set_str_val() but for numeric values */
569 void set_int_val(int msgpos, int *target, char *default_value)
572 sprintf(buf, "%d", *target);
574 set_str_val(msgpos, buf, default_value);
575 } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
580 void edit_value(int curr)
582 struct passwd *pw = NULL;
583 char ctdluidname[256];
585 char *default_value = NULL;
591 if (default_value == NULL) {
598 getconf_str(admin_name, "c_sysadm");
599 set_str_val(curr, admin_name, default_value);
600 setconf_str("c_sysadm", admin_name);
604 set_str_val(curr, admin_pass, default_value);
608 ctdluid = getconf_int("c_ctdluid");
609 pw = getpwuid(ctdluid);
611 set_int_val(curr, &ctdluid, default_value);
614 strcpy(ctdluidname, pw->pw_name);
615 set_str_val(curr, ctdluidname, default_value);
616 pw = getpwnam(ctdluidname);
618 ctdluid = pw->pw_uid;
620 else if (atoi(ctdluidname) > 0) {
621 ctdluid = atoi(ctdluidname);
624 setconf_int("c_ctdluid", ctdluid);
628 getconf_str(buf, "c_ip_addr");
629 set_str_val(curr, buf, default_value);
630 setconf_str("c_ip_addr", buf);
634 portnum = getconf_int("c_port_number");
635 set_int_val(curr, &portnum, default_value);
636 setconf_int("c_port_number", portnum);
640 auth = getconf_int("c_auth_mode");
641 set_int_val(curr, &auth, default_value);
642 setconf_int("c_auth_mode", auth);
646 getconf_str(buf, "c_ldap_host");
647 if (IsEmptyStr(buf)) {
648 strcpy(buf, "localhost");
650 set_str_val(curr, buf, default_value);
651 setconf_str("c_ldap_host", buf);
655 lportnum = getconf_int("c_ldap_port");
659 set_int_val(curr, &lportnum, default_value);
660 setconf_int("c_ldap_port", lportnum);
664 getconf_str(buf, "c_ldap_base_dn");
665 set_str_val(curr, buf, default_value);
666 setconf_str("c_ldap_base_dn", buf);
670 getconf_str(buf, "c_ldap_bind_dn");
671 set_str_val(curr, buf, default_value);
672 setconf_str("c_ldap_bind_dn", buf);
676 getconf_str(buf, "c_ldap_bind_pw");
677 set_str_val(curr, buf, default_value);
678 setconf_str("c_ldap_bind_pw", buf);
685 * Strip "db" entries out of /etc/nsswitch.conf
694 int file_changed = 0;
695 char new_filename[64];
698 fp_read = fopen(NSSCONF, "r");
699 if (fp_read == NULL) {
703 strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
704 fd_write = mkstemp(new_filename);
710 while (fgets(buf, sizeof buf, fp_read) != NULL) {
712 for (i=0; buf_nc[i]; ++i) {
713 if (buf_nc[i] == '#') {
718 for (i=0; i<strlen(buf_nc); ++i) {
719 if (!strncasecmp(&buf_nc[i], "db", 2)) {
721 if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
723 strcpy(&buf_nc[i], &buf_nc[i+2]);
724 strcpy(&buf[i], &buf[i+2]);
726 strcpy(&buf_nc[i], &buf_nc[i+1]);
727 strcpy(&buf[i], &buf[i+1]);
733 long buflen = strlen(buf);
734 if (write(fd_write, buf, buflen) != buflen) {
737 unlink(new_filename);
745 unlink(new_filename);
749 snprintf(question, sizeof question,
752 "/etc/nsswitch.conf is configured to use the 'db' module for\n"
753 "one or more services. This is not necessary on most systems,\n"
754 "and it is known to crash the Citadel server when delivering\n"
755 "mail to the Internet.\n"
757 "Do you want this module to be automatically disabled?\n"
762 if (yesno(question, 1)) {
763 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
766 fprintf(stderr, "failed to edit %s.\n", NSSCONF);
768 chmod(NSSCONF, 0644);
770 unlink(new_filename);
775 * Messages that are no longer in use.
776 * We keep them here so we don't lose the translations if we need them later.
779 important_message(_("Setup finished"),
780 _("Setup of the Citadel server is complete.\n"
781 "If you will be using WebCit, please run its\n"
782 "setup program now; otherwise, run './citadel'\n"
784 important_message(_("Setup failed"),
785 _("Setup is finished, but the Citadel server failed to start.\n"
786 "Go back and check your configuration.\n");
787 important_message(_("Setup finished"),
788 _("Setup is finished. You may now start the server."));
792 int main(int argc, char *argv[])
800 char relhome[PATH_MAX]="";
801 char ctdldir[PATH_MAX]=CTDLDIR;
804 char *activity = NULL;
806 /* Keep a mild groove on */
807 program_title = _("Citadel setup program");
809 /* set an invalid setup type */
812 /* parse command line args */
813 for (a = 0; a < argc; ++a) {
814 if (!strncmp(argv[a], "-u", 2)) {
815 strcpy(aaa, argv[a]);
816 strcpy(aaa, &aaa[2]);
817 setup_type = atoi(aaa);
819 else if (!strncmp(argv[a], "-h", 2)) {
820 relh=argv[a][2]!='/';
822 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
824 safestrncpy(relhome, &argv[a][2], sizeof relhome);
830 calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
833 enable_home = ( relh | home );
835 if (chdir(ctdl_run_dir) != 0) {
836 display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir);
842 * Connect to the running Citadel server.
844 char *connectingmsg = _("Connecting to Citadel server");
845 for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) { /* wait for server to start up */
846 progress(connectingmsg, i, 30);
847 serv_sock = uds_connectsock(file_citadel_admin_socket);
850 progress(connectingmsg, 30, 30);
855 _("Setup could not connect to a running Citadel server."),
856 strerror(errno), file_citadel_admin_socket
862 * read the server greeting
866 display_error("%s\n", buf);
871 * Are we connected to the correct Citadel server?
876 display_error("%s\n", buf);
880 while (serv_gets(buf), strcmp(buf, "000")) {
882 if (atoi(buf) != REV_LEVEL) {
883 display_error("%s\n",
884 _("Your setup program and Citadel server are from different versions.")
892 printf("\n\n\n *** %s ***\n\n", program_title);
894 /* Go through a series of dialogs prompting for config info */
895 for (curr = 1; curr < eMaxQuestions; ++curr) {
898 if ( (curr == eAuthType)
899 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
900 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
902 curr += 5; /* skip LDAP questions if we're not authenticating against LDAP */
905 if (curr == eSysAdminName) {
906 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
907 /* for native auth mode, fetch the admin's existing pw */
908 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
912 extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
916 ++curr; /* skip the password question for non-native auth modes */
921 if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
927 if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
928 display_error("%s\n", _("failed to create directories"));
931 activity = _("Reconfiguring Citadel server");
932 progress(activity, 0, 5);
933 sleep(1); /* Let the message appear briefly */
936 * Create the administrator account. It's ok if the command fails if this user already exists.
938 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
939 progress(activity, 1, 5);
940 snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
942 progress(activity, 2, 5);
945 progress(activity, 3, 5);
948 * Assign the desired password and access level to the administrator account.
950 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
951 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
953 progress(activity, 4, 5);
956 int admin_flags = extract_int(&buf[4], 2);
957 int admin_times_called = extract_int(&buf[4], 3);
958 int admin_msgs_posted = extract_int(&buf[4], 4);
959 snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
960 admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
966 progress(activity, 5, 5);
968 check_xinetd_entry(); /* Check /etc/xinetd.d/telnet */
969 disable_other_mtas(); /* Offer to disable other MTAs */
970 fixnss(); /* Check for the 'db' nss and offer to disable it */
975 activity = _("Restarting Citadel server to apply changes");
976 progress(activity, 0, 51);
980 long original_start_time = extract_long(&buf[4], 3);
982 progress(activity, 1, 51);
984 progress(activity, 2, 51);
987 display_error("%s\n", buf);
994 for (i=3; i<=6; ++i) { /* wait for server to shut down */
995 progress(activity, i, 51);
999 for (i=7; ((i<=48) && (serv_sock < 0)) ; ++i) { /* wait for server to start up */
1000 progress(activity, i, 51);
1001 serv_sock = uds_connectsock(file_citadel_admin_socket);
1005 progress(activity, 49, 51);
1008 progress(activity, 50, 51);
1011 long new_start_time = extract_long(&buf[4], 3);
1014 progress(activity, 51, 51);
1016 if ((original_start_time == new_start_time) || (new_start_time <= 0)) {
1017 display_error("%s\n", _("Setup failed to restart Citadel server. Please restart it manually."));