Removed the fixnss() step from setup. I don't think it's needed anymore. Moving...
[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         setup_titles[eSysAdminName] = _("Citadel administrator username:");
132         setup_text[eSysAdminName] = _(
133 "Please enter the name of the Citadel user account that should be granted "
134 "administrative privileges once created. If using internal authentication "
135 "this user account will be created if it does not exist. For external "
136 "authentication this user account has to exist.");
137
138         setup_titles[eSysAdminPW] = _("Administrator password:");
139         setup_text[eSysAdminPW] = _(
140 "Enter a password for the system administrator. When setup\n"
141 "completes it will attempt to create the administrator user\n"
142 "and set the password specified here.\n");
143
144         setup_titles[eUID] = _("Citadel User ID:");
145         setup_text[eUID] = _(
146 "Citadel needs to run under its own user ID.  This would\n"
147 "typically be called \"citadel\", but if you are running Citadel\n"
148 "as a public site, you might also call it \"bbs\" or \"guest\".\n"
149 "The server will run under this user ID.  Please specify that\n"
150 "user ID here.  You may specify either a user name or a numeric\n"
151 "UID.\n");
152
153         setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:");
154         setup_text[eIP_ADDR] = _(
155 "Please specify the IP address which the server should be listening to. "
156 "You can name a specific IPv4 or IPv6 address, or you can specify\n"
157 "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n"
158 "for \"any IPv4 address\". If you leave this blank, Citadel will\n"
159 "listen on all addresses. "
160 "This can usually be left to the default unless multiple instances of Citadel "
161 "are running on the same computer.");
162
163         setup_titles[eCTDL_Port] = _("Server port number:");
164         setup_text[eCTDL_Port] = _(
165 "Specify the TCP port number on which your server will run.\n"
166 "Normally, this will be port 504, which is the official port\n"
167 "assigned by the IANA for Citadel servers.  You will only need\n"
168 "to specify a different port number if you run multiple instances\n"
169 "of Citadel on the same computer and there is something else\n"
170 "already using port 504.\n");
171
172         setup_titles[eAuthType] = _("Authentication method to use:");
173         setup_text[eAuthType] = _(
174 "Please choose the user authentication mode. By default Citadel will use its "
175 "own internal user accounts database. If you choose Host, Citadel users will "
176 "have accounts on the host system, authenticated via /etc/passwd or a PAM "
177 "source. LDAP chooses an RFC 2307 compliant directory server, the last option "
178 "chooses the nonstandard MS Active Directory LDAP scheme."
179 "\n"
180 "Do not change this option unless you are sure it is required, since changing "
181 "back requires a full reinstall of Citadel."
182 "\n"
183 " 0. Self contained authentication\n"
184 " 1. Host system integrated authentication\n"
185 " 2. External LDAP - RFC 2307 POSIX schema\n"
186 " 3. External LDAP - MS Active Directory schema\n"
187 "\n"
188 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
189 "\n"
190 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
191
192         setup_titles[eLDAP_Host] = _("LDAP host:");
193         setup_text[eLDAP_Host] = _(
194 "Please enter the host name or IP address of your LDAP server.\n");
195
196         setup_titles[eLDAP_Port] = _("LDAP port number:");
197         setup_text[eLDAP_Port] = _(
198 "Please enter the port number of the LDAP service (usually 389).\n");
199
200         setup_titles[eLDAP_Base_DN] = _("LDAP base DN:");
201         setup_text[eLDAP_Base_DN] = _(
202 "Please enter the Base DN to search for authentication\n"
203 "(for example: dc=example,dc=com)\n");
204
205         setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:");
206         setup_text[eLDAP_Bind_DN] = _(
207 "Please enter the DN of an account to use for binding to the LDAP server for "
208 "performing queries. The account does not require any other privileges. If "
209 "your LDAP server allows anonymous queries, you can leave this blank.\n");
210
211         setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:");
212         setup_text[eLDAP_Bind_PW] = _(
213 "If you entered a Bind DN in the previous question, you must now enter\n"
214 "the password associated with that account.  Otherwise, you can leave this\n"
215 "blank.\n");
216
217 #if 0
218 // Debug loading of locales... Strace does a better job though.
219         printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale"));
220         printf("Text domain: %s\n", textdomain("citadel-setup"));
221         printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8"));
222         {
223                 int i;
224                 for (i = 0; i < eMaxQuestions; i++)
225                         printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
226                 exit(0);
227         }
228 #endif
229 }
230
231
232 void title(const char *text) {
233         printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
234 }
235
236
237 int yesno(const char *question, int default_value) {
238         int answer = 0;
239         char buf[SIZ];
240
241         do {
242                 printf("%s\n%s [%s] --> ", question, _("Yes/No"), ( default_value ? _("Yes") : _("No") ));
243                 if (fgets(buf, sizeof buf, stdin)) {
244                         answer = tolower(buf[0]);
245                         if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
246                                 answer = default_value;
247                         }
248                         else if (answer == 'y') {
249                                 answer = 1;
250                         }
251                         else if (answer == 'n') {
252                                 answer = 0;
253                         }
254                 }
255         } while ((answer < 0) || (answer > 1));
256         return (answer);
257 }
258
259
260 void important_message(const char *title, const char *msgtext) {
261         char buf[SIZ];
262
263         printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
264         printf("       %s \n\n%s\n\n", title, msgtext);
265         printf("%s", _("Press return to continue..."));
266         if (fgets(buf, sizeof buf, stdin)) {
267                 ;
268         }
269 }
270
271
272 void important_msgnum(int msgnum) {
273         important_message(_("Important Message"), setup_text[msgnum]);
274 }
275
276
277 void display_error(char *error_message_format, ...) {
278         StrBuf *Msg;
279         va_list arg_ptr;
280
281         Msg = NewStrBuf();
282         va_start(arg_ptr, error_message_format);
283         StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
284         va_end(arg_ptr);
285
286         important_message(_("Error"), ChrPtr(Msg));
287         FreeStrBuf(&Msg);
288 }
289
290
291 void progress(char *text, long int curr, long int cmax) {
292         static long dots_printed = 0L;
293         long a = 0;
294
295         if (curr == 0) {
296                 printf("%s\n", text);
297                 printf("....................................................");
298                 printf("..........................\r");
299                 dots_printed = 0;
300         } else if (curr == cmax) {
301                 printf("\r%79s\n", "");
302         } else {
303                 a = (curr * 100) / cmax;
304                 a = a * 78;
305                 a = a / 100;
306                 while (dots_printed < a) {
307                         printf("*");
308                         ++dots_printed;
309                 }
310         }
311         fflush(stdout);
312 }
313
314
315 int uds_connectsock(char *sockpath) {
316         int s;
317         struct sockaddr_un addr;
318
319         memset(&addr, 0, sizeof(addr));
320         addr.sun_family = AF_UNIX;
321         strcpy(addr.sun_path, sockpath);
322
323         s = socket(AF_UNIX, SOCK_STREAM, 0);
324         if (s < 0) {
325                 return(-1);
326         }
327
328         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
329                 close(s);
330                 return(-1);
331         }
332
333         return s;
334 }
335
336
337 /*
338  * input binary data from socket
339  */
340 void serv_read(char *buf, int bytes) {
341         int len, rlen;
342
343         len = 0;
344         while (len < bytes) {
345                 rlen = read(serv_sock, &buf[len], bytes - len);
346                 if (rlen < 1) {
347                         return;
348                 }
349                 len = len + rlen;
350         }
351 }
352
353
354 /*
355  * send binary to server
356  */
357 void serv_write(char *buf, int nbytes) {
358         int bytes_written = 0;
359         int retval;
360         while (bytes_written < nbytes) {
361                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
362                 if (retval < 1) {
363                         return;
364                 }
365                 bytes_written = bytes_written + retval;
366         }
367 }
368
369
370 /*
371  * input string from socket - implemented in terms of serv_read()
372  */
373 void serv_gets(char *buf) {
374         int i;
375
376         /* Read one character at a time.
377          */
378         for (i = 0;; i++) {
379                 serv_read(&buf[i], 1);
380                 if (buf[i] == '\n' || i == (SIZ-1))
381                         break;
382         }
383
384         /* If we got a long line, discard characters until the newline.
385          */
386         if (i == (SIZ-1)) {
387                 while (buf[i] != '\n') {
388                         serv_read(&buf[i], 1);
389                 }
390         }
391
392         /* Strip all trailing nonprintables (crlf)
393          */
394         buf[i] = 0;
395 }
396
397
398 /*
399  * send line to server - implemented in terms of serv_write()
400  */
401 void serv_puts(char *buf) {
402         serv_write(buf, strlen(buf));
403         serv_write("\n", 1);
404 }
405
406
407 /*
408  * Convenience functions to get/set system configuration entries
409  */
410 void getconf_str(char *buf, char *key) {
411         char cmd[SIZ];
412         char ret[SIZ];
413
414         sprintf(cmd, "CONF GETVAL|%s", key);
415         serv_puts(cmd);
416         serv_gets(ret);
417         if (ret[0] == '2') {
418                 extract_token(buf, &ret[4], 0, '|', SIZ);
419         }
420         else {
421                 strcpy(buf, "");
422         }
423 }
424
425
426 int getconf_int(char *key) {
427         char buf[SIZ];
428         getconf_str(buf, key);
429         return atoi(buf);
430 }
431
432
433 void setconf_str(char *key, char *val) {
434         char buf[SIZ];
435
436         sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
437         serv_puts(buf);
438         serv_gets(buf);
439 }
440
441
442 void setconf_int(char *key, int val) {
443         char buf[SIZ];
444
445         sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
446         serv_puts(buf);
447         serv_gets(buf);
448 }
449
450
451 /*
452  * On systems which use xinetd, see if we can offer to install Citadel as
453  * the default telnet target.
454  */
455 void check_xinetd_entry(void)
456 {
457         char *filename = "/etc/xinetd.d/telnet";
458         FILE *fp;
459         char buf[SIZ];
460         int already_citadel = 0;
461         int rv;
462
463         fp = fopen(filename, "r+");
464         if (fp == NULL) return;         /* Not there.  Oh well... */
465
466         while (fgets(buf, sizeof buf, fp) != NULL) {
467                 if (strstr(buf, "/citadel") != NULL) {
468                         already_citadel = 1;
469                 }
470         }
471         fclose(fp);
472         if (already_citadel) return;    /* Already set up this way. */
473
474         /* Otherwise, prompt the user to create an entry. */
475         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
476                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
477                         return;
478                 }
479         }
480         else {
481                 snprintf(buf, sizeof buf,
482                          _("Setup can configure the \"xinetd\" service to automatically\n"
483                            "connect incoming telnet sessions to Citadel, bypassing the\n"
484                            "host system login: prompt.  Would you like to do this?\n"
485                          )
486                 );
487                 if (yesno(buf, 1) == 0) {
488                         return;
489                 }
490         }
491
492         fp = fopen(filename, "w");
493         fprintf(fp,
494                 "# description: telnet service for Citadel users\n"
495                 "service telnet\n"
496                 "{\n"
497                 "       disable = no\n"
498                 "       flags           = REUSE\n"
499                 "       socket_type     = stream\n"
500                 "       wait            = no\n"
501                 "       user            = root\n"
502                 "       server          = /usr/sbin/in.telnetd\n"
503                 "       server_args     = -h -L %s/citadel\n"
504                 "       log_on_failure  += USERID\n"
505                 "}\n",
506                 ctdl_bin_dir
507         );
508         fclose(fp);
509
510         /* Now try to restart the service.  This will not have the intended effect on Solaris, but who the hell uses Solaris anymore? */
511         rv = system("systemctl restart xinetd >/dev/null 2>&1");
512         if (rv != 0) {
513                 rv = system("service xinetd restart >/dev/null 2>&1");
514         }
515         if (rv != 0) {
516                 display_error(_("failed to restart xinetd.\n"));
517         }
518 }
519
520
521 void disable_other_mtas(void)
522 {
523         if ((getenv("ACT_AS_MTA") == NULL) || (getenv("ACT_AS_MTA") && strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
524                 /* Offer to disable other MTA's on the system. */
525                 /* FIXME this has to be rewritten to work in the new systemd-based world. */
526         }
527 }
528
529
530 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
531 {
532         char buf[SIZ] = "";
533         char setupmsg[SIZ];
534
535         strcpy(setupmsg, "");
536
537         title(prompt_title);
538         printf("\n%s\n", prompt_text);
539         printf("%s\n%s\n", _("This is currently set to:"), Target);
540         printf("%s\n", _("Enter new value or press return to leave unchanged:"));
541         if (fgets(buf, sizeof buf, stdin)) {
542                 buf[strlen(buf) - 1] = 0;
543         }
544         if (!IsEmptyStr(buf)) {
545                 strcpy(Target, buf);
546         }
547 }
548
549
550 void set_bool_val(int msgpos, int *ip, char *DefValue) {
551         title(setup_titles[msgpos]);
552         *ip = yesno(setup_text[msgpos], *ip);
553 }
554
555
556 void set_str_val(int msgpos, char *Target, char *DefValue) 
557 {
558         strprompt(setup_titles[msgpos], 
559                   setup_text[msgpos], 
560                   Target, 
561                   DefValue
562         );
563 }
564
565
566 /* like set_str_val() but for numeric values */
567 void set_int_val(int msgpos, int *target, char *default_value)
568 {
569         char buf[32];
570         sprintf(buf, "%d", *target);
571         do {
572                 set_str_val(msgpos, buf, default_value);
573         } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
574         *target = atoi(buf);
575 }
576
577
578 void edit_value(int curr)
579 {
580         struct passwd *pw = NULL;
581         char ctdluidname[256];
582         char buf[SIZ];
583         char *default_value = NULL;
584         int ctdluid = 0;
585         int portnum = 0;
586         int auth = 0;
587         int lportnum = 0;
588
589         if (default_value == NULL) {
590                 default_value = "";
591         }
592
593         switch (curr) {
594
595         case eSysAdminName:
596                 getconf_str(admin_name, "c_sysadm");
597                 set_str_val(curr, admin_name, default_value);
598                 setconf_str("c_sysadm", admin_name);
599                 break;
600
601         case eSysAdminPW:
602                 set_str_val(curr, admin_pass, default_value);
603                 break;
604         
605         case eUID:
606                 ctdluid = getconf_int("c_ctdluid");
607                 pw = getpwuid(ctdluid);
608                 if (pw == NULL) {
609                         set_int_val(curr, &ctdluid, default_value);
610                 }
611                 else {
612                         strcpy(ctdluidname, pw->pw_name);
613                         set_str_val(curr, ctdluidname, default_value);
614                         pw = getpwnam(ctdluidname);
615                         if (pw != NULL) {
616                                 ctdluid = pw->pw_uid;
617                         }
618                         else if (atoi(ctdluidname) > 0) {
619                                 ctdluid = atoi(ctdluidname);
620                         }
621                 }
622                 setconf_int("c_ctdluid", ctdluid);
623                 break;
624
625         case eIP_ADDR:
626                 getconf_str(buf, "c_ip_addr");
627                 set_str_val(curr, buf, default_value);
628                 setconf_str("c_ip_addr", buf);
629                 break;
630
631         case eCTDL_Port:
632                 portnum = getconf_int("c_port_number");
633                 set_int_val(curr, &portnum, default_value);
634                 setconf_int("c_port_number", portnum);
635                 break;
636
637         case eAuthType:
638                 auth = getconf_int("c_auth_mode");
639                 set_int_val(curr, &auth, default_value);
640                 setconf_int("c_auth_mode", auth);
641                 break;
642
643         case eLDAP_Host:
644                 getconf_str(buf, "c_ldap_host");
645                 if (IsEmptyStr(buf)) {
646                         strcpy(buf, "localhost");
647                 }
648                 set_str_val(curr, buf, default_value);
649                 setconf_str("c_ldap_host", buf);
650                 break;
651
652         case eLDAP_Port:
653                 lportnum = getconf_int("c_ldap_port");
654                 if (lportnum == 0) {
655                         lportnum = 389;
656                 }
657                 set_int_val(curr, &lportnum, default_value);
658                 setconf_int("c_ldap_port", lportnum);
659                 break;
660
661         case eLDAP_Base_DN:
662                 getconf_str(buf, "c_ldap_base_dn");
663                 set_str_val(curr, buf, default_value);
664                 setconf_str("c_ldap_base_dn", buf);
665                 break;
666
667         case eLDAP_Bind_DN:
668                 getconf_str(buf, "c_ldap_bind_dn");
669                 set_str_val(curr, buf, default_value);
670                 setconf_str("c_ldap_bind_dn", buf);
671                 break;
672
673         case eLDAP_Bind_PW:
674                 getconf_str(buf, "c_ldap_bind_pw");
675                 set_str_val(curr, buf, default_value);
676                 setconf_str("c_ldap_bind_pw", buf);
677                 break;
678         }
679 }
680
681
682 #if 0
683 /*
684  * Strip "db" entries out of /etc/nsswitch.conf
685  */
686 void fixnss(void) {
687         FILE *fp_read;
688         int fd_write;
689         char buf[256];
690         char buf_nc[256];
691         char question[512];
692         int i;
693         int file_changed = 0;
694         char new_filename[64];
695         int rv;
696
697         fp_read = fopen(NSSCONF, "r");
698         if (fp_read == NULL) {
699                 return;
700         }
701
702         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
703         fd_write = mkstemp(new_filename);
704         if (fd_write < 0) {
705                 fclose(fp_read);
706                 return;
707         }
708
709         while (fgets(buf, sizeof buf, fp_read) != NULL) {
710                 strcpy(buf_nc, buf);
711                 for (i=0; buf_nc[i]; ++i) {
712                         if (buf_nc[i] == '#') {
713                                 buf_nc[i] = 0;
714                                 break;
715                         }
716                 }
717                 for (i=0; i<strlen(buf_nc); ++i) {
718                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
719                                 if (i > 0) {
720                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
721                                                 file_changed = 1;
722                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
723                                                 strcpy(&buf[i], &buf[i+2]);
724                                                 if (buf[i]==32) {
725                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
726                                                         strcpy(&buf[i], &buf[i+1]);
727                                                 }
728                                         }
729                                 }
730                         }
731                 }
732                 long buflen = strlen(buf);
733                 if (write(fd_write, buf, buflen) != buflen) {
734                         fclose(fp_read);
735                         close(fd_write);
736                         unlink(new_filename);
737                         return;
738                 }
739         }
740
741         fclose(fp_read);
742         
743         if (!file_changed) {
744                 unlink(new_filename);
745                 return;
746         }
747
748         snprintf(question, sizeof question,
749                  _(
750                          "\n"
751                          "/etc/nsswitch.conf is configured to use the 'db' module for\n"
752                          "one or more services.  This is not necessary on most systems,\n"
753                          "and it is known to crash the Citadel server when delivering\n"
754                          "mail to the Internet.\n"
755                          "\n"
756                          "Do you want this module to be automatically disabled?\n"
757                          "\n"
758                          )
759         );
760
761         if (yesno(question, 1)) {
762                 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
763                 rv = system(buf);
764                 if (rv != 0) {
765                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
766                 }
767                 chmod(NSSCONF, 0644);
768         }
769         unlink(new_filename);
770 }
771 #endif
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();       (No longer needed)
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 }