Removed a lot of legacy cruft out of the setup utility. Eventually I want to remove...
[citadel.git] / citadel / utils / setup.c
1 /*
2  * Citadel setup utility
3  *
4  * Copyright (c) 1987-2019 by the citadel.org team
5  *
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.
8  *
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.
13  */
14
15 #define SHOW_ME_VAPPEND_PRINTF
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <stdio.h>
19 #include <string.h>
20 #include <ctype.h>
21 #include <fcntl.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <sys/wait.h>
25 #include <signal.h>
26 #include <netdb.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <pwd.h>
30 #include <time.h>
31 #include <sys/socket.h>
32 #include <sys/un.h>
33 #include <assert.h>
34 #include <libcitadel.h>
35 #include "citadel.h"
36 #include "axdefs.h"
37 #include "sysdep.h"
38 #include "citadel_dirs.h"
39
40 #ifdef ENABLE_NLS
41 #ifdef HAVE_XLOCALE_H
42 #include <xlocale.h>
43 #endif
44 #include <libintl.h>
45 #include <locale.h>
46 #define _(string)       gettext(string)
47 #else
48 #define _(string)       (string)
49 #endif
50
51 #define SERVICE_NAME    "citadel"
52 #define PROTO_NAME      "tcp"
53 #define NSSCONF         "/etc/nsswitch.conf"
54
55 typedef enum _SetupStep {
56         eCitadelHomeDir = 0,
57         eSysAdminName = 1,
58         eSysAdminPW = 2,
59         eUID = 3,
60         eIP_ADDR = 4,
61         eCTDL_Port = 5,
62         eAuthType = 6,
63         eLDAP_Host = 7,
64         eLDAP_Port = 8,
65         eLDAP_Base_DN = 9,
66         eLDAP_Bind_DN = 10,
67         eLDAP_Bind_PW = 11,
68         eMaxQuestions = 12
69 } eSetupStep;
70
71 ///"CREATE_XINETD_ENTRY";
72 /* Environment variables, don't translate! */
73 const char *EnvNames [eMaxQuestions] = {
74         "HOME_DIRECTORY",
75         "SYSADMIN_NAME",
76         "SYSADMIN_PW",
77         "CITADEL_UID",
78         "IP_ADDR",
79         "CITADEL_PORT",
80         "ENABLE_UNIX_AUTH",
81         "LDAP_HOST",
82         "LDAP_PORT",
83         "LDAP_BASE_DN",
84         "LDAP_BIND_DN",
85         "LDAP_BIND_PW"
86 };
87
88 int setup_type = (-1);
89 int enable_home = 1;
90 char admin_name[SIZ];
91 char admin_pass[SIZ];
92 char admin_cmd[SIZ];
93 int serv_sock = (-1) ;
94
95 const char *setup_titles[eMaxQuestions];
96 const char *setup_text[eMaxQuestions];
97
98 char *program_title;
99
100 void SetTitles(void)
101 {
102         int have_run_dir;
103 #ifndef HAVE_RUN_DIR
104         have_run_dir = 1;
105 #else
106         have_run_dir = 0;
107 #endif
108
109 #ifdef ENABLE_NLS
110         setlocale(LC_MESSAGES, getenv("LANG"));
111         bindtextdomain("citadel-setup", LOCALEDIR"/locale");
112         textdomain("citadel-setup");
113         bind_textdomain_codeset("citadel-setup","UTF8");
114 #endif
115
116         setup_titles[eCitadelHomeDir] = _("Citadel Home Directory");
117         if (have_run_dir)
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");
123         else
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 /");
130
131
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.");
138
139
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");
145
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"
153 "UID.\n");
154
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.");
164
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");
173
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."
181 "\n"
182 "Do not change this option unless you are sure it is required, since changing "
183 "back requires a full reinstall of Citadel."
184 "\n"
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"
189 "\n"
190 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
191 "\n"
192 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
193
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");
197
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");
201
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");
206
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");
212
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"
217 "blank.\n");
218
219 #if 0
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"));
224         {
225                 int i;
226                 for (i = 0; i < eMaxQuestions; i++)
227                         printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
228                 exit(0);
229         }
230 #endif
231 }
232
233
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);
236 }
237
238
239 int yesno(const char *question, int default_value) {
240         int answer = 0;
241         char buf[SIZ];
242
243         do {
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;
249                         }
250                         else if (answer == 'y') {
251                                 answer = 1;
252                         }
253                         else if (answer == 'n') {
254                                 answer = 0;
255                         }
256                 }
257         } while ((answer < 0) || (answer > 1));
258         return (answer);
259 }
260
261
262 void important_message(const char *title, const char *msgtext) {
263         char buf[SIZ];
264
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)) {
269                 ;
270         }
271 }
272
273
274 void important_msgnum(int msgnum) {
275         important_message(_("Important Message"), setup_text[msgnum]);
276 }
277
278
279 void display_error(char *error_message_format, ...) {
280         StrBuf *Msg;
281         va_list arg_ptr;
282
283         Msg = NewStrBuf();
284         va_start(arg_ptr, error_message_format);
285         StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
286         va_end(arg_ptr);
287
288         important_message(_("Error"), ChrPtr(Msg));
289         FreeStrBuf(&Msg);
290 }
291
292
293 void progress(char *text, long int curr, long int cmax) {
294         static long dots_printed = 0L;
295         long a = 0;
296
297         if (curr == 0) {
298                 printf("%s\n", text);
299                 printf("....................................................");
300                 printf("..........................\r");
301                 dots_printed = 0;
302         } else if (curr == cmax) {
303                 printf("\r%79s\n", "");
304         } else {
305                 a = (curr * 100) / cmax;
306                 a = a * 78;
307                 a = a / 100;
308                 while (dots_printed < a) {
309                         printf("*");
310                         ++dots_printed;
311                 }
312         }
313         fflush(stdout);
314 }
315
316
317 int uds_connectsock(char *sockpath) {
318         int s;
319         struct sockaddr_un addr;
320
321         memset(&addr, 0, sizeof(addr));
322         addr.sun_family = AF_UNIX;
323         strcpy(addr.sun_path, sockpath);
324
325         s = socket(AF_UNIX, SOCK_STREAM, 0);
326         if (s < 0) {
327                 return(-1);
328         }
329
330         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
331                 close(s);
332                 return(-1);
333         }
334
335         return s;
336 }
337
338
339 /*
340  * input binary data from socket
341  */
342 void serv_read(char *buf, int bytes) {
343         int len, rlen;
344
345         len = 0;
346         while (len < bytes) {
347                 rlen = read(serv_sock, &buf[len], bytes - len);
348                 if (rlen < 1) {
349                         return;
350                 }
351                 len = len + rlen;
352         }
353 }
354
355
356 /*
357  * send binary to server
358  */
359 void serv_write(char *buf, int nbytes) {
360         int bytes_written = 0;
361         int retval;
362         while (bytes_written < nbytes) {
363                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
364                 if (retval < 1) {
365                         return;
366                 }
367                 bytes_written = bytes_written + retval;
368         }
369 }
370
371
372 /*
373  * input string from socket - implemented in terms of serv_read()
374  */
375 void serv_gets(char *buf) {
376         int i;
377
378         /* Read one character at a time.
379          */
380         for (i = 0;; i++) {
381                 serv_read(&buf[i], 1);
382                 if (buf[i] == '\n' || i == (SIZ-1))
383                         break;
384         }
385
386         /* If we got a long line, discard characters until the newline.
387          */
388         if (i == (SIZ-1)) {
389                 while (buf[i] != '\n') {
390                         serv_read(&buf[i], 1);
391                 }
392         }
393
394         /* Strip all trailing nonprintables (crlf)
395          */
396         buf[i] = 0;
397 }
398
399
400 /*
401  * send line to server - implemented in terms of serv_write()
402  */
403 void serv_puts(char *buf) {
404         serv_write(buf, strlen(buf));
405         serv_write("\n", 1);
406 }
407
408
409 /*
410  * Convenience functions to get/set system configuration entries
411  */
412 void getconf_str(char *buf, char *key) {
413         char cmd[SIZ];
414         char ret[SIZ];
415
416         sprintf(cmd, "CONF GETVAL|%s", key);
417         serv_puts(cmd);
418         serv_gets(ret);
419         if (ret[0] == '2') {
420                 extract_token(buf, &ret[4], 0, '|', SIZ);
421         }
422         else {
423                 strcpy(buf, "");
424         }
425 }
426
427
428 int getconf_int(char *key) {
429         char buf[SIZ];
430         getconf_str(buf, key);
431         return atoi(buf);
432 }
433
434
435 void setconf_str(char *key, char *val) {
436         char buf[SIZ];
437
438         sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
439         serv_puts(buf);
440         serv_gets(buf);
441 }
442
443
444 void setconf_int(char *key, int val) {
445         char buf[SIZ];
446
447         sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
448         serv_puts(buf);
449         serv_gets(buf);
450 }
451
452
453 /*
454  * On systems which use xinetd, see if we can offer to install Citadel as
455  * the default telnet target.
456  */
457 void check_xinetd_entry(void)
458 {
459         char *filename = "/etc/xinetd.d/telnet";
460         FILE *fp;
461         char buf[SIZ];
462         int already_citadel = 0;
463         int rv;
464
465         fp = fopen(filename, "r+");
466         if (fp == NULL) return;         /* Not there.  Oh well... */
467
468         while (fgets(buf, sizeof buf, fp) != NULL) {
469                 if (strstr(buf, "/citadel") != NULL) {
470                         already_citadel = 1;
471                 }
472         }
473         fclose(fp);
474         if (already_citadel) return;    /* Already set up this way. */
475
476         /* Otherwise, prompt the user to create an entry. */
477         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
478                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
479                         return;
480                 }
481         }
482         else {
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"
487                          )
488                 );
489                 if (yesno(buf, 1) == 0) {
490                         return;
491                 }
492         }
493
494         fp = fopen(filename, "w");
495         fprintf(fp,
496                 "# description: telnet service for Citadel users\n"
497                 "service telnet\n"
498                 "{\n"
499                 "       disable = no\n"
500                 "       flags           = REUSE\n"
501                 "       socket_type     = stream\n"
502                 "       wait            = no\n"
503                 "       user            = root\n"
504                 "       server          = /usr/sbin/in.telnetd\n"
505                 "       server_args     = -h -L %s/citadel\n"
506                 "       log_on_failure  += USERID\n"
507                 "}\n",
508                 ctdl_bin_dir
509         );
510         fclose(fp);
511
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");
514         if (rv != 0) {
515                 rv = system("service xinetd restart >/dev/null 2>&1");
516         }
517         if (rv != 0) {
518                 display_error(_("failed to restart xinetd.\n"));
519         }
520 }
521
522
523 void disable_other_mtas(void)
524 {
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. */
528         }
529 }
530
531
532 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
533 {
534         char buf[SIZ] = "";
535         char setupmsg[SIZ];
536
537         strcpy(setupmsg, "");
538
539         title(prompt_title);
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;
545         }
546         if (!IsEmptyStr(buf)) {
547                 strcpy(Target, buf);
548         }
549 }
550
551
552 void set_bool_val(int msgpos, int *ip, char *DefValue) {
553         title(setup_titles[msgpos]);
554         *ip = yesno(setup_text[msgpos], *ip);
555 }
556
557
558 void set_str_val(int msgpos, char *Target, char *DefValue) 
559 {
560         strprompt(setup_titles[msgpos], 
561                   setup_text[msgpos], 
562                   Target, 
563                   DefValue
564         );
565 }
566
567
568 /* like set_str_val() but for numeric values */
569 void set_int_val(int msgpos, int *target, char *default_value)
570 {
571         char buf[32];
572         sprintf(buf, "%d", *target);
573         do {
574                 set_str_val(msgpos, buf, default_value);
575         } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
576         *target = atoi(buf);
577 }
578
579
580 void edit_value(int curr)
581 {
582         struct passwd *pw = NULL;
583         char ctdluidname[256];
584         char buf[SIZ];
585         char *default_value = NULL;
586         int ctdluid = 0;
587         int portnum = 0;
588         int auth = 0;
589         int lportnum = 0;
590
591         if (default_value == NULL) {
592                 default_value = "";
593         }
594
595         switch (curr) {
596
597         case eSysAdminName:
598                 getconf_str(admin_name, "c_sysadm");
599                 set_str_val(curr, admin_name, default_value);
600                 setconf_str("c_sysadm", admin_name);
601                 break;
602
603         case eSysAdminPW:
604                 set_str_val(curr, admin_pass, default_value);
605                 break;
606         
607         case eUID:
608                 ctdluid = getconf_int("c_ctdluid");
609                 pw = getpwuid(ctdluid);
610                 if (pw == NULL) {
611                         set_int_val(curr, &ctdluid, default_value);
612                 }
613                 else {
614                         strcpy(ctdluidname, pw->pw_name);
615                         set_str_val(curr, ctdluidname, default_value);
616                         pw = getpwnam(ctdluidname);
617                         if (pw != NULL) {
618                                 ctdluid = pw->pw_uid;
619                         }
620                         else if (atoi(ctdluidname) > 0) {
621                                 ctdluid = atoi(ctdluidname);
622                         }
623                 }
624                 setconf_int("c_ctdluid", ctdluid);
625                 break;
626
627         case eIP_ADDR:
628                 getconf_str(buf, "c_ip_addr");
629                 set_str_val(curr, buf, default_value);
630                 setconf_str("c_ip_addr", buf);
631                 break;
632
633         case eCTDL_Port:
634                 portnum = getconf_int("c_port_number");
635                 set_int_val(curr, &portnum, default_value);
636                 setconf_int("c_port_number", portnum);
637                 break;
638
639         case eAuthType:
640                 auth = getconf_int("c_auth_mode");
641                 set_int_val(curr, &auth, default_value);
642                 setconf_int("c_auth_mode", auth);
643                 break;
644
645         case eLDAP_Host:
646                 getconf_str(buf, "c_ldap_host");
647                 if (IsEmptyStr(buf)) {
648                         strcpy(buf, "localhost");
649                 }
650                 set_str_val(curr, buf, default_value);
651                 setconf_str("c_ldap_host", buf);
652                 break;
653
654         case eLDAP_Port:
655                 lportnum = getconf_int("c_ldap_port");
656                 if (lportnum == 0) {
657                         lportnum = 389;
658                 }
659                 set_int_val(curr, &lportnum, default_value);
660                 setconf_int("c_ldap_port", lportnum);
661                 break;
662
663         case eLDAP_Base_DN:
664                 getconf_str(buf, "c_ldap_base_dn");
665                 set_str_val(curr, buf, default_value);
666                 setconf_str("c_ldap_base_dn", buf);
667                 break;
668
669         case eLDAP_Bind_DN:
670                 getconf_str(buf, "c_ldap_bind_dn");
671                 set_str_val(curr, buf, default_value);
672                 setconf_str("c_ldap_bind_dn", buf);
673                 break;
674
675         case eLDAP_Bind_PW:
676                 getconf_str(buf, "c_ldap_bind_pw");
677                 set_str_val(curr, buf, default_value);
678                 setconf_str("c_ldap_bind_pw", buf);
679                 break;
680         }
681 }
682
683
684 /*
685  * Strip "db" entries out of /etc/nsswitch.conf
686  */
687 void fixnss(void) {
688         FILE *fp_read;
689         int fd_write;
690         char buf[256];
691         char buf_nc[256];
692         char question[512];
693         int i;
694         int file_changed = 0;
695         char new_filename[64];
696         int rv;
697
698         fp_read = fopen(NSSCONF, "r");
699         if (fp_read == NULL) {
700                 return;
701         }
702
703         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
704         fd_write = mkstemp(new_filename);
705         if (fd_write < 0) {
706                 fclose(fp_read);
707                 return;
708         }
709
710         while (fgets(buf, sizeof buf, fp_read) != NULL) {
711                 strcpy(buf_nc, buf);
712                 for (i=0; buf_nc[i]; ++i) {
713                         if (buf_nc[i] == '#') {
714                                 buf_nc[i] = 0;
715                                 break;
716                         }
717                 }
718                 for (i=0; i<strlen(buf_nc); ++i) {
719                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
720                                 if (i > 0) {
721                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
722                                                 file_changed = 1;
723                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
724                                                 strcpy(&buf[i], &buf[i+2]);
725                                                 if (buf[i]==32) {
726                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
727                                                         strcpy(&buf[i], &buf[i+1]);
728                                                 }
729                                         }
730                                 }
731                         }
732                 }
733                 long buflen = strlen(buf);
734                 if (write(fd_write, buf, buflen) != buflen) {
735                         fclose(fp_read);
736                         close(fd_write);
737                         unlink(new_filename);
738                         return;
739                 }
740         }
741
742         fclose(fp_read);
743         
744         if (!file_changed) {
745                 unlink(new_filename);
746                 return;
747         }
748
749         snprintf(question, sizeof question,
750                  _(
751                          "\n"
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"
756                          "\n"
757                          "Do you want this module to be automatically disabled?\n"
758                          "\n"
759                          )
760         );
761
762         if (yesno(question, 1)) {
763                 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
764                 rv = system(buf);
765                 if (rv != 0) {
766                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
767                 }
768                 chmod(NSSCONF, 0644);
769         }
770         unlink(new_filename);
771 }
772
773
774 /*
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.
777  */
778 #if 0
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"
783 "to log in.\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."));
789 #endif
790
791
792 int main(int argc, char *argv[])
793 {
794         int a, i;
795         int curr;
796         char buf[1024]; 
797         char aaa[128];
798         int relh = 0;
799         int home = 0;
800         char relhome[PATH_MAX]="";
801         char ctdldir[PATH_MAX]=CTDLDIR;
802         struct passwd *pw;
803         gid_t gid;
804         char *activity = NULL;
805         
806         /* Keep a mild groove on */
807         program_title = _("Citadel setup program");
808
809         /* set an invalid setup type */
810         setup_type = (-1);
811
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);
818                 }
819                 else if (!strncmp(argv[a], "-h", 2)) {
820                         relh=argv[a][2]!='/';
821                         if (!relh) {
822                                 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
823                         } else {
824                                 safestrncpy(relhome, &argv[a][2], sizeof relhome);
825                         }
826                         home = 1;
827                 }
828         }
829
830         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
831         SetTitles();
832
833         enable_home = ( relh | home );
834
835         if (chdir(ctdl_run_dir) != 0) {
836                 display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir);
837                 exit(errno);
838         }
839
840
841         /*
842          * Connect to the running Citadel server.
843          */
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);
848                 sleep(1);
849         }
850         progress(connectingmsg, 30, 30);
851
852         if (serv_sock < 0) { 
853                 display_error(
854                         "%s: %s %s\n", 
855                         _("Setup could not connect to a running Citadel server."),
856                         strerror(errno), file_citadel_admin_socket
857                 );
858                 exit(1);
859         }
860
861         /*
862          * read the server greeting
863          */
864         serv_gets(buf);
865         if (buf[0] != '2') {
866                 display_error("%s\n", buf);
867                 exit(2);
868         }
869
870         /*
871          * Are we connected to the correct Citadel server?
872          */
873         serv_puts("INFO");
874         serv_gets(buf);
875         if (buf[0] != '1') {
876                 display_error("%s\n", buf);
877                 exit(3);
878         }
879         a = 0;
880         while (serv_gets(buf), strcmp(buf, "000")) {
881                 if (a == 5) {
882                         if (atoi(buf) != REV_LEVEL) {
883                                 display_error("%s\n",
884                                 _("Your setup program and Citadel server are from different versions.")
885                                 );
886                                 exit(4);
887                         }
888                 }
889                 ++a;
890         }
891
892         printf("\n\n\n         *** %s ***\n\n", program_title);
893
894         /* Go through a series of dialogs prompting for config info */
895         for (curr = 1; curr < eMaxQuestions; ++curr) {
896                 edit_value(curr);
897
898                 if (    (curr == eAuthType)
899                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
900                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
901                 ) {
902                         curr += 5;      /* skip LDAP questions if we're not authenticating against LDAP */
903                 }
904
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);
909                                 serv_puts(buf);
910                                 serv_gets(buf);
911                                 if (buf[0] == '2') {
912                                         extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
913                                 }
914                         }
915                         else {
916                                 ++curr;         /* skip the password question for non-native auth modes */
917                         }
918                 }
919         }
920
921         if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
922                 gid = getgid();
923         } else {
924                 gid = pw->pw_gid;
925         }
926
927         if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
928                 display_error("%s\n", _("failed to create directories"));
929         }
930                 
931         activity = _("Reconfiguring Citadel server");
932         progress(activity, 0, 5);
933         sleep(1);                                       /* Let the message appear briefly */
934
935         /*
936          * Create the administrator account.  It's ok if the command fails if this user already exists.
937          */
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);
941                 serv_puts(buf);
942                 progress(activity, 2, 5);
943                 serv_gets(buf);
944         }
945         progress(activity, 3, 5);
946
947         /*
948          * Assign the desired password and access level to the administrator account.
949          */
950         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
951                 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
952                 serv_puts(buf);
953                 progress(activity, 4, 5);
954                 serv_gets(buf);
955                 if (buf[0] == '2') {
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
961                         );
962                         serv_puts(buf);
963                         serv_gets(buf);
964                 }
965         }
966         progress(activity, 5, 5);
967
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 */
971
972         /*
973          * Restart citserver
974          */
975         activity = _("Restarting Citadel server to apply changes");
976         progress(activity, 0, 51);
977
978         serv_puts("TIME");
979         serv_gets(buf);
980         long original_start_time = extract_long(&buf[4], 3);
981
982         progress(activity, 1, 51);
983         serv_puts("DOWN 1");
984         progress(activity, 2, 51);
985         serv_gets(buf);
986         if (buf[0] != '2') {
987                 display_error("%s\n", buf);
988                 exit(6);
989         }
990
991         close(serv_sock);
992         serv_sock = (-1);
993
994         for (i=3; i<=6; ++i) {                                  /* wait for server to shut down */
995                 progress(activity, i, 51);
996                 sleep(1);
997         }
998
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);
1002                 sleep(1);
1003         }
1004
1005         progress(activity, 49, 51);
1006         serv_gets(buf);
1007
1008         progress(activity, 50, 51);
1009         serv_puts("TIME");
1010         serv_gets(buf);
1011         long new_start_time = extract_long(&buf[4], 3);
1012
1013         close(serv_sock);
1014         progress(activity, 51, 51);
1015
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."));
1018                 exit(7);
1019         }
1020
1021         exit(0);
1022         return 0;
1023 }