2508712a2ae0108121ec9c81ee8592109785379d
[citadel.git] / citadel / utils / setup.c
1 /*
2  * Citadel setup utility
3  *
4  * Copyright (c) 1987-2017 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 #if HAVE_BACKTRACE
40 #include <execinfo.h>
41 #endif
42
43 #ifdef ENABLE_NLS
44 #ifdef HAVE_XLOCALE_H
45 #include <xlocale.h>
46 #endif
47 #include <libintl.h>
48 #include <locale.h>
49 #define _(string)       gettext(string)
50 #else
51 #define _(string)       (string)
52 #endif
53
54 #define UI_TEXT         0       /* Default setup type -- text only */
55 #define UI_DIALOG       2       /* Use the 'whiptail' or 'dialog' program */
56 #define UI_SILENT       3       /* Silent running, for use in scripts */
57
58 #define SERVICE_NAME    "citadel"
59 #define PROTO_NAME      "tcp"
60 #define NSSCONF         "/etc/nsswitch.conf"
61
62 typedef enum _SetupStep {
63         eCitadelHomeDir = 0,
64         eSysAdminName = 1,
65         eSysAdminPW = 2,
66         eUID = 3,
67         eIP_ADDR = 4,
68         eCTDL_Port = 5,
69         eAuthType = 6,
70         eLDAP_Host = 7,
71         eLDAP_Port = 8,
72         eLDAP_Base_DN = 9,
73         eLDAP_Bind_DN = 10,
74         eLDAP_Bind_PW = 11,
75         eMaxQuestions = 12
76 } eSetupStep;
77
78 ///"CREATE_XINETD_ENTRY";
79 /* Environment variables, don't translate! */
80 const char *EnvNames [eMaxQuestions] = {
81         "HOME_DIRECTORY",
82         "SYSADMIN_NAME",
83         "SYSADMIN_PW",
84         "CITADEL_UID",
85         "IP_ADDR",
86         "CITADEL_PORT",
87         "ENABLE_UNIX_AUTH",
88         "LDAP_HOST",
89         "LDAP_PORT",
90         "LDAP_BASE_DN",
91         "LDAP_BIND_DN",
92         "LDAP_BIND_PW"
93 };
94
95 int setup_type = (-1);
96 int enable_home = 1;
97 char admin_name[SIZ];
98 char admin_pass[SIZ];
99 char admin_cmd[SIZ];
100 int serv_sock = (-1) ;
101
102 const char *setup_titles[eMaxQuestions];
103 const char *setup_text[eMaxQuestions];
104
105 char *program_title;
106
107 void SetTitles(void)
108 {
109         int have_run_dir;
110 #ifndef HAVE_RUN_DIR
111         have_run_dir = 1;
112 #else
113         have_run_dir = 0;
114 #endif
115
116 #ifdef ENABLE_NLS
117         setlocale(LC_MESSAGES, getenv("LANG"));
118
119         bindtextdomain("citadel-setup", LOCALEDIR"/locale");
120         textdomain("citadel-setup");
121         bind_textdomain_codeset("citadel-setup","UTF8");
122 #endif
123
124         setup_titles[eCitadelHomeDir] = _("Citadel Home Directory");
125         if (have_run_dir)
126                 setup_text[eCitadelHomeDir] = _(
127 "Enter the full pathname of the directory in which the Citadel\n"
128 "installation you are creating or updating resides.  If you\n"
129 "specify a directory other than the default, you will need to\n"
130 "specify the -h flag to the server when you start it up.\n");
131         else
132                 setup_text[eCitadelHomeDir] = _(
133 "Enter the subdirectory name for an alternate installation of "
134 "Citadel. To do a default installation just leave it blank."
135 "If you specify a directory other than the default, you will need to\n"
136 "specify the -h flag to the server when you start it up.\n"
137 "note that it may not have a leading /");
138
139
140         setup_titles[eSysAdminName] = _("Citadel administrator username:");
141         setup_text[eSysAdminName] = _(
142 "Please enter the name of the Citadel user account that should be granted "
143 "administrative privileges once created. If using internal authentication "
144 "this user account will be created if it does not exist. For external "
145 "authentication this user account has to exist.");
146
147
148         setup_titles[eSysAdminPW] = _("Administrator password:");
149         setup_text[eSysAdminPW] = _(
150 "Enter a password for the system administrator. When setup\n"
151 "completes it will attempt to create the administrator user\n"
152 "and set the password specified here.\n");
153
154         setup_titles[eUID] = _("Citadel User ID:");
155         setup_text[eUID] = _(
156 "Citadel needs to run under its own user ID.  This would\n"
157 "typically be called \"citadel\", but if you are running Citadel\n"
158 "as a public site, you might also call it \"bbs\" or \"guest\".\n"
159 "The server will run under this user ID.  Please specify that\n"
160 "user ID here.  You may specify either a user name or a numeric\n"
161 "UID.\n");
162
163         setup_titles[eIP_ADDR] = _("Listening address for the Citadel server:");
164         setup_text[eIP_ADDR] = _(
165 "Please specify the IP address which the server should be listening to. "
166 "You can name a specific IPv4 or IPv6 address, or you can specify\n"
167 "\"*\" for \"any address\", \"::\" for \"any IPv6 address\", or \"0.0.0.0\"\n"
168 "for \"any IPv4 address\". If you leave this blank, Citadel will\n"
169 "listen on all addresses. "
170 "This can usually be left to the default unless multiple instances of Citadel "
171 "are running on the same computer.");
172
173         setup_titles[eCTDL_Port] = _("Server port number:");
174         setup_text[eCTDL_Port] = _(
175 "Specify the TCP port number on which your server will run.\n"
176 "Normally, this will be port 504, which is the official port\n"
177 "assigned by the IANA for Citadel servers.  You will only need\n"
178 "to specify a different port number if you run multiple instances\n"
179 "of Citadel on the same computer and there is something else\n"
180 "already using port 504.\n");
181
182         setup_titles[eAuthType] = _("Authentication method to use:");
183         setup_text[eAuthType] = _(
184 "Please choose the user authentication mode. By default Citadel will use its "
185 "own internal user accounts database. If you choose Host, Citadel users will "
186 "have accounts on the host system, authenticated via /etc/passwd or a PAM "
187 "source. LDAP chooses an RFC 2307 compliant directory server, the last option "
188 "chooses the nonstandard MS Active Directory LDAP scheme."
189 "\n"
190 "Do not change this option unless you are sure it is required, since changing "
191 "back requires a full reinstall of Citadel."
192 "\n"
193 " 0. Self contained authentication\n"
194 " 1. Host system integrated authentication\n"
195 " 2. External LDAP - RFC 2307 POSIX schema\n"
196 " 3. External LDAP - MS Active Directory schema\n"
197 "\n"
198 "For help: http://www.citadel.org/doku.php/faq:installation:authmodes\n"
199 "\n"
200 "ANSWER \"0\" UNLESS YOU COMPLETELY UNDERSTAND THIS OPTION.\n");
201
202         setup_titles[eLDAP_Host] = _("LDAP host:");
203         setup_text[eLDAP_Host] = _(
204 "Please enter the host name or IP address of your LDAP server.\n");
205
206         setup_titles[eLDAP_Port] = _("LDAP port number:");
207         setup_text[eLDAP_Port] = _(
208 "Please enter the port number of the LDAP service (usually 389).\n");
209
210         setup_titles[eLDAP_Base_DN] = _("LDAP base DN:");
211         setup_text[eLDAP_Base_DN] = _(
212 "Please enter the Base DN to search for authentication\n"
213 "(for example: dc=example,dc=com)\n");
214
215         setup_titles[eLDAP_Bind_DN] = _("LDAP bind DN:");
216         setup_text[eLDAP_Bind_DN] = _(
217 "Please enter the DN of an account to use for binding to the LDAP server for "
218 "performing queries. The account does not require any other privileges. If "
219 "your LDAP server allows anonymous queries, you can leave this blank.\n");
220
221         setup_titles[eLDAP_Bind_PW] = _("LDAP bind password:");
222         setup_text[eLDAP_Bind_PW] = _(
223 "If you entered a Bind DN in the previous question, you must now enter\n"
224 "the password associated with that account.  Otherwise, you can leave this\n"
225 "blank.\n");
226
227 #if 0
228 // Debug loading of locales... Strace does a better job though.
229         printf("Message catalog directory: %s\n", bindtextdomain("citadel-setup", LOCALEDIR"/locale"));
230         printf("Text domain: %s\n", textdomain("citadel-setup"));
231         printf("Text domain Charset: %s\n", bind_textdomain_codeset("citadel-setup","UTF8"));
232         {
233                 int i;
234                 for (i = 0; i < eMaxQuestions; i++)
235                         printf("%s - %s\n", setup_titles[i], _(setup_titles[i]));
236                 exit(0);
237         }
238 #endif
239 }
240
241
242
243 void title(const char *text)
244 {
245         if (setup_type == UI_TEXT) {
246                 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
247         }
248 }
249
250
251
252 int yesno(const char *question, int default_value)
253 {
254         int i = 0;
255         int answer = 0;
256         char buf[SIZ];
257
258         switch (setup_type) {
259
260         case UI_TEXT:
261                 do {
262                         printf("%s\n%s [%s] --> ",
263                                question,
264                                _("Yes/No"),
265                                ( default_value ? _("Yes") : _("No") )
266                         );
267                         if (fgets(buf, sizeof buf, stdin))
268                         {
269                                 answer = tolower(buf[0]);
270                                 if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
271                                         answer = default_value;
272                                 }
273                                 else if (answer == 'y') {
274                                         answer = 1;
275                                 }
276                                 else if (answer == 'n') {
277                                         answer = 0;
278                                 }
279                         }
280                 } while ((answer < 0) || (answer > 1));
281                 break;
282
283         case UI_DIALOG:
284                 snprintf(buf, sizeof buf, "exec %s --backtitle '%s' %s --yesno '%s' 15 75",
285                         getenv("CTDL_DIALOG"),
286                         program_title,
287                         ( default_value ? "" : "--defaultno" ),
288                         question);
289                 i = system(buf);
290                 if (i == 0) {
291                         answer = 1;
292                 }
293                 else {
294                         answer = 0;
295                 }
296                 break;
297         case UI_SILENT:
298                 break;
299         }
300         return (answer);
301 }
302
303
304 void important_message(const char *title, const char *msgtext)
305 {
306         char buf[SIZ];
307
308         switch (setup_type) {
309
310         case UI_TEXT:
311                 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");
312                 printf("       %s \n\n%s\n\n", title, msgtext);
313                 printf("%s", _("Press return to continue..."));
314                 if (fgets(buf, sizeof buf, stdin))
315                 {;}
316                 break;
317
318         case UI_DIALOG:
319                 snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --msgbox '%s' 19 72",
320                         getenv("CTDL_DIALOG"),
321                         program_title,
322                         msgtext);
323                 int rv = system(buf);
324                 if (rv != 0) {
325                         fprintf(stderr, _("failed to run the dialog command\n"));
326                 }
327                 break;
328         case UI_SILENT:
329                 fprintf(stderr, "%s\n", msgtext);
330                 break;
331         }
332 }
333
334 void important_msgnum(int msgnum)
335 {
336         important_message(_("Important Message"), setup_text[msgnum]);
337 }
338
339 void display_error(char *error_message_format, ...)
340 {
341         StrBuf *Msg;
342         va_list arg_ptr;
343
344         Msg = NewStrBuf();
345         va_start(arg_ptr, error_message_format);
346         StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
347         va_end(arg_ptr);
348
349         important_message(_("Error"), ChrPtr(Msg));
350         FreeStrBuf(&Msg);
351 }
352
353 void progress(char *text, long int curr, long int cmax)
354 {
355         static long dots_printed = 0L;
356         long a = 0;
357         static FILE *fp = NULL;
358         char buf[SIZ];
359
360         switch (setup_type) {
361
362         case UI_TEXT:
363                 if (curr == 0) {
364                         printf("%s\n", text);
365                         printf("....................................................");
366                         printf("..........................\r");
367                         dots_printed = 0;
368                 } else if (curr == cmax) {
369                         printf("\r%79s\n", "");
370                 } else {
371                         a = (curr * 100) / cmax;
372                         a = a * 78;
373                         a = a / 100;
374                         while (dots_printed < a) {
375                                 printf("*");
376                                 ++dots_printed;
377                         }
378                 }
379                 fflush(stdout);
380                 break;
381
382         case UI_DIALOG:
383                 if (curr == 0) {
384                         snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --gauge '%s' 7 72 0",
385                                 getenv("CTDL_DIALOG"),
386                                 program_title,
387                                 text);
388                         fp = popen(buf, "w");
389                         if (fp != NULL) {
390                                 fprintf(fp, "0\n");
391                                 fflush(fp);
392                         }
393                 } 
394                 else if (curr == cmax) {
395                         if (fp != NULL) {
396                                 fprintf(fp, "100\n");
397                                 pclose(fp);
398                                 fp = NULL;
399                         }
400                 }
401                 else {
402                         a = (curr * 100) / cmax;
403                         if (fp != NULL) {
404                                 fprintf(fp, "%ld\n", a);
405                                 fflush(fp);
406                         }
407                 }
408                 break;
409         case UI_SILENT:
410                 break;
411
412         default:
413                 assert(1==0);   /* If we got here then the developer is a moron */
414         }
415 }
416
417
418 int uds_connectsock(char *sockpath)
419 {
420         int s;
421         struct sockaddr_un addr;
422
423         memset(&addr, 0, sizeof(addr));
424         addr.sun_family = AF_UNIX;
425         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
426
427         s = socket(AF_UNIX, SOCK_STREAM, 0);
428         if (s < 0) {
429                 return(-1);
430         }
431
432         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
433                 close(s);
434                 return(-1);
435         }
436
437         return s;
438 }
439
440
441 /*
442  * input binary data from socket
443  */
444 void serv_read(char *buf, int bytes)
445 {
446         int len, rlen;
447
448         len = 0;
449         while (len < bytes) {
450                 rlen = read(serv_sock, &buf[len], bytes - len);
451                 if (rlen < 1) {
452                         return;
453                 }
454                 len = len + rlen;
455         }
456 }
457
458
459 /*
460  * send binary to server
461  */
462 void serv_write(char *buf, int nbytes)
463 {
464         int bytes_written = 0;
465         int retval;
466         while (bytes_written < nbytes) {
467                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
468                 if (retval < 1) {
469                         return;
470                 }
471                 bytes_written = bytes_written + retval;
472         }
473 }
474
475
476
477 /*
478  * input string from socket - implemented in terms of serv_read()
479  */
480 void serv_gets(char *buf)
481 {
482         int i;
483
484         /* Read one character at a time.
485          */
486         for (i = 0;; i++) {
487                 serv_read(&buf[i], 1);
488                 if (buf[i] == '\n' || i == (SIZ-1))
489                         break;
490         }
491
492         /* If we got a long line, discard characters until the newline.
493          */
494         if (i == (SIZ-1)) {
495                 while (buf[i] != '\n') {
496                         serv_read(&buf[i], 1);
497                 }
498         }
499
500         /* Strip all trailing nonprintables (crlf)
501          */
502         buf[i] = 0;
503 }
504
505
506 /*
507  * send line to server - implemented in terms of serv_write()
508  */
509 void serv_puts(char *buf)
510 {
511         serv_write(buf, strlen(buf));
512         serv_write("\n", 1);
513 }
514
515
516 /*
517  * Convenience functions to get/set system configuration entries
518  */
519 void getconf_str(char *buf, char *key)
520 {
521         char cmd[SIZ];
522         char ret[SIZ];
523
524         sprintf(cmd, "CONF GETVAL|%s", key);
525         serv_puts(cmd);
526         serv_gets(ret);
527         if (ret[0] == '2') {
528                 extract_token(buf, &ret[4], 0, '|', SIZ);
529         }
530         else {
531                 strcpy(buf, "");
532         }
533 }
534
535 int getconf_int(char *key)
536 {
537         char buf[SIZ];
538         getconf_str(buf, key);
539         return atoi(buf);
540 }
541
542 void setconf_str(char *key, char *val)
543 {
544         char buf[SIZ];
545
546         sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
547         serv_puts(buf);
548         serv_gets(buf);
549 }
550
551
552 void setconf_int(char *key, int val)
553 {
554         char buf[SIZ];
555
556         sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
557         serv_puts(buf);
558         serv_gets(buf);
559 }
560
561
562
563
564
565 /*
566  * On systems which use xinetd, see if we can offer to install Citadel as
567  * the default telnet target.
568  */
569 void check_xinetd_entry(void)
570 {
571         char *filename = "/etc/xinetd.d/telnet";
572         FILE *fp;
573         char buf[SIZ];
574         int already_citadel = 0;
575         int rv;
576
577         fp = fopen(filename, "r+");
578         if (fp == NULL) return;         /* Not there.  Oh well... */
579
580         while (fgets(buf, sizeof buf, fp) != NULL) {
581                 if (strstr(buf, "/citadel") != NULL) {
582                         already_citadel = 1;
583                 }
584         }
585         fclose(fp);
586         if (already_citadel) return;    /* Already set up this way. */
587
588         /* Otherwise, prompt the user to create an entry. */
589         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
590                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
591                         return;
592                 }
593         }
594         else {
595                 snprintf(buf, sizeof buf,
596                          _("Setup can configure the \"xinetd\" service to automatically\n"
597                            "connect incoming telnet sessions to Citadel, bypassing the\n"
598                            "host system login: prompt.  Would you like to do this?\n"
599                          )
600                 );
601                 if (yesno(buf, 1) == 0) {
602                         return;
603                 }
604         }
605
606         fp = fopen(filename, "w");
607         fprintf(fp,
608                 "# description: telnet service for Citadel users\n"
609                 "service telnet\n"
610                 "{\n"
611                 "       disable = no\n"
612                 "       flags           = REUSE\n"
613                 "       socket_type     = stream\n"
614                 "       wait            = no\n"
615                 "       user            = root\n"
616                 "       server          = /usr/sbin/in.telnetd\n"
617                 "       server_args     = -h -L %s/citadel\n"
618                 "       log_on_failure  += USERID\n"
619                 "}\n",
620                 ctdl_bin_dir
621         );
622         fclose(fp);
623
624         /* Now try to restart the service.  This will not have the intended effect on Solaris, but who the hell uses Solaris anymore? */
625         rv = system("systemctl restart xinetd >/dev/null 2>&1");
626         if (rv != 0) {
627                 rv = system("service xinetd restart >/dev/null 2>&1");
628         }
629         if (rv != 0) {
630                 display_error(_("failed to restart xinetd.\n"));
631         }
632 }
633
634
635 /*
636  * Offer to disable other MTA's
637  */
638 void disable_other_mta(const char *mta) {
639         char buf[SIZ];
640         FILE *fp;
641         int lines = 0;
642         int rv;
643
644         snprintf(buf, sizeof buf,
645                 "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
646                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
647                 mta, mta
648         );
649         fp = popen(buf, "r");
650         if (fp == NULL) return;
651
652         while (fgets(buf, sizeof buf, fp) != NULL) {
653                 ++lines;
654         }
655         fclose(fp);
656         if (lines == 0) return;         /* Nothing to do. */
657
658         /* Offer to replace other MTA with the vastly superior Citadel :)  */
659
660         snprintf(buf, sizeof buf,
661                  "%s \"%s\" %s%s%s%s%s%s%s", 
662                  _("You appear to have the "), 
663                  mta, 
664                  _(" email program\n"
665                    "running on your system.  If you want Citadel mail\n"
666                    "connected with "), 
667                  mta,
668                  _(" you will have to manually integrate\n"
669                    "them.  It is preferable to disable "), 
670                  mta,
671                  _(", and use Citadel's\n"
672                    "SMTP, POP3, and IMAP services.\n\n"
673                    "May we disable "), 
674                  mta, 
675                  _("so that Citadel has access to ports\n"
676                    "25, 110, and 143?\n")
677                 );
678         if (yesno(buf, 1) == 0) {
679                 return;
680         }
681         
682
683         snprintf(buf, sizeof buf, "for x in /etc/rc*.d/S*%s; do mv $x `echo $x |sed s/S/K/g`; done >/dev/null 2>&1", mta);
684         rv = system(buf);
685         if (rv != 0)
686                 display_error("%s %s.\n", _("failed to disable other mta"), mta);
687
688         snprintf(buf, sizeof buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
689         rv = system(buf);
690         if (rv != 0)
691                 display_error(" %s.\n", _("failed to disable other mta"), mta);
692 }
693
694 const char *other_mtas[] = {
695         "courier-authdaemon",
696         "courier-imap",
697         "courier-imap-ssl",
698         "courier-pop",
699         "courier-pop3",
700         "courier-pop3d",
701         "cyrmaster",
702         "cyrus",
703         "dovecot",
704         "exim",
705         "exim4",
706         "imapd",
707         "mta",
708         "pop3d",
709         "popd",
710         "postfix",
711         "qmail",
712         "saslauthd",
713         "sendmail",
714         "vmailmgrd",
715         ""
716 };
717
718 void disable_other_mtas(void)
719 {
720         int i = 0;
721         if ((getenv("ACT_AS_MTA") == NULL) || 
722             (getenv("ACT_AS_MTA") &&
723              strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
724                 /* Offer to disable other MTA's on the system. */
725                 while (!IsEmptyStr(other_mtas[i]))
726                 {
727                         disable_other_mta(other_mtas[i]);
728                         i++;
729                 }
730         }
731 }
732
733 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
734 {
735         char buf[SIZ] = "";
736         char setupmsg[SIZ];
737         char dialog_result[PATH_MAX];
738         FILE *fp = NULL;
739         int rv;
740
741         strcpy(setupmsg, "");
742
743         switch (setup_type) {
744         case UI_TEXT:
745                 title(prompt_title);
746                 printf("\n%s\n", prompt_text);
747                 printf("%s\n%s\n", _("This is currently set to:"), Target);
748                 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
749                 if (fgets(buf, sizeof buf, stdin)) {
750                         buf[strlen(buf) - 1] = 0;
751                 }
752                 if (!IsEmptyStr(buf))
753                         strcpy(Target, buf);
754                 break;
755
756         case UI_DIALOG:
757                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
758                 snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --nocancel --inputbox '%s' 19 72 '%s' 2>%s",
759                         getenv("CTDL_DIALOG"),
760                         program_title,
761                         prompt_text,
762                         Target,
763                         dialog_result);
764                 rv = system(buf);
765                 if (rv != 0) {
766                         fprintf(stderr, "failed to run whiptail or dialog\n");
767                 }
768                 
769                 fp = fopen(dialog_result, "r");
770                 if (fp != NULL) {
771                         if (fgets(Target, sizeof buf, fp)) {
772                                 if (Target[strlen(Target)-1] == 10) {
773                                         Target[strlen(Target)-1] = 0;
774                                 }
775                         }
776                         fclose(fp);
777                         unlink(dialog_result);
778                 }
779                 break;
780         case UI_SILENT:
781                 if (*DefValue != '\0')
782                         strcpy(Target, DefValue);
783                 break;
784         }
785 }
786
787 void set_bool_val(int msgpos, int *ip, char *DefValue) 
788 {
789         title(setup_titles[msgpos]);
790         *ip = yesno(setup_text[msgpos], *ip);
791 }
792
793 void set_str_val(int msgpos, char *Target, char *DefValue) 
794 {
795         strprompt(setup_titles[msgpos], 
796                   setup_text[msgpos], 
797                   Target, 
798                   DefValue
799         );
800 }
801
802 /* like set_str_val() but for numeric values */
803 void set_int_val(int msgpos, int *target, char *default_value)
804 {
805         char buf[32];
806         sprintf(buf, "%d", *target);
807         do {
808                 set_str_val(msgpos, buf, default_value);
809         } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
810         *target = atoi(buf);
811 }
812
813
814 void edit_value(int curr)
815 {
816         struct passwd *pw = NULL;
817         char ctdluidname[256];
818         char buf[SIZ];
819         char *default_value = NULL;
820         int ctdluid = 0;
821         int portnum = 0;
822         int auth = 0;
823         int lportnum = 0;
824
825         if (setup_type == UI_SILENT)
826         {
827                 default_value = getenv(EnvNames[curr]);
828         }
829         if (default_value == NULL) {
830                 default_value = "";
831         }
832
833         switch (curr) {
834
835         case eSysAdminName:
836                 getconf_str(admin_name, "c_sysadm");
837                 set_str_val(curr, admin_name, default_value);
838                 setconf_str("c_sysadm", admin_name);
839                 break;
840
841         case eSysAdminPW:
842                 set_str_val(curr, admin_pass, default_value);
843                 break;
844         
845         case eUID:
846                 ctdluid = getconf_int("c_ctdluid");
847                 if (setup_type == UI_SILENT)
848                 {               
849                         if (default_value) {
850                                 ctdluid = atoi(default_value);
851                         }                                       
852                 }
853                 else
854                 {
855 #ifdef __CYGWIN__
856                         ctdluid = 0;    /* work-around for Windows */
857 #else
858                         pw = getpwuid(ctdluid);
859                         if (pw == NULL) {
860                                 set_int_val(curr, &ctdluid, default_value);
861                         }
862                         else {
863                                 strcpy(ctdluidname, pw->pw_name);
864                                 set_str_val(curr, ctdluidname, default_value);
865                                 pw = getpwnam(ctdluidname);
866                                 if (pw != NULL) {
867                                         ctdluid = pw->pw_uid;
868                                 }
869                                 else if (atoi(ctdluidname) > 0) {
870                                         ctdluid = atoi(ctdluidname);
871                                 }
872                         }
873 #endif
874                 }
875                 setconf_int("c_ctdluid", ctdluid);
876                 break;
877
878         case eIP_ADDR:
879                 getconf_str(buf, "c_ip_addr");
880                 set_str_val(curr, buf, default_value);
881                 setconf_str("c_ip_addr", buf);
882                 break;
883
884         case eCTDL_Port:
885                 portnum = getconf_int("c_port_number");
886                 set_int_val(curr, &portnum, default_value);
887                 setconf_int("c_port_number", portnum);
888                 break;
889
890         case eAuthType:
891                 auth = getconf_int("c_auth_mode");
892                 if (setup_type == UI_SILENT)
893                 {
894                         if ( (default_value) && (!strcasecmp(default_value, "yes")) ) auth = AUTHMODE_HOST;
895                         if ( (default_value) && (!strcasecmp(default_value, "host")) ) auth = AUTHMODE_HOST;
896                         if ( (default_value) && (!strcasecmp(default_value, "ldap")) ) auth = AUTHMODE_LDAP;
897                         if ( (default_value) && (!strcasecmp(default_value, "ldap_ad")) ) auth = AUTHMODE_LDAP_AD;
898                         if ( (default_value) && (!strcasecmp(default_value, "active directory")) ) auth = AUTHMODE_LDAP_AD;
899                 }
900                 else {
901                         set_int_val(curr, &auth, default_value);
902                 }
903                 setconf_int("c_auth_mode", auth);
904                 break;
905
906         case eLDAP_Host:
907                 getconf_str(buf, "c_ldap_host");
908                 if (IsEmptyStr(buf)) {
909                         strcpy(buf, "localhost");
910                 }
911                 set_str_val(curr, buf, default_value);
912                 setconf_str("c_ldap_host", buf);
913                 break;
914
915         case eLDAP_Port:
916                 lportnum = getconf_int("c_ldap_port");
917                 if (lportnum == 0) {
918                         lportnum = 389;
919                 }
920                 set_int_val(curr, &lportnum, default_value);
921                 setconf_int("c_ldap_port", lportnum);
922                 break;
923
924         case eLDAP_Base_DN:
925                 getconf_str(buf, "c_ldap_base_dn");
926                 set_str_val(curr, buf, default_value);
927                 setconf_str("c_ldap_base_dn", buf);
928                 break;
929
930         case eLDAP_Bind_DN:
931                 getconf_str(buf, "c_ldap_bind_dn");
932                 set_str_val(curr, buf, default_value);
933                 setconf_str("c_ldap_bind_dn", buf);
934                 break;
935
936         case eLDAP_Bind_PW:
937                 getconf_str(buf, "c_ldap_bind_pw");
938                 set_str_val(curr, buf, default_value);
939                 setconf_str("c_ldap_bind_pw", buf);
940                 break;
941         }
942 }
943
944
945
946 /*
947  * Figure out what type of user interface we're going to use
948  */
949 int discover_ui(void)
950 {
951
952         /* Use "whiptail" or "dialog" if we have it */
953         if (getenv("CTDL_DIALOG") != NULL) {
954                 return UI_DIALOG;
955         }
956                 
957         return UI_TEXT;
958 }
959
960
961
962 /*
963  * Strip "db" entries out of /etc/nsswitch.conf
964  */
965 void fixnss(void) {
966         FILE *fp_read;
967         int fd_write;
968         char buf[256];
969         char buf_nc[256];
970         char question[512];
971         int i;
972         int file_changed = 0;
973         char new_filename[64];
974         int rv;
975
976         fp_read = fopen(NSSCONF, "r");
977         if (fp_read == NULL) {
978                 return;
979         }
980
981         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
982         fd_write = mkstemp(new_filename);
983         if (fd_write < 0) {
984                 fclose(fp_read);
985                 return;
986         }
987
988         while (fgets(buf, sizeof buf, fp_read) != NULL) {
989                 strcpy(buf_nc, buf);
990                 for (i=0; buf_nc[i]; ++i) {
991                         if (buf_nc[i] == '#') {
992                                 buf_nc[i] = 0;
993                                 break;
994                         }
995                 }
996                 for (i=0; i<strlen(buf_nc); ++i) {
997                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
998                                 if (i > 0) {
999                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
1000                                                 file_changed = 1;
1001                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
1002                                                 strcpy(&buf[i], &buf[i+2]);
1003                                                 if (buf[i]==32) {
1004                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
1005                                                         strcpy(&buf[i], &buf[i+1]);
1006                                                 }
1007                                         }
1008                                 }
1009                         }
1010                 }
1011                 long buflen = strlen(buf);
1012                 if (write(fd_write, buf, buflen) != buflen) {
1013                         fclose(fp_read);
1014                         close(fd_write);
1015                         unlink(new_filename);
1016                         return;
1017                 }
1018         }
1019
1020         fclose(fp_read);
1021         
1022         if (!file_changed) {
1023                 unlink(new_filename);
1024                 return;
1025         }
1026
1027         snprintf(question, sizeof question,
1028                  _(
1029                          "\n"
1030                          "/etc/nsswitch.conf is configured to use the 'db' module for\n"
1031                          "one or more services.  This is not necessary on most systems,\n"
1032                          "and it is known to crash the Citadel server when delivering\n"
1033                          "mail to the Internet.\n"
1034                          "\n"
1035                          "Do you want this module to be automatically disabled?\n"
1036                          "\n"
1037                          )
1038         );
1039
1040         if (yesno(question, 1)) {
1041                 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
1042                 rv = system(buf);
1043                 if (rv != 0) {
1044                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
1045                 }
1046                 chmod(NSSCONF, 0644);
1047         }
1048         unlink(new_filename);
1049 }
1050
1051
1052 /*
1053  * Messages that are no longer in use.
1054  * We keep them here so we don't lose the translations if we need them later.
1055  */
1056 #if 0
1057                                 important_message(_("Setup finished"),
1058                                                   _("Setup of the Citadel server is complete.\n"
1059                                                     "If you will be using WebCit, please run its\n"
1060                                                     "setup program now; otherwise, run './citadel'\n"
1061                                                     "to log in.\n"));
1062                         important_message(_("Setup failed"),
1063                                           _("Setup is finished, but the Citadel server failed to start.\n"
1064                                             "Go back and check your configuration.\n")
1065                 important_message(_("Setup finished"),
1066                                   _("Setup is finished.  You may now start the server."));
1067 #endif
1068
1069
1070
1071
1072 int main(int argc, char *argv[])
1073 {
1074         int a, i;
1075         int curr;
1076         char buf[1024]; 
1077         char aaa[128];
1078         int relh = 0;
1079         int home = 0;
1080         char relhome[PATH_MAX]="";
1081         char ctdldir[PATH_MAX]=CTDLDIR;
1082         struct passwd *pw;
1083         gid_t gid;
1084         char *activity = NULL;
1085         
1086         /* Keep a mild groove on */
1087         program_title = _("Citadel setup program");
1088
1089         /* set an invalid setup type */
1090         setup_type = (-1);
1091
1092         /* parse command line args */
1093         for (a = 0; a < argc; ++a) {
1094                 if (!strncmp(argv[a], "-u", 2)) {
1095                         strcpy(aaa, argv[a]);
1096                         strcpy(aaa, &aaa[2]);
1097                         setup_type = atoi(aaa);
1098                 }
1099                 else if (!strcmp(argv[a], "-q")) {
1100                         setup_type = UI_SILENT;
1101                 }
1102                 else if (!strncmp(argv[a], "-h", 2)) {
1103                         relh=argv[a][2]!='/';
1104                         if (!relh) {
1105                                 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
1106                         } else {
1107                                 safestrncpy(relhome, &argv[a][2], sizeof relhome);
1108                         }
1109                         home = 1;
1110                 }
1111
1112         }
1113
1114         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1115         SetTitles();
1116
1117         /* If a setup type was not specified, try to determine automatically
1118          * the best one to use out of all available types.
1119          */
1120         if (setup_type < 0) {
1121                 setup_type = discover_ui();
1122         }
1123
1124         enable_home = ( relh | home );
1125
1126         if (chdir(ctdl_run_dir) != 0) {
1127                 display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir);
1128                 exit(errno);
1129         }
1130
1131
1132         /*
1133          * Connect to the running Citadel server.
1134          */
1135         char *connectingmsg = _("Connecting to Citadel server");
1136         for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) {          /* wait for server to start up */
1137                 progress(connectingmsg, i, 30);
1138                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1139                 sleep(1);
1140         }
1141         progress(connectingmsg, 30, 30);
1142
1143         if (serv_sock < 0) { 
1144                 display_error(
1145                         "%s: %s %s\n", 
1146                         _("Setup could not connect to a running Citadel server."),
1147                         strerror(errno), file_citadel_admin_socket
1148                 );
1149                 exit(1);
1150         }
1151
1152         /*
1153          * read the server greeting
1154          */
1155         serv_gets(buf);
1156         if (buf[0] != '2') {
1157                 display_error("%s\n", buf);
1158                 exit(2);
1159         }
1160
1161         /*
1162          * Are we connected to the correct Citadel server?
1163          */
1164         serv_puts("INFO");
1165         serv_gets(buf);
1166         if (buf[0] != '1') {
1167                 display_error("%s\n", buf);
1168                 exit(3);
1169         }
1170         a = 0;
1171         while (serv_gets(buf), strcmp(buf, "000")) {
1172                 if (a == 5) {
1173                         if (atoi(buf) != REV_LEVEL) {
1174                                 display_error("%s\n",
1175                                 _("Your setup program and Citadel server are from different versions.")
1176                                 );
1177                                 exit(4);
1178                         }
1179                 }
1180                 ++a;
1181         }
1182
1183         /*
1184          * Now begin.
1185          */
1186
1187
1188         if (setup_type == UI_TEXT) {
1189                 printf("\n\n\n         *** %s ***\n\n", program_title);
1190         }
1191
1192         if (setup_type == UI_DIALOG) {
1193                 system("clear 2>/dev/null");
1194         }
1195
1196         /* Go through a series of dialogs prompting for config info */
1197         for (curr = 1; curr < eMaxQuestions; ++curr) {
1198                 edit_value(curr);
1199
1200                 if (    (curr == eAuthType)
1201                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
1202                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
1203                 ) {
1204                         curr += 5;      /* skip LDAP questions if we're not authenticating against LDAP */
1205                 }
1206
1207                 if (curr == eSysAdminName) {
1208                         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1209                                                 /* for native auth mode, fetch the admin's existing pw */
1210                                 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
1211                                 serv_puts(buf);
1212                                 serv_gets(buf);
1213                                 if (buf[0] == '2') {
1214                                         extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
1215                                 }
1216                         }
1217                         else {
1218                                 ++curr;         /* skip the password question for non-native auth modes */
1219                         }
1220                 }
1221         }
1222
1223         if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
1224                 gid = getgid();
1225         } else {
1226                 gid = pw->pw_gid;
1227         }
1228
1229         if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
1230                 display_error("%s\n", _("failed to create directories"));
1231         }
1232                 
1233         activity = _("Reconfiguring Citadel server");
1234         progress(activity, 0, 5);
1235         sleep(1);                                       /* Let the message appear briefly */
1236
1237         /*
1238          * Create the administrator account.  It's ok if the command fails if this user already exists.
1239          */
1240         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1241                 progress(activity, 1, 5);
1242                 snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
1243                 serv_puts(buf);
1244                 progress(activity, 2, 5);
1245                 serv_gets(buf);
1246         }
1247         progress(activity, 3, 5);
1248
1249         /*
1250          * Assign the desired password and access level to the administrator account.
1251          */
1252         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1253                 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
1254                 serv_puts(buf);
1255                 progress(activity, 4, 5);
1256                 serv_gets(buf);
1257                 if (buf[0] == '2') {
1258                         int admin_flags = extract_int(&buf[4], 2);
1259                         int admin_times_called = extract_int(&buf[4], 3);
1260                         int admin_msgs_posted = extract_int(&buf[4], 4);
1261                         snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
1262                                 admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
1263                         );
1264                         serv_puts(buf);
1265                         serv_gets(buf);
1266                 }
1267         }
1268         progress(activity, 5, 5);
1269
1270 #ifndef __CYGWIN__
1271         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1272         disable_other_mtas();   /* Offer to disable other MTAs */
1273         fixnss();               /* Check for the 'db' nss and offer to disable it */
1274 #endif
1275
1276         /*
1277          * Restart citserver
1278          */
1279         activity = _("Restarting Citadel server to apply changes");
1280         progress(activity, 0, 51);
1281
1282         serv_puts("TIME");
1283         serv_gets(buf);
1284         long original_start_time = extract_long(&buf[4], 3);
1285
1286         progress(activity, 1, 51);
1287         serv_puts("DOWN 1");
1288         progress(activity, 2, 51);
1289         serv_gets(buf);
1290         if (buf[0] != '2') {
1291                 display_error("%s\n", buf);
1292                 exit(6);
1293         }
1294
1295         close(serv_sock);
1296         serv_sock = (-1);
1297
1298         for (i=3; i<=6; ++i) {                                  /* wait for server to shut down */
1299                 progress(activity, i, 51);
1300                 sleep(1);
1301         }
1302
1303         for (i=7; ((i<=48) && (serv_sock < 0)) ; ++i) {         /* wait for server to start up */
1304                 progress(activity, i, 51);
1305                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1306                 sleep(1);
1307         }
1308
1309         progress(activity, 49, 51);
1310         serv_gets(buf);
1311
1312         progress(activity, 50, 51);
1313         serv_puts("TIME");
1314         serv_gets(buf);
1315         long new_start_time = extract_long(&buf[4], 3);
1316
1317         close(serv_sock);
1318         progress(activity, 51, 51);
1319
1320         if (    (original_start_time == new_start_time)
1321                 || (new_start_time <= 0)
1322         ) {
1323                 display_error("%s\n", _("Setup failed to restart Citadel server.  Please restart it manually."));
1324                 exit(7);
1325         }
1326
1327         exit(0);
1328         return 0;
1329 }