1 // Citadel setup utility
3 // Copyright (c) 1987-2022 by the citadel.org team
5 // This program is open source software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License version 3.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 #define SHOW_ME_VAPPEND_PRINTF
20 #include <sys/types.h>
29 #include <sys/socket.h>
32 #include <libcitadel.h>
33 #include "../server/citadel.h"
35 #include "../server/sysdep.h"
36 #include "../server/citadel_dirs.h"
44 #define _(string) gettext(string)
46 #define _(string) (string)
49 #define SERVICE_NAME "citadel"
50 #define PROTO_NAME "tcp"
51 #define NSSCONF "/etc/nsswitch.conf"
53 typedef enum _SetupStep {
69 ///"CREATE_XINETD_ENTRY";
70 /* Environment variables, don't translate! */
71 const char *EnvNames [eMaxQuestions] = {
86 int setup_type = (-1);
91 int serv_sock = (-1) ;
93 const char *setup_titles[eMaxQuestions];
94 const char *setup_text[eMaxQuestions];
98 void SetTitles(void) {
107 setlocale(LC_MESSAGES, getenv("LANG"));
108 bindtextdomain("citadel-setup", LOCALEDIR"/locale");
109 textdomain("citadel-setup");
110 bind_textdomain_codeset("citadel-setup","UTF8");
113 setup_titles[eCitadelHomeDir] = _("Citadel Home Directory");
115 setup_text[eCitadelHomeDir] = _(
116 "Enter the full pathname of the directory in which the Citadel\n"
117 "installation you are creating or updating resides. If you\n"
118 "specify a directory other than the default, you will need to\n"
119 "specify the -h flag to the server when you start it up.\n");
121 setup_text[eCitadelHomeDir] = _(
122 "Enter the subdirectory name for an alternate installation of "
123 "Citadel. To do a default installation just leave it blank."
124 "If you specify a directory other than the default, you will need to\n"
125 "specify the -h flag to the server when you start it up.\n"
126 "note that it may not have a leading /");
128 setup_titles[eSysAdminName] = _("Citadel administrator username:");
129 setup_text[eSysAdminName] = _(
130 "Please enter the name of the Citadel user account that should be granted "
131 "administrative privileges once created. If using internal authentication "
132 "this user account will be created if it does not exist. For external "
133 "authentication this user account has to exist.");
135 setup_titles[eSysAdminPW] = _("Administrator password:");
136 setup_text[eSysAdminPW] = _(
137 "Enter a password for the system administrator. When setup\n"
138 "completes it will attempt to create the administrator user\n"
139 "and set the password specified here.\n");
141 setup_titles[eUID] = _("Citadel User ID:");
142 setup_text[eUID] = _(
143 "Citadel needs to run under its own user ID. This would\n"
144 "typically be called \"citadel\", but if you are running Citadel\n"
145 "as a public site, you might also call it \"bbs\" or \"guest\".\n"
146 "The server will run under this user ID. Please specify that\n"
147 "user ID here. You may specify either a user name or a numeric\n"
150 setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:");
151 setup_text[eIP_ADDR] = _(
152 "Please specify the IP address which the server should be listening to. "
153 "You can name a specific IPv4 or IPv6 address, or you can specify\n"
154 "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n"
155 "for \"any IPv4 address\". If you leave this blank, Citadel will\n"
156 "listen on all addresses. "
157 "This can usually be left to the default unless multiple instances of Citadel "
158 "are running on the same computer.");
160 setup_titles[eCTDL_Port] = _("Server port number:");
161 setup_text[eCTDL_Port] = _(
162 "Specify the TCP port number on which your server will run.\n"
163 "Normally, this will be port 504, which is the official port\n"
164 "assigned by the IANA for Citadel servers. You will only need\n"
165 "to specify a different port number if you run multiple instances\n"
166 "of Citadel on the same computer and there is something else\n"
167 "already using port 504.\n");
169 setup_titles[eAuthType] = _("Authentication method to use:");
170 setup_text[eAuthType] = _(
171 "Please choose the user authentication mode. By default Citadel will use its "
172 "own internal user accounts database. If you choose Host, Citadel users will "
173 "have accounts on the host system, authenticated via /etc/passwd or a PAM "
174 "source. LDAP chooses an RFC 2307 compliant directory server, the last option "
175 "chooses the nonstandard MS Active Directory LDAP scheme."
177 "Do not change this option unless you are sure it is required, since changing "
178 "back requires a full reinstall of Citadel."
180 " 0. Self contained authentication\n"
181 " 1. Host system integrated authentication\n"
182 " 2. External LDAP - RFC 2307 POSIX schema\n"
183 " 3. External LDAP - MS Active Directory schema\n"
185 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
187 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
189 setup_titles[eLDAP_Host] = _("LDAP host:");
190 setup_text[eLDAP_Host] = _(
191 "Please enter the host name or IP address of your LDAP server.\n");
193 setup_titles[eLDAP_Port] = _("LDAP port number:");
194 setup_text[eLDAP_Port] = _(
195 "Please enter the port number of the LDAP service (usually 389).\n");
197 setup_titles[eLDAP_Base_DN] = _("LDAP base DN:");
198 setup_text[eLDAP_Base_DN] = _(
199 "Please enter the Base DN to search for authentication\n"
200 "(for example: dc=example,dc=com)\n");
202 setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:");
203 setup_text[eLDAP_Bind_DN] = _(
204 "Please enter the DN of an account to use for binding to the LDAP server for "
205 "performing queries. The account does not require any other privileges. If "
206 "your LDAP server allows anonymous queries, you can leave this blank.\n");
208 setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:");
209 setup_text[eLDAP_Bind_PW] = _(
210 "If you entered a Bind DN in the previous question, you must now enter\n"
211 "the password associated with that account. Otherwise, you can leave this\n"
215 // Debug loading of locales... Strace does a better job though.
216 printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale"));
217 printf("Text domain: %s\n", textdomain("citadel-setup"));
218 printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8"));
221 for (i = 0; i < eMaxQuestions; i++)
222 printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
229 void title(const char *text) {
230 printf("\n\033[1m\033[32m<\033[33m%s\033[32m>\033[0m\n", text);
234 int yesno(const char *question, int default_value) {
239 printf("%s\n%s [%s] --> ", question, _("Yes/No"), ( default_value ? _("Yes") : _("No") ));
240 if (fgets(buf, sizeof buf, stdin)) {
241 answer = tolower(buf[0]);
242 if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
243 answer = default_value;
245 else if (answer == 'y') {
248 else if (answer == 'n') {
252 } while ((answer < 0) || (answer > 1));
257 void important_message(const char *title, const char *msgtext) {
260 printf("\n%s\n%s\n\n", title, msgtext);
261 printf("%s", _("Press return to continue..."));
262 if (fgets(buf, sizeof buf, stdin)) {
268 void important_msgnum(int msgnum) {
269 important_message(_("Important Message"), setup_text[msgnum]);
273 void display_error(char *error_message_format, ...) {
278 va_start(arg_ptr, error_message_format);
279 StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
282 important_message(_("Error"), ChrPtr(Msg));
287 void progress(char *text, long int curr, long int cmax) {
292 printf("%s\n", text);
293 printf("\033[1m\033[33m[\033[32m............................................................................\033[33m]\033[0m\r");
295 else if (curr == cmax) {
296 printf("\r%79s\n", "");
299 printf("\033[1m\033[33m[\033[32m");
300 a = (curr * 100) / cmax;
303 for (i=0; i<a; ++i) {
312 int uds_connectsock(char *sockpath) {
314 struct sockaddr_un addr;
316 memset(&addr, 0, sizeof(addr));
317 addr.sun_family = AF_UNIX;
318 strcpy(addr.sun_path, sockpath);
320 s = socket(AF_UNIX, SOCK_STREAM, 0);
325 if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
334 // input binary data from socket
335 void serv_read(char *buf, int bytes) {
339 while (len < bytes) {
340 rlen = read(serv_sock, &buf[len], bytes - len);
349 // send binary to server
350 void serv_write(char *buf, int nbytes) {
351 int bytes_written = 0;
353 while (bytes_written < nbytes) {
354 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
358 bytes_written = bytes_written + retval;
363 // input string from socket - implemented in terms of serv_read()
364 void serv_gets(char *buf) {
367 // Read one character at a time.
369 serv_read(&buf[i], 1);
370 if (buf[i] == '\n' || i == (SIZ-1))
374 // If we got a long line, discard characters until the newline.
376 while (buf[i] != '\n') {
377 serv_read(&buf[i], 1);
381 // Strip all trailing nonprintables (crlf)
386 // send line to server - implemented in terms of serv_write()
387 void serv_puts(char *buf) {
388 serv_write(buf, strlen(buf));
393 // Convenience functions to get/set system configuration entries
394 void getconf_str(char *buf, char *key) {
398 sprintf(cmd, "CONF GETVAL|%s", key);
402 extract_token(buf, &ret[4], 0, '|', SIZ);
410 int getconf_int(char *key) {
412 getconf_str(buf, key);
417 void setconf_str(char *key, char *val) {
420 sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
426 void setconf_int(char *key, int val) {
429 sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
435 // On systems which use xinetd, see if we can offer to install Citadel as
436 // the default telnet target.
437 void check_xinetd_entry(void) {
438 char *filename = "/etc/xinetd.d/telnet";
441 int already_citadel = 0;
444 fp = fopen(filename, "r+");
445 if (fp == NULL) return; // Not there. Oh well...
447 while (fgets(buf, sizeof buf, fp) != NULL) {
448 if (strstr(buf, "/citadel") != NULL) {
453 if (already_citadel) return; // Already set up this way.
455 // Otherwise, prompt the user to create an entry.
456 if (getenv("CREATE_XINETD_ENTRY") != NULL) {
457 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
462 snprintf(buf, sizeof buf,
463 _("Setup can configure the \"xinetd\" service to automatically\n"
464 "connect incoming telnet sessions to Citadel, bypassing the\n"
465 "host system login: prompt. Would you like to do this?\n"
468 if (yesno(buf, 1) == 0) {
473 fp = fopen(filename, "w");
475 "# description: telnet service for Citadel users\n"
480 " socket_type = stream\n"
483 " server = /usr/sbin/in.telnetd\n"
484 " server_args = -h -L %s/citadel\n"
485 " log_on_failure += USERID\n"
491 // Now try to restart the service. This will not have the intended effect on Solaris, but who uses Solaris anymore?
492 rv = system("systemctl restart xinetd >/dev/null 2>&1");
494 rv = system("service xinetd restart >/dev/null 2>&1");
497 display_error(_("failed to restart xinetd.\n"));
502 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue) {
506 strcpy(setupmsg, "");
509 printf("\n%s\n", prompt_text);
510 printf("%s\n%s\n", _("This is currently set to:"), Target);
511 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
512 if (fgets(buf, sizeof buf, stdin)) {
513 buf[strlen(buf) - 1] = 0;
515 if (!IsEmptyStr(buf)) {
521 void set_bool_val(int msgpos, int *ip, char *DefValue) {
522 title(setup_titles[msgpos]);
523 *ip = yesno(setup_text[msgpos], *ip);
527 void set_str_val(int msgpos, char *Target, char *DefValue)
529 strprompt(setup_titles[msgpos],
537 // like set_str_val() but for numeric values
538 void set_int_val(int msgpos, int *target, char *default_value) {
540 sprintf(buf, "%d", *target);
542 set_str_val(msgpos, buf, default_value);
543 } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
548 void edit_value(int curr) {
549 struct passwd *pw = NULL;
550 char ctdluidname[256];
552 char *default_value = NULL;
558 if (default_value == NULL) {
565 getconf_str(admin_name, "c_sysadm");
566 set_str_val(curr, admin_name, default_value);
567 setconf_str("c_sysadm", admin_name);
571 set_str_val(curr, admin_pass, default_value);
575 ctdluid = getconf_int("c_ctdluid");
576 pw = getpwuid(ctdluid);
578 set_int_val(curr, &ctdluid, default_value);
581 strcpy(ctdluidname, pw->pw_name);
582 set_str_val(curr, ctdluidname, default_value);
583 pw = getpwnam(ctdluidname);
585 ctdluid = pw->pw_uid;
587 else if (atoi(ctdluidname) > 0) {
588 ctdluid = atoi(ctdluidname);
591 setconf_int("c_ctdluid", ctdluid);
595 getconf_str(buf, "c_ip_addr");
596 set_str_val(curr, buf, default_value);
597 setconf_str("c_ip_addr", buf);
601 portnum = getconf_int("c_port_number");
602 set_int_val(curr, &portnum, default_value);
603 setconf_int("c_port_number", portnum);
607 auth = getconf_int("c_auth_mode");
608 set_int_val(curr, &auth, default_value);
609 setconf_int("c_auth_mode", auth);
613 getconf_str(buf, "c_ldap_host");
614 if (IsEmptyStr(buf)) {
615 strcpy(buf, "localhost");
617 set_str_val(curr, buf, default_value);
618 setconf_str("c_ldap_host", buf);
622 lportnum = getconf_int("c_ldap_port");
626 set_int_val(curr, &lportnum, default_value);
627 setconf_int("c_ldap_port", lportnum);
631 getconf_str(buf, "c_ldap_base_dn");
632 set_str_val(curr, buf, default_value);
633 setconf_str("c_ldap_base_dn", buf);
637 getconf_str(buf, "c_ldap_bind_dn");
638 set_str_val(curr, buf, default_value);
639 setconf_str("c_ldap_bind_dn", buf);
643 getconf_str(buf, "c_ldap_bind_pw");
644 set_str_val(curr, buf, default_value);
645 setconf_str("c_ldap_bind_pw", buf);
651 // Messages that are no longer in use.
652 // We keep them here so we don't lose the translations if we need them later.
654 important_message(_("Setup finished"),
655 _("Setup of the Citadel server is complete.\n"
656 "If you will be using WebCit, please run its\n"
657 "setup program now; otherwise, run './citadel'\n"
659 important_message(_("Setup failed"),
660 _("Setup is finished, but the Citadel server failed to start.\n"
661 "Go back and check your configuration.\n");
662 important_message(_("Setup finished"),
663 _("Setup is finished. You may now start the server."));
667 int main(int argc, char *argv[]) {
672 char ctdldir[PATH_MAX]=CTDLDIR;
675 char *activity = NULL;
677 // Keep a mild groove on
678 program_title = _("Citadel setup program");
680 // set an invalid setup type
683 // parse command line args
684 for (a = 0; a < argc; ++a) {
685 if (!strncmp(argv[a], "-u", 2)) {
686 strcpy(aaa, argv[a]);
687 strcpy(aaa, &aaa[2]);
688 setup_type = atoi(aaa);
690 else if (!strncmp(argv[a], "-h", 2)) {
691 safestrncpy(ctdldir, &argv[a][2], sizeof ctdldir);
695 if (chdir(ctdldir) != 0) {
696 fprintf(stderr, "sendcommand: %s: %s\n", ctdldir, strerror(errno));
702 // Connect to the running Citadel server.
703 char *connectingmsg = _("Connecting to Citadel server");
704 for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) { // wait for server to start up
705 progress(connectingmsg, i, 30);
706 serv_sock = uds_connectsock(file_citadel_admin_socket);
709 progress(connectingmsg, 30, 30);
714 _("Setup could not connect to a running Citadel server."),
715 strerror(errno), file_citadel_admin_socket
720 // read the server greeting
723 display_error("%s\n", buf);
727 // Are we connected to the correct Citadel server?
731 display_error("%s\n", buf);
735 while (serv_gets(buf), strcmp(buf, "000")) {
737 if (atoi(buf) != REV_LEVEL) {
738 display_error("%s\n",
739 _("Your setup program and Citadel server are from different versions.")
747 printf("\n\n\n *** %s ***\n\n", program_title);
749 // Go through a series of dialogs prompting for config info
750 for (curr = 1; curr < eMaxQuestions; ++curr) {
753 if ( (curr == eAuthType)
754 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
755 && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
757 curr += 5; // skip LDAP questions if we're not authenticating against LDAP
760 if (curr == eSysAdminName) {
761 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) { // for native auth mode, fetch the admin's existing pw
762 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
766 extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
770 ++curr; // skip the password question for non-native auth modes
775 if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
782 // setup now must be run after Citadel Server is already running, so we don't need this anymore.
783 //if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
784 //display_error("%s\n", _("failed to create directories"));
787 activity = _("Reconfiguring Citadel server");
788 progress(activity, 0, 5);
789 sleep(1); // Let the message appear briefly
791 // Create the administrator account. It's ok if the command fails if this user already exists.
792 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
793 progress(activity, 1, 5);
794 snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
796 progress(activity, 2, 5);
799 progress(activity, 3, 5);
801 // Assign the desired password and access level to the administrator account.
802 if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
803 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
805 progress(activity, 4, 5);
808 int admin_flags = extract_int(&buf[4], 2);
809 int admin_times_called = extract_int(&buf[4], 3);
810 int admin_msgs_posted = extract_int(&buf[4], 4);
811 snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
812 admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
818 progress(activity, 5, 5);
820 check_xinetd_entry(); // Check /etc/xinetd.d/telnet
823 activity = _("Restarting Citadel server to apply changes");
824 progress(activity, 0, 51);
828 long original_start_time = extract_long(&buf[4], 3);
830 progress(activity, 1, 51);
832 progress(activity, 2, 51);
835 display_error("%s\n", buf);
842 for (i=3; i<=6; ++i) { // wait for server to shut down
843 progress(activity, i, 51);
847 for (i=7; ((i<=48) && (serv_sock < 0)) ; ++i) { // wait for server to start up
848 progress(activity, i, 51);
849 serv_sock = uds_connectsock(file_citadel_admin_socket);
853 progress(activity, 49, 51);
856 progress(activity, 50, 51);
859 long new_start_time = extract_long(&buf[4], 3);
862 progress(activity, 51, 51);
864 if ((original_start_time == new_start_time) || (new_start_time <= 0)) {
865 display_error("%s\n", _("Setup failed to restart Citadel server. Please restart it manually."));