ce99e8f02772212a8a79673d37039e6918397187
[citadel.git] / citadel / utils / setup.c
1 /*
2  * Citadel setup utility
3  *
4  * Copyright (c) 1987-2016 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 compliant directory\n"
196 " 3. External LDAP - nonstandard MS Active Directory\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
419 int uds_connectsock(char *sockpath)
420 {
421         int s;
422         struct sockaddr_un addr;
423
424         memset(&addr, 0, sizeof(addr));
425         addr.sun_family = AF_UNIX;
426         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
427
428         s = socket(AF_UNIX, SOCK_STREAM, 0);
429         if (s < 0) {
430                 return(-1);
431         }
432
433         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
434                 close(s);
435                 return(-1);
436         }
437
438         return s;
439 }
440
441
442 /*
443  * input binary data from socket
444  */
445 void serv_read(char *buf, int bytes)
446 {
447         int len, rlen;
448
449         len = 0;
450         while (len < bytes) {
451                 rlen = read(serv_sock, &buf[len], bytes - len);
452                 if (rlen < 1) {
453                         return;
454                 }
455                 len = len + rlen;
456         }
457 }
458
459
460 /*
461  * send binary to server
462  */
463 void serv_write(char *buf, int nbytes)
464 {
465         int bytes_written = 0;
466         int retval;
467         while (bytes_written < nbytes) {
468                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
469                 if (retval < 1) {
470                         return;
471                 }
472                 bytes_written = bytes_written + retval;
473         }
474 }
475
476
477
478 /*
479  * input string from socket - implemented in terms of serv_read()
480  */
481 void serv_gets(char *buf)
482 {
483         int i;
484
485         /* Read one character at a time.
486          */
487         for (i = 0;; i++) {
488                 serv_read(&buf[i], 1);
489                 if (buf[i] == '\n' || i == (SIZ-1))
490                         break;
491         }
492
493         /* If we got a long line, discard characters until the newline.
494          */
495         if (i == (SIZ-1)) {
496                 while (buf[i] != '\n') {
497                         serv_read(&buf[i], 1);
498                 }
499         }
500
501         /* Strip all trailing nonprintables (crlf)
502          */
503         buf[i] = 0;
504 }
505
506
507 /*
508  * send line to server - implemented in terms of serv_write()
509  */
510 void serv_puts(char *buf)
511 {
512         serv_write(buf, strlen(buf));
513         serv_write("\n", 1);
514 }
515
516
517 /*
518  * Convenience functions to get/set system configuration entries
519  */
520 void getconf_str(char *buf, char *key)
521 {
522         char cmd[SIZ];
523         char ret[SIZ];
524
525         sprintf(cmd, "CONF GETVAL|%s", key);
526         serv_puts(cmd);
527         serv_gets(ret);
528         if (ret[0] == '2') {
529                 extract_token(buf, &ret[4], 0, '|', SIZ);
530         }
531         else {
532                 strcpy(buf, "");
533         }
534 }
535
536 int getconf_int(char *key)
537 {
538         char buf[SIZ];
539         getconf_str(buf, key);
540         return atoi(buf);
541 }
542
543 void setconf_str(char *key, char *val)
544 {
545         char buf[SIZ];
546
547         sprintf(buf, "CONF PUTVAL|%s|%s", key, val);
548         serv_puts(buf);
549         serv_gets(buf);
550 }
551
552
553 void setconf_int(char *key, int val)
554 {
555         char buf[SIZ];
556
557         sprintf(buf, "CONF PUTVAL|%s|%d", key, val);
558         serv_puts(buf);
559         serv_gets(buf);
560 }
561
562
563
564
565
566 /*
567  * On systems which use xinetd, see if we can offer to install Citadel as
568  * the default telnet target.
569  */
570 void check_xinetd_entry(void)
571 {
572         char *filename = "/etc/xinetd.d/telnet";
573         FILE *fp;
574         char buf[SIZ];
575         int already_citadel = 0;
576         int rv;
577
578         fp = fopen(filename, "r+");
579         if (fp == NULL) return;         /* Not there.  Oh well... */
580
581         while (fgets(buf, sizeof buf, fp) != NULL) {
582                 if (strstr(buf, "/citadel") != NULL) {
583                         already_citadel = 1;
584                 }
585         }
586         fclose(fp);
587         if (already_citadel) return;    /* Already set up this way. */
588
589         /* Otherwise, prompt the user to create an entry. */
590         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
591                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
592                         return;
593                 }
594         }
595         else {
596                 snprintf(buf, sizeof buf,
597                          _("Setup can configure the \"xinetd\" service to automatically\n"
598                            "connect incoming telnet sessions to Citadel, bypassing the\n"
599                            "host system login: prompt.  Would you like to do this?\n"
600                          )
601                 );
602                 if (yesno(buf, 1) == 0) {
603                         return;
604                 }
605         }
606
607         fp = fopen(filename, "w");
608         fprintf(fp,
609                 "# description: telnet service for Citadel users\n"
610                 "service telnet\n"
611                 "{\n"
612                 "       disable = no\n"
613                 "       flags           = REUSE\n"
614                 "       socket_type     = stream\n"
615                 "       wait            = no\n"
616                 "       user            = root\n"
617                 "       server          = /usr/sbin/in.telnetd\n"
618                 "       server_args     = -h -L %s/citadel\n"
619                 "       log_on_failure  += USERID\n"
620                 "}\n",
621                 ctdl_bin_dir);
622         fclose(fp);
623
624         /* Now try to restart the service */
625         rv = system("/etc/init.d/xinetd restart >/dev/null 2>&1");
626         if (rv != 0) {
627                 display_error(_("failed to restart xinetd.\n"));
628         }
629 }
630
631
632
633 /*
634  * Offer to disable other MTA's
635  */
636 void disable_other_mta(const char *mta) {
637         char buf[SIZ];
638         FILE *fp;
639         int lines = 0;
640         int rv;
641
642         snprintf(buf, sizeof buf,
643                 "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
644                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
645                 mta, mta
646         );
647         fp = popen(buf, "r");
648         if (fp == NULL) return;
649
650         while (fgets(buf, sizeof buf, fp) != NULL) {
651                 ++lines;
652         }
653         fclose(fp);
654         if (lines == 0) return;         /* Nothing to do. */
655
656         /* Offer to replace other MTA with the vastly superior Citadel :)  */
657
658         snprintf(buf, sizeof buf,
659                  "%s \"%s\" %s%s%s%s%s%s%s", 
660                  _("You appear to have the "), 
661                  mta, 
662                  _(" email program\n"
663                    "running on your system.  If you want Citadel mail\n"
664                    "connected with "), 
665                  mta,
666                  _(" you will have to manually integrate\n"
667                    "them.  It is preferable to disable "), 
668                  mta,
669                  _(", and use Citadel's\n"
670                    "SMTP, POP3, and IMAP services.\n\n"
671                    "May we disable "), 
672                  mta, 
673                  _("so that Citadel has access to ports\n"
674                    "25, 110, and 143?\n")
675                 );
676         if (yesno(buf, 1) == 0) {
677                 return;
678         }
679         
680
681         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);
682         rv = system(buf);
683         if (rv != 0)
684                 display_error("%s %s.\n", _("failed to disable other mta"), mta);
685
686         snprintf(buf, sizeof buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
687         rv = system(buf);
688         if (rv != 0)
689                 display_error(" %s.\n", _("failed to disable other mta"), mta);
690 }
691
692 const char *other_mtas[] = {
693         "courier-authdaemon",
694         "courier-imap",
695         "courier-imap-ssl",
696         "courier-pop",
697         "courier-pop3",
698         "courier-pop3d",
699         "cyrmaster",
700         "cyrus",
701         "dovecot",
702         "exim",
703         "exim4",
704         "imapd",
705         "mta",
706         "pop3d",
707         "popd",
708         "postfix",
709         "qmail",
710         "saslauthd",
711         "sendmail",
712         "vmailmgrd",
713         ""
714 };
715
716 void disable_other_mtas(void)
717 {
718         int i = 0;
719         if ((getenv("ACT_AS_MTA") == NULL) || 
720             (getenv("ACT_AS_MTA") &&
721              strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
722                 /* Offer to disable other MTA's on the system. */
723                 while (!IsEmptyStr(other_mtas[i]))
724                 {
725                         disable_other_mta(other_mtas[i]);
726                         i++;
727                 }
728         }
729 }
730
731 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
732 {
733         char buf[SIZ] = "";
734         char setupmsg[SIZ];
735         char dialog_result[PATH_MAX];
736         FILE *fp = NULL;
737         int rv;
738
739         strcpy(setupmsg, "");
740
741         switch (setup_type) {
742         case UI_TEXT:
743                 title(prompt_title);
744                 printf("\n%s\n", prompt_text);
745                 printf("%s\n%s\n", _("This is currently set to:"), Target);
746                 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
747                 if (fgets(buf, sizeof buf, stdin)) {
748                         buf[strlen(buf) - 1] = 0;
749                 }
750                 if (!IsEmptyStr(buf))
751                         strcpy(Target, buf);
752                 break;
753
754         case UI_DIALOG:
755                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
756                 snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --nocancel --inputbox '%s' 19 72 '%s' 2>%s",
757                         getenv("CTDL_DIALOG"),
758                         program_title,
759                         prompt_text,
760                         Target,
761                         dialog_result);
762                 rv = system(buf);
763                 if (rv != 0) {
764                         fprintf(stderr, "failed to run whiptail or dialog\n");
765                 }
766                 
767                 fp = fopen(dialog_result, "r");
768                 if (fp != NULL) {
769                         if (fgets(Target, sizeof buf, fp)) {
770                                 if (Target[strlen(Target)-1] == 10) {
771                                         Target[strlen(Target)-1] = 0;
772                                 }
773                         }
774                         fclose(fp);
775                         unlink(dialog_result);
776                 }
777                 break;
778         case UI_SILENT:
779                 if (*DefValue != '\0')
780                         strcpy(Target, DefValue);
781                 break;
782         }
783 }
784
785 void set_bool_val(int msgpos, int *ip, char *DefValue) 
786 {
787         title(setup_titles[msgpos]);
788         *ip = yesno(setup_text[msgpos], *ip);
789 }
790
791 void set_str_val(int msgpos, char *Target, char *DefValue) 
792 {
793         strprompt(setup_titles[msgpos], 
794                   setup_text[msgpos], 
795                   Target, 
796                   DefValue
797         );
798 }
799
800 /* like set_str_val() but for numeric values */
801 void set_int_val(int msgpos, int *target, char *default_value)
802 {
803         char buf[32];
804         sprintf(buf, "%d", *target);
805         do {
806                 set_str_val(msgpos, buf, default_value);
807         } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
808         *target = atoi(buf);
809 }
810
811
812 void edit_value(int curr)
813 {
814         struct passwd *pw = NULL;
815         char ctdluidname[256];
816         char buf[SIZ];
817         char *default_value = NULL;
818         int ctdluid = 0;
819         int portnum = 0;
820         int auth = 0;
821         int lportnum = 0;
822
823         if (setup_type == UI_SILENT)
824         {
825                 default_value = getenv(EnvNames[curr]);
826         }
827         if (default_value == NULL) {
828                 default_value = "";
829         }
830
831         switch (curr) {
832
833         case eSysAdminName:
834                 getconf_str(admin_name, "c_sysadm");
835                 set_str_val(curr, admin_name, default_value);
836                 setconf_str("c_sysadm", admin_name);
837                 break;
838
839         case eSysAdminPW:
840                 set_str_val(curr, admin_pass, default_value);
841                 break;
842         
843         case eUID:
844                 ctdluid = getconf_int("c_ctdluid");
845                 if (setup_type == UI_SILENT)
846                 {               
847                         if (default_value) {
848                                 ctdluid = atoi(default_value);
849                         }                                       
850                 }
851                 else
852                 {
853 #ifdef __CYGWIN__
854                         ctdluid = 0;    /* work-around for Windows */
855 #else
856                         pw = getpwuid(ctdluid);
857                         if (pw == NULL) {
858                                 set_int_val(curr, &ctdluid, default_value);
859                         }
860                         else {
861                                 strcpy(ctdluidname, pw->pw_name);
862                                 set_str_val(curr, ctdluidname, default_value);
863                                 pw = getpwnam(ctdluidname);
864                                 if (pw != NULL) {
865                                         ctdluid = pw->pw_uid;
866                                 }
867                                 else if (atoi(ctdluidname) > 0) {
868                                         ctdluid = atoi(ctdluidname);
869                                 }
870                         }
871 #endif
872                 }
873                 setconf_int("c_ctdluid", ctdluid);
874                 break;
875
876         case eIP_ADDR:
877                 getconf_str(buf, "c_ip_addr");
878                 set_str_val(curr, buf, default_value);
879                 setconf_str("c_ip_addr", buf);
880                 break;
881
882         case eCTDL_Port:
883                 portnum = getconf_int("c_port_number");
884                 set_int_val(curr, &portnum, default_value);
885                 setconf_int("c_port_number", portnum);
886                 break;
887
888         case eAuthType:
889                 auth = getconf_int("c_auth_mode");
890                 if (setup_type == UI_SILENT)
891                 {
892                         if ( (default_value) && (!strcasecmp(default_value, "yes")) ) auth = AUTHMODE_HOST;
893                         if ( (default_value) && (!strcasecmp(default_value, "host")) ) auth = AUTHMODE_HOST;
894                         if ( (default_value) && (!strcasecmp(default_value, "ldap")) ) auth = AUTHMODE_LDAP;
895                         if ( (default_value) && (!strcasecmp(default_value, "ldap_ad")) ) auth = AUTHMODE_LDAP_AD;
896                         if ( (default_value) && (!strcasecmp(default_value, "active directory")) ) auth = AUTHMODE_LDAP_AD;
897                 }
898                 else {
899                         set_int_val(curr, &auth, default_value);
900                 }
901                 setconf_int("c_auth_mode", auth);
902                 break;
903
904         case eLDAP_Host:
905                 getconf_str(buf, "c_ldap_host");
906                 if (IsEmptyStr(buf)) {
907                         strcpy(buf, "localhost");
908                 }
909                 set_str_val(curr, buf, default_value);
910                 setconf_str("c_ldap_host", buf);
911                 break;
912
913         case eLDAP_Port:
914                 lportnum = getconf_int("c_ldap_port");
915                 if (lportnum == 0) {
916                         lportnum = 389;
917                 }
918                 set_int_val(curr, &lportnum, default_value);
919                 setconf_int("c_ldap_port", lportnum);
920                 break;
921
922         case eLDAP_Base_DN:
923                 getconf_str(buf, "c_ldap_base_dn");
924                 set_str_val(curr, buf, default_value);
925                 setconf_str("c_ldap_base_dn", buf);
926                 break;
927
928         case eLDAP_Bind_DN:
929                 getconf_str(buf, "c_ldap_bind_dn");
930                 set_str_val(curr, buf, default_value);
931                 setconf_str("c_ldap_bind_dn", buf);
932                 break;
933
934         case eLDAP_Bind_PW:
935                 getconf_str(buf, "c_ldap_bind_pw");
936                 set_str_val(curr, buf, default_value);
937                 setconf_str("c_ldap_bind_pw", buf);
938                 break;
939         }
940 }
941
942
943
944 /*
945  * Figure out what type of user interface we're going to use
946  */
947 int discover_ui(void)
948 {
949
950         /* Use "whiptail" or "dialog" if we have it */
951         if (getenv("CTDL_DIALOG") != NULL) {
952                 return UI_DIALOG;
953         }
954                 
955         return UI_TEXT;
956 }
957
958
959
960 /*
961  * Strip "db" entries out of /etc/nsswitch.conf
962  */
963 void fixnss(void) {
964         FILE *fp_read;
965         int fd_write;
966         char buf[256];
967         char buf_nc[256];
968         char question[512];
969         int i;
970         int file_changed = 0;
971         char new_filename[64];
972         int rv;
973
974         fp_read = fopen(NSSCONF, "r");
975         if (fp_read == NULL) {
976                 return;
977         }
978
979         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
980         fd_write = mkstemp(new_filename);
981         if (fd_write < 0) {
982                 fclose(fp_read);
983                 return;
984         }
985
986         while (fgets(buf, sizeof buf, fp_read) != NULL) {
987                 strcpy(buf_nc, buf);
988                 for (i=0; buf_nc[i]; ++i) {
989                         if (buf_nc[i] == '#') {
990                                 buf_nc[i] = 0;
991                                 break;
992                         }
993                 }
994                 for (i=0; i<strlen(buf_nc); ++i) {
995                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
996                                 if (i > 0) {
997                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
998                                                 file_changed = 1;
999                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
1000                                                 strcpy(&buf[i], &buf[i+2]);
1001                                                 if (buf[i]==32) {
1002                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
1003                                                         strcpy(&buf[i], &buf[i+1]);
1004                                                 }
1005                                         }
1006                                 }
1007                         }
1008                 }
1009                 long buflen = strlen(buf);
1010                 if (write(fd_write, buf, buflen) != buflen) {
1011                         fclose(fp_read);
1012                         close(fd_write);
1013                         unlink(new_filename);
1014                         return;
1015                 }
1016         }
1017
1018         fclose(fp_read);
1019         
1020         if (!file_changed) {
1021                 unlink(new_filename);
1022                 return;
1023         }
1024
1025         snprintf(question, sizeof question,
1026                  _(
1027                          "\n"
1028                          "/etc/nsswitch.conf is configured to use the 'db' module for\n"
1029                          "one or more services.  This is not necessary on most systems,\n"
1030                          "and it is known to crash the Citadel server when delivering\n"
1031                          "mail to the Internet.\n"
1032                          "\n"
1033                          "Do you want this module to be automatically disabled?\n"
1034                          "\n"
1035                          )
1036         );
1037
1038         if (yesno(question, 1)) {
1039                 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
1040                 rv = system(buf);
1041                 if (rv != 0) {
1042                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
1043                 }
1044                 chmod(NSSCONF, 0644);
1045         }
1046         unlink(new_filename);
1047 }
1048
1049
1050 /*
1051  * Messages that are no longer in use.
1052  * We keep them here so we don't lose the translations if we need them later.
1053  */
1054 #if 0
1055                                 important_message(_("Setup finished"),
1056                                                   _("Setup of the Citadel server is complete.\n"
1057                                                     "If you will be using WebCit, please run its\n"
1058                                                     "setup program now; otherwise, run './citadel'\n"
1059                                                     "to log in.\n"));
1060                         important_message(_("Setup failed"),
1061                                           _("Setup is finished, but the Citadel server failed to start.\n"
1062                                             "Go back and check your configuration.\n")
1063                 important_message(_("Setup finished"),
1064                                   _("Setup is finished.  You may now start the server."));
1065 #endif
1066
1067
1068
1069
1070 int main(int argc, char *argv[])
1071 {
1072         int a, i;
1073         int curr;
1074         char buf[1024]; 
1075         char aaa[128];
1076         int relh = 0;
1077         int home = 0;
1078         char relhome[PATH_MAX]="";
1079         char ctdldir[PATH_MAX]=CTDLDIR;
1080         struct passwd *pw;
1081         gid_t gid;
1082         char *activity = NULL;
1083         
1084         /* Keep a mild groove on */
1085         program_title = _("Citadel setup program");
1086
1087         /* set an invalid setup type */
1088         setup_type = (-1);
1089
1090         /* parse command line args */
1091         for (a = 0; a < argc; ++a) {
1092                 if (!strncmp(argv[a], "-u", 2)) {
1093                         strcpy(aaa, argv[a]);
1094                         strcpy(aaa, &aaa[2]);
1095                         setup_type = atoi(aaa);
1096                 }
1097                 else if (!strcmp(argv[a], "-q")) {
1098                         setup_type = UI_SILENT;
1099                 }
1100                 else if (!strncmp(argv[a], "-h", 2)) {
1101                         relh=argv[a][2]!='/';
1102                         if (!relh) {
1103                                 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
1104                         } else {
1105                                 safestrncpy(relhome, &argv[a][2], sizeof relhome);
1106                         }
1107                         home = 1;
1108                 }
1109
1110         }
1111
1112         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1113         SetTitles();
1114
1115         /* If a setup type was not specified, try to determine automatically
1116          * the best one to use out of all available types.
1117          */
1118         if (setup_type < 0) {
1119                 setup_type = discover_ui();
1120         }
1121
1122         enable_home = ( relh | home );
1123
1124         if (chdir(ctdl_run_dir) != 0) {
1125                 display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir);
1126                 exit(errno);
1127         }
1128
1129
1130         /*
1131          * Connect to the running Citadel server.
1132          */
1133         char *connectingmsg = _("Connecting to Citadel server");
1134         for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) {          /* wait for server to start up */
1135                 progress(connectingmsg, i, 30);
1136                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1137                 sleep(1);
1138         }
1139         progress(connectingmsg, 30, 30);
1140
1141         if (serv_sock < 0) { 
1142                 display_error(
1143                         "%s: %s %s\n", 
1144                         _("Setup could not connect to a running Citadel server."),
1145                         strerror(errno), file_citadel_admin_socket
1146                 );
1147                 exit(1);
1148         }
1149
1150         /*
1151          * read the server greeting
1152          */
1153         serv_gets(buf);
1154         if (buf[0] != '2') {
1155                 display_error("%s\n", buf);
1156                 exit(2);
1157         }
1158
1159         /*
1160          * Are we connected to the correct Citadel server?
1161          */
1162         serv_puts("INFO");
1163         serv_gets(buf);
1164         if (buf[0] != '1') {
1165                 display_error("%s\n", buf);
1166                 exit(3);
1167         }
1168         a = 0;
1169         while (serv_gets(buf), strcmp(buf, "000")) {
1170                 if (a == 5) {
1171                         if (atoi(buf) != REV_LEVEL) {
1172                                 display_error("%s\n",
1173                                 _("Your setup program and Citadel server are from different versions.")
1174                                 );
1175                                 exit(4);
1176                         }
1177                 }
1178                 ++a;
1179         }
1180
1181         /*
1182          * Now begin.
1183          */
1184
1185
1186         if (setup_type == UI_TEXT) {
1187                 printf("\n\n\n         *** %s ***\n\n", program_title);
1188         }
1189
1190         if (setup_type == UI_DIALOG) {
1191                 system("clear 2>/dev/null");
1192         }
1193
1194         /* Go through a series of dialogs prompting for config info */
1195         for (curr = 1; curr < eMaxQuestions; ++curr) {
1196                 edit_value(curr);
1197
1198                 if (    (curr == eAuthType)
1199                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
1200                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
1201                 ) {
1202                         curr += 5;      /* skip LDAP questions if we're not authenticating against LDAP */
1203                 }
1204
1205                 if (curr == eSysAdminName) {
1206                         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1207                                                 /* for native auth mode, fetch the admin's existing pw */
1208                                 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
1209                                 serv_puts(buf);
1210                                 serv_gets(buf);
1211                                 if (buf[0] == '2') {
1212                                         extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
1213                                 }
1214                         }
1215                         else {
1216                                 ++curr;         /* skip the password question for non-native auth modes */
1217                         }
1218                 }
1219         }
1220
1221         if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
1222                 gid = getgid();
1223         } else {
1224                 gid = pw->pw_gid;
1225         }
1226
1227         if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
1228                 display_error("%s\n", _("failed to create directories"));
1229         }
1230                 
1231         activity = _("Reconfiguring Citadel server");
1232         progress(activity, 0, 5);
1233         sleep(1);                                       /* Let the message appear briefly */
1234
1235         /*
1236          * Create the administrator account.  It's ok if the command fails if this user already exists.
1237          */
1238         progress(activity, 1, 5);
1239         snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
1240         serv_puts(buf);
1241         progress(activity, 2, 5);
1242         serv_gets(buf);
1243         progress(activity, 3, 5);
1244
1245         /*
1246          * Assign the desired password and access level to the administrator account.
1247          */
1248         snprintf(buf, sizeof buf, "AGUP %s", admin_name);
1249         serv_puts(buf);
1250         progress(activity, 4, 5);
1251         serv_gets(buf);
1252         if (buf[0] == '2') {
1253                 int admin_flags = extract_int(&buf[4], 2);
1254                 int admin_times_called = extract_int(&buf[4], 3);
1255                 int admin_msgs_posted = extract_int(&buf[4], 4);
1256                 snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
1257                         admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
1258                 );
1259                 serv_puts(buf);
1260                 serv_gets(buf);
1261         }
1262         progress(activity, 5, 5);
1263
1264 #ifndef __CYGWIN__
1265         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1266         disable_other_mtas();   /* Offer to disable other MTAs */
1267         fixnss();               /* Check for the 'db' nss and offer to disable it */
1268 #endif
1269
1270         /*
1271          * Restart citserver
1272          */
1273         activity = _("Restarting Citadel server to apply changes");
1274         progress(activity, 0, 41);
1275
1276         serv_puts("TIME");
1277         serv_gets(buf);
1278         long original_start_time = extract_long(&buf[4], 3);
1279
1280         progress(activity, 1, 41);
1281         serv_puts("DOWN 1");
1282         progress(activity, 2, 41);
1283         serv_gets(buf);
1284         if (buf[0] != '2') {
1285                 display_error("%s\n", buf);
1286                 exit(6);
1287         }
1288
1289         close(serv_sock);
1290         serv_sock = (-1);
1291
1292         for (i=3; i<=6; ++i) {                                  /* wait for server to shut down */
1293                 progress(activity, i, 41);
1294                 sleep(1);
1295         }
1296
1297         for (i=7; ((i<=38) && (serv_sock < 0)) ; ++i) {         /* wait for server to start up */
1298                 progress(activity, i, 41);
1299                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1300                 sleep(1);
1301         }
1302
1303         progress(activity, 39, 41);
1304         serv_gets(buf);
1305
1306         progress(activity, 40, 41);
1307         serv_puts("TIME");
1308         serv_gets(buf);
1309         long new_start_time = extract_long(&buf[4], 3);
1310
1311         close(serv_sock);
1312         progress(activity, 41, 41);
1313
1314         if (    (original_start_time == new_start_time)
1315                 || (new_start_time <= 0)
1316         ) {
1317                 display_error("%s\n", _("Setup failed to restart Citadel server.  Please restart it manually."));
1318                 exit(7);
1319         }
1320
1321         exit(0);
1322         return 0;
1323 }