1 // Citadel setup utility
3 // Copyright (c) 1987-2022 by the citadel.org team
5 // This program is open source software. Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7 // The program is distributed without any warranty, expressed or implied.
9 #define SHOW_ME_VAPPEND_PRINTF
16 #include <sys/types.h>
25 #include <sys/socket.h>
28 #include <libcitadel.h>
29 #include "../server/citadel.h"
31 #include "../server/sysdep.h"
32 #include "../server/citadel_dirs.h"
40 #define _(string) gettext(string)
42 #define _(string) (string)
45 #define SERVICE_NAME "citadel"
46 #define PROTO_NAME "tcp"
47 #define NSSCONF "/etc/nsswitch.conf"
49 typedef enum _SetupStep {
65 ///"CREATE_XINETD_ENTRY";
66 /* Environment variables, don't translate! */
67 const char *EnvNames [eMaxQuestions] = {
82 int setup_type = (-1);
87 int serv_sock = (-1) ;
89 const char *setup_titles[eMaxQuestions];
90 const char *setup_text[eMaxQuestions];
94 void SetTitles(void) {
103 setlocale(LC_MESSAGES, getenv("LANG"));
104 bindtextdomain("citadel-setup", LOCALEDIR"/locale");
105 textdomain("citadel-setup");
106 bind_textdomain_codeset("citadel-setup","UTF8");
109 setup_titles[eCitadelHomeDir] = _("Citadel Home Directory");
111 setup_text[eCitadelHomeDir] = _(
112 "Enter the full pathname of the directory in which the Citadel\n"
113 "installation you are creating or updating resides. If you\n"
114 "specify a directory other than the default, you will need to\n"
115 "specify the -h flag to the server when you start it up.\n");
117 setup_text[eCitadelHomeDir] = _(
118 "Enter the subdirectory name for an alternate installation of "
119 "Citadel. To do a default installation just leave it blank."
120 "If you specify a directory other than the default, you will need to\n"
121 "specify the -h flag to the server when you start it up.\n"
122 "note that it may not have a leading /");
124 setup_titles[eSysAdminName] = _("Citadel administrator username:");
125 setup_text[eSysAdminName] = _(
126 "Please enter the name of the Citadel user account that should be granted "
127 "administrative privileges once created. If using internal authentication "
128 "this user account will be created if it does not exist. For external "
129 "authentication this user account has to exist.");
131 setup_titles[eSysAdminPW] = _("Administrator password:");
132 setup_text[eSysAdminPW] = _(
133 "Enter a password for the system administrator. When setup\n"
134 "completes it will attempt to create the administrator user\n"
135 "and set the password specified here.\n");
137 setup_titles[eUID] = _("Citadel User ID:");
138 setup_text[eUID] = _(
139 "Citadel needs to run under its own user ID. This would\n"
140 "typically be called \"citadel\", but if you are running Citadel\n"
141 "as a public site, you might also call it \"bbs\" or \"guest\".\n"
142 "The server will run under this user ID. Please specify that\n"
143 "user ID here. You may specify either a user name or a numeric\n"
146 setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:");
147 setup_text[eIP_ADDR] = _(
148 "Please specify the IP address which the server should be listening to. "
149 "You can name a specific IPv4 or IPv6 address, or you can specify\n"
150 "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n"
151 "for \"any IPv4 address\". If you leave this blank, Citadel will\n"
152 "listen on all addresses. "
153 "This can usually be left to the default unless multiple instances of Citadel "
154 "are running on the same computer.");
156 setup_titles[eCTDL_Port] = _("Server port number:");
157 setup_text[eCTDL_Port] = _(
158 "Specify the TCP port number on which your server will run.\n"
159 "Normally, this will be port 504, which is the official port\n"
160 "assigned by the IANA for Citadel servers. You will only need\n"
161 "to specify a different port number if you run multiple instances\n"
162 "of Citadel on the same computer and there is something else\n"
163 "already using port 504.\n");
165 setup_titles[eAuthType] = _("Authentication method to use:");
166 setup_text[eAuthType] = _(
167 "Please choose the user authentication mode. By default Citadel will use its "
168 "own internal user accounts database. If you choose Host, Citadel users will "
169 "have accounts on the host system, authenticated via /etc/passwd or a PAM "
170 "source. LDAP chooses an RFC 2307 compliant directory server, the last option "
171 "chooses the nonstandard MS Active Directory LDAP scheme."
173 "Do not change this option unless you are sure it is required, since changing "
174 "back requires a full reinstall of Citadel."
176 " 0. Self contained authentication\n"
177 " 1. Host system integrated authentication\n"
178 " 2. External LDAP - RFC 2307 POSIX schema\n"
179 " 3. External LDAP - MS Active Directory schema\n"
181 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
183 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
185 setup_titles[eLDAP_Host] = _("LDAP host:");
186 setup_text[eLDAP_Host] = _(
187 "Please enter the host name or IP address of your LDAP server.\n");
189 setup_titles[eLDAP_Port] = _("LDAP port number:");
190 setup_text[eLDAP_Port] = _(
191 "Please enter the port number of the LDAP service (usually 389).\n");
193 setup_titles[eLDAP_Base_DN] = _("LDAP base DN:");
194 setup_text[eLDAP_Base_DN] = _(
195 "Please enter the Base DN to search for authentication\n"
196 "(for example: dc=example,dc=com)\n");
198 setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:");
199 setup_text[eLDAP_Bind_DN] = _(
200 "Please enter the DN of an account to use for binding to the LDAP server for "
201 "performing queries. The account does not require any other privileges. If "
202 "your LDAP server allows anonymous queries, you can leave this blank.\n");
204 setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:");
205 setup_text[eLDAP_Bind_PW] = _(
206 "If you entered a Bind DN in the previous question, you must now enter\n"
207 "the password associated with that account. Otherwise, you can leave this\n"
211 // Debug loading of locales... Strace does a better job though.
212 printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale"));
213 printf("Text domain: %s\n", textdomain("citadel-setup"));
214 printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8"));
217 for (i = 0; i < eMaxQuestions; i++)
218 printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
225 void title(const char *text) {
226 printf("\n\033[1m\033[32m<\033[33m%s\033[32m>\033[0m\n", text);
230 int yesno(const char *question, int default_value) {
235 printf("\033[31m\033[32m%s\n%s [\033[33m%s\033[32m]\033[0m --> ", question, _("Yes/No"), ( default_value ? _("Yes") : _("No") ));
236 if (fgets(buf, sizeof buf, stdin)) {
237 answer = tolower(buf[0]);
238 if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
239 answer = default_value;
241 else if (answer == 'y') {
244 else if (answer == 'n') {
248 } while ((answer < 0) || (answer > 1));
253 void important_message(const char *title, const char *msgtext) {
256 printf("\n%s\n%s\n\n", title, msgtext);
257 printf("%s", _("Press return to continue..."));
258 if (fgets(buf, sizeof buf, stdin)) {
264 void important_msgnum(int msgnum) {
265 important_message(_("Important Message"), setup_text[msgnum]);
269 void display_error(char *error_message_format, ...) {
274 va_start(arg_ptr, error_message_format);
275 StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
278 important_message(_("Error"), ChrPtr(Msg));
283 void progress(char *text, long int curr, long int cmax) {
288 printf("%s\n", text);
289 printf("\033[1m\033[33m[\033[32m............................................................................\033[33m]\033[0m\r");
291 else if (curr == cmax) {
292 printf("\r%79s\n", "");
295 printf("\033[1m\033[33m[\033[32m");
296 a = (curr * 100) / cmax;
299 for (i=0; i<a; ++i) {
308 int uds_connectsock(char *sockpath) {
310 struct sockaddr_un addr;
312 memset(&addr, 0, sizeof(addr));
313 addr.sun_family = AF_UNIX;
314 strcpy(addr.sun_path, sockpath);
316 s = socket(AF_UNIX, SOCK_STREAM, 0);
321 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
330 // input binary data from socket
331 void serv_read(char *buf, int bytes) {
335 while (len < bytes) {
336 rlen = read(serv_sock, &buf[len], bytes - len);
345 // send binary to server
346 void serv_write(char *buf, int nbytes) {
347 int bytes_written = 0;
349 while (bytes_written < nbytes) {
350 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
354 bytes_written = bytes_written + retval;
359 // input string from socket - implemented in terms of serv_read()
360 void serv_gets(char *buf) {
363 // Read one character at a time.
365 serv_read(&buf[i], 1);
366 if (buf[i] == '\n' || i == (SIZ-1))
370 // If we got a long line, discard characters until the newline.
372 while (buf[i] != '\n') {
373 serv_read(&buf[i], 1);
377 // Strip all trailing nonprintables (crlf)
382 // send line to server - implemented in terms of serv_write()
383 void serv_puts(char *buf) {
384 serv_write(buf, strlen(buf));
389 // Convenience functions to get/set system configuration entries
390 void getconf_str(char *buf, char *key) {
394 sprintf(cmd, "CONF GETVAL|%s", key);
398 extract_token(buf, &ret[4], 0, '|', SIZ);
406 int getconf_int(char *key) {
408 getconf_str(buf, key);
413 void setconf_str(char *key, char *val) {
416 sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
422 void setconf_int(char *key, int val) {
425 sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
431 // On systems which use xinetd, see if we can offer to install Citadel as
432 // the default telnet target.
433 void check_xinetd_entry(void) {
434 char *filename = "/etc/xinetd.d/telnet";
437 int already_citadel = 0;
440 fp = fopen(filename, "r+");
441 if (fp == NULL) return; // Not there. Oh well...
443 while (fgets(buf, sizeof buf, fp) != NULL) {
444 if (strstr(buf, "/citadel") != NULL) {
449 if (already_citadel) return; // Already set up this way.
451 // Otherwise, prompt the user to create an entry.
452 if (getenv("CREATE_XINETD_ENTRY") != NULL) {
453 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
458 snprintf(buf, sizeof buf,
459 _("Setup can configure the \"xinetd\" service to automatically\n"
460 "connect incoming telnet sessions to Citadel, bypassing the\n"
461 "host system login: prompt. Would you like to do this?\n"
464 if (yesno(buf, 1) == 0) {
469 fp = fopen(filename, "w");
471 "# description: telnet service for Citadel users\n"
476 " socket_type = stream\n"
479 " server = /usr/sbin/in.telnetd\n"
480 " server_args = -h -L %s/citadel\n"
481 " log_on_failure += USERID\n"
487 // Now try to restart the service. This will not have the intended effect on Solaris, but who uses Solaris anymore?
488 rv = system("systemctl restart xinetd >/dev/null 2>&1");
490 rv = system("service xinetd restart >/dev/null 2>&1");
493 display_error(_("failed to restart xinetd.\n"));
498 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue) {
502 strcpy(setupmsg, "");
505 printf("\n%s\n", prompt_text);
506 printf("%s\n%s\n", _("This is currently set to:"), Target);
507 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
508 if (fgets(buf, sizeof buf, stdin)) {
509 buf[strlen(buf) - 1] = 0;
511 if (!IsEmptyStr(buf)) {
517 void set_bool_val(int msgpos, int *ip, char *DefValue) {
518 title(setup_titles[msgpos]);
519 *ip = yesno(setup_text[msgpos], *ip);
523 void set_str_val(int msgpos, char *Target, char *DefValue) {
524 strprompt(setup_titles[msgpos],
532 // like set_str_val() but for numeric values
533 void set_int_val(int msgpos, int *target, char *default_value) {
535 sprintf(buf, "%d", *target);
537 set_str_val(msgpos, buf, default_value);
538 } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
543 void edit_value(int curr) {
544 struct passwd *pw = NULL;
545 char ctdluidname[256];
547 char *default_value = NULL;
553 if (default_value == NULL) {
560 getconf_str(admin_name, "c_sysadm");
561 set_str_val(curr, admin_name, default_value);
562 setconf_str("c_sysadm", admin_name);
566 set_str_val(curr, admin_pass, default_value);
570 ctdluid = getconf_int("c_ctdluid");
571 pw = getpwuid(ctdluid);
573 set_int_val(curr, &ctdluid, default_value);
576 strcpy(ctdluidname, pw->pw_name);
577 set_str_val(curr, ctdluidname, default_value);
578 pw = getpwnam(ctdluidname);
580 ctdluid = pw->pw_uid;
582 else if (atoi(ctdluidname) > 0) {
583 ctdluid = atoi(ctdluidname);
586 setconf_int("c_ctdluid", ctdluid);
590 getconf_str(buf, "c_ip_addr");
591 set_str_val(curr, buf, default_value);
592 setconf_str("c_ip_addr", buf);
596 portnum = getconf_int("c_port_number");
597 set_int_val(curr, &portnum, default_value);
598 setconf_int("c_port_number", portnum);
602 auth = getconf_int("c_auth_mode");
603 set_int_val(curr, &auth, default_value);
604 setconf_int("c_auth_mode", auth);
608 getconf_str(buf, "c_ldap_host");
609 if (IsEmptyStr(buf)) {
610 strcpy(buf, "localhost");
612 set_str_val(curr, buf, default_value);
613 setconf_str("c_ldap_host", buf);
617 lportnum = getconf_int("c_ldap_port");
621 set_int_val(curr, &lportnum, default_value);
622 setconf_int("c_ldap_port", lportnum);
626 getconf_str(buf, "c_ldap_base_dn");
627 set_str_val(curr, buf, default_value);
628 setconf_str("c_ldap_base_dn", buf);
632 getconf_str(buf, "c_ldap_bind_dn");
633 set_str_val(curr, buf, default_value);
634 setconf_str("c_ldap_bind_dn", buf);
638 getconf_str(buf, "c_ldap_bind_pw");
639 set_str_val(curr, buf, default_value);
640 setconf_str("c_ldap_bind_pw", buf);
646 // Messages that are no longer in use.
647 // We keep them here so we don't lose the translations if we need them later.
649 important_message(_("Setup finished"),
650 _("Setup of the Citadel server is complete.\n"
651 "If you will be using WebCit, please run its\n"
652 "setup program now; otherwise, run './citadel'\n"
654 important_message(_("Setup failed"),
655 _("Setup is finished, but the Citadel server failed to start.\n"
656 "Go back and check your configuration.\n");
657 important_message(_("Setup finished"),
658 _("Setup is finished. You may now start the server."));
662 int main(int argc, char *argv[]) {
667 char ctdldir[PATH_MAX]=CTDLDIR;
670 char *activity = NULL;
672 // Keep a mild groove on
673 program_title = _("Citadel setup program");
675 // set an invalid setup type
678 // parse command line args
679 for (a = 0; a < argc; ++a) {
680 if (!strncmp(argv[a], "-u", 2)) {
681 strcpy(aaa, argv[a]);
682 strcpy(aaa, &aaa[2]);
683 setup_type = atoi(aaa);
685 else if (!strncmp(argv[a], "-h", 2)) {
686 safestrncpy(ctdldir, &argv[a][2], sizeof ctdldir);
690 if (chdir(ctdldir) != 0) {
691 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
697 // Connect to the running Citadel server.
698 char *connectingmsg = _("Connecting to Citadel server");
699 for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) { // wait for server to start up
700 progress(connectingmsg, i, 30);
701 serv_sock = uds_connectsock(file_citadel_admin_socket);
704 progress(connectingmsg, 30, 30);
709 _("Setup could not connect to a running Citadel server."),
710 strerror(errno), file_citadel_admin_socket
715 // read the server greeting
718 display_error("%s\n", buf);
722 // Are we connected to the correct Citadel server?
726 display_error("%s\n", buf);
730 while (serv_gets(buf), strcmp(buf, "000")) {
732 if (atoi(buf) != REV_LEVEL) {
733 display_error("%s\n",
734 _("Your setup program and Citadel server are from different versions.")
742 printf("\n\n\n *** %s ***\n\n", program_title);
744 // Go through a series of dialogs prompting for config info
745 for (curr = 1; curr < eMaxQuestions; ++curr) {
748 if ( (curr == eAuthType)
749 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
750 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
752 curr += 5; // skip LDAP questions if we're not authenticating against LDAP
755 if (curr == eSysAdminName) {
756 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) { // for native auth mode, fetch the admin's existing pw
757 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
761 extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
765 ++curr; // skip the password question for non-native auth modes
770 if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
777 // setup now must be run after Citadel Server is already running, so we don't need this anymore.
778 //if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
779 //display_error("%s\n", _("failed to create directories"));
782 activity = _("Reconfiguring Citadel server");
783 progress(activity, 0, 5);
784 sleep(1); // Let the message appear briefly
786 // Create the administrator account. It's ok if the command fails if this user already exists.
787 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
788 progress(activity, 1, 5);
789 snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
791 progress(activity, 2, 5);
794 progress(activity, 3, 5);
796 // Assign the desired password and access level to the administrator account.
797 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
798 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
800 progress(activity, 4, 5);
803 int admin_flags = extract_int(&buf[4], 2);
804 int admin_times_called = extract_int(&buf[4], 3);
805 int admin_msgs_posted = extract_int(&buf[4], 4);
806 snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
807 admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
813 progress(activity, 5, 5);
815 check_xinetd_entry(); // Check /etc/xinetd.d/telnet
818 activity = _("Restarting Citadel server to apply changes");
819 progress(activity, 0, 51);
823 long original_start_time = extract_long(&buf[4], 3);
825 progress(activity, 1, 51);
827 progress(activity, 2, 51);
830 display_error("%s\n", buf);
837 for (i=3; i<=6; ++i) { // wait for server to shut down
838 progress(activity, i, 51);
842 for (i=7; ((i<=48) && (serv_sock < 0)) ; ++i) { // wait for server to start up
843 progress(activity, i, 51);
844 serv_sock = uds_connectsock(file_citadel_admin_socket);
848 progress(activity, 49, 51);
851 progress(activity, 50, 51);
854 long new_start_time = extract_long(&buf[4], 3);
857 progress(activity, 51, 51);
859 if ((original_start_time == new_start_time) || (new_start_time <= 0)) {
860 display_error("%s\n", _("Setup failed to restart Citadel server. Please restart it manually."));