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