Major overhaul of the setup utility. It now expects the server to already be running...
[citadel.git] / citadel / utils / setup.c
1 /*
2  * Citadel setup utility
3  *
4  * Copyright (c) 1987-2012 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/utsname.h>
25 #include <sys/wait.h>
26 #include <signal.h>
27 #include <netdb.h>
28 #include <errno.h>
29 #include <limits.h>
30 #include <pwd.h>
31 #include <time.h>
32 #include <sys/socket.h>
33 #include <sys/un.h>
34 #include <assert.h>
35 #include <libcitadel.h>
36 #include "citadel.h"
37 #include "axdefs.h"
38 #include "sysdep.h"
39 #include "citadel_dirs.h"
40 #if HAVE_BACKTRACE
41 #include <execinfo.h>
42 #endif
43
44 #ifdef ENABLE_NLS
45 #ifdef HAVE_XLOCALE_H
46 #include <xlocale.h>
47 #endif
48 #include <libintl.h>
49 #include <locale.h>
50 #define _(string)       gettext(string)
51 #else
52 #define _(string)       (string)
53 #endif
54
55 #define UI_TEXT         0       /* Default setup type -- text only */
56 #define UI_DIALOG       2       /* Use the 'whiptail' or 'dialog' program */
57 #define UI_SILENT       3       /* Silent running, for use in scripts */
58
59 #define SERVICE_NAME    "citadel"
60 #define PROTO_NAME      "tcp"
61 #define NSSCONF         "/etc/nsswitch.conf"
62
63 typedef enum _SetupStep {
64         eCitadelHomeDir = 0,
65         eSysAdminName = 1,
66         eSysAdminPW = 2,
67         eUID = 3,
68         eIP_ADDR = 4,
69         eCTDL_Port = 5,
70         eAuthType = 6,
71         eLDAP_Host = 7,
72         eLDAP_Port = 8,
73         eLDAP_Base_DN = 9,
74         eLDAP_Bind_DN = 10,
75         eLDAP_Bind_PW = 11,
76         eMaxQuestions = 12
77 } eSteupStep;
78
79 ///"CREATE_XINETD_ENTRY";
80 /* Environment variables, don't translate! */
81 const char *EnvNames [eMaxQuestions] = {
82         "HOME_DIRECTORY",
83         "SYSADMIN_NAME",
84         "SYSADMIN_PW",
85         "CITADEL_UID",
86         "IP_ADDR",
87         "CITADEL_PORT",
88         "ENABLE_UNIX_AUTH",
89         "LDAP_HOST",
90         "LDAP_PORT",
91         "LDAP_BASE_DN",
92         "LDAP_BIND_DN",
93         "LDAP_BIND_PW"
94 };
95
96 int setup_type = (-1);
97 int using_web_installer = 0;
98 int enable_home = 1;
99 char admin_pass[SIZ];
100 char admin_cmd[SIZ];
101 int serv_sock = (-1) ;
102 char configs[NUM_CONFIGS][1024];
103
104 const char *setup_titles[eMaxQuestions];
105 const char *setup_text[eMaxQuestions];
106
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  * Print the stack frame for a backtrace
244  */
245 void cit_backtrace(void)
246 {
247 #ifdef HAVE_BACKTRACE
248         void *stack_frames[50];
249         size_t size, i;
250         char **strings;
251
252         size = backtrace(stack_frames, sizeof(stack_frames) / sizeof(void*));
253         strings = backtrace_symbols(stack_frames, size);
254         for (i = 0; i < size; i++) {
255                 if (strings != NULL)
256                         fprintf(stderr, "%s\n", strings[i]);
257                 else
258                         fprintf(stderr, "%p\n", stack_frames[i]);
259         }
260         free(strings);
261 #endif
262 }
263
264 int direction;
265
266
267
268 void title(const char *text)
269 {
270         if (setup_type == UI_TEXT) {
271                 printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<%s>\n", text);
272         }
273 }
274
275
276
277 int yesno(const char *question, int default_value)
278 {
279         int i = 0;
280         int answer = 0;
281         char buf[SIZ];
282
283         switch (setup_type) {
284
285         case UI_TEXT:
286                 do {
287                         printf("%s\n%s [%s] --> ",
288                                question,
289                                _("Yes/No"),
290                                ( default_value ? _("Yes") : _("No") )
291                         );
292                         if (fgets(buf, sizeof buf, stdin))
293                         {
294                                 answer = tolower(buf[0]);
295                                 if ((buf[0]==0) || (buf[0]==13) || (buf[0]==10)) {
296                                         answer = default_value;
297                                 }
298                                 else if (answer == 'y') {
299                                         answer = 1;
300                                 }
301                                 else if (answer == 'n') {
302                                         answer = 0;
303                                 }
304                         }
305                 } while ((answer < 0) || (answer > 1));
306                 break;
307
308         case UI_DIALOG:
309                 sprintf(buf, "exec %s %s --yesno '%s' 15 75",
310                         getenv("CTDL_DIALOG"),
311                         ( default_value ? "" : "--defaultno" ),
312                         question);
313                 i = system(buf);
314                 if (i == 0) {
315                         answer = 1;
316                 }
317                 else {
318                         answer = 0;
319                 }
320                 break;
321         case UI_SILENT:
322                 break;
323         }
324         return (answer);
325 }
326
327
328 void important_message(const char *title, const char *msgtext)
329 {
330         char buf[SIZ];
331
332         switch (setup_type) {
333
334         case UI_TEXT:
335                 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");
336                 printf("       %s \n\n%s\n\n", title, msgtext);
337                 printf("%s", _("Press return to continue..."));
338                 if (fgets(buf, sizeof buf, stdin));
339                 break;
340
341         case UI_DIALOG:
342                 sprintf(buf, "exec %s --msgbox '%s' 19 72",
343                         getenv("CTDL_DIALOG"),
344                         msgtext);
345                 int rv = system(buf);
346                 if (rv != 0) {
347                         fprintf(stderr, _("failed to run the dialog command\n"));
348                 }
349                 break;
350         case UI_SILENT:
351                 fprintf(stderr, "%s\n", msgtext);
352                 break;
353         }
354 }
355
356 void important_msgnum(int msgnum)
357 {
358         important_message(_("Important Message"), setup_text[msgnum]);
359 }
360
361 void display_error(char *error_message_format, ...)
362 {
363         StrBuf *Msg;
364         va_list arg_ptr;
365
366         Msg = NewStrBuf();
367         va_start(arg_ptr, error_message_format);
368         StrBufVAppendPrintf(Msg, error_message_format, arg_ptr);
369         va_end(arg_ptr);
370
371         important_message(_("Error"), ChrPtr(Msg));
372         FreeStrBuf(&Msg);
373 }
374
375 void progress(char *text, long int curr, long int cmax)
376 {
377         static long dots_printed = 0L;
378         long a = 0;
379         static FILE *fp = NULL;
380         char buf[SIZ];
381
382         switch (setup_type) {
383
384         case UI_TEXT:
385                 if (curr == 0) {
386                         printf("%s\n", text);
387                         printf("....................................................");
388                         printf("..........................\r");
389                         dots_printed = 0;
390                 } else if (curr == cmax) {
391                         printf("\r%79s\n", "");
392                 } else {
393                         a = (curr * 100) / cmax;
394                         a = a * 78;
395                         a = a / 100;
396                         while (dots_printed < a) {
397                                 printf("*");
398                                 ++dots_printed;
399                         }
400                 }
401                 fflush(stdout);
402                 break;
403
404         case UI_DIALOG:
405                 if (curr == 0) {
406                         sprintf(buf, "exec %s --gauge '%s' 7 72 0",
407                                 getenv("CTDL_DIALOG"),
408                                 text);
409                         fp = popen(buf, "w");
410                         if (fp != NULL) {
411                                 fprintf(fp, "0\n");
412                                 fflush(fp);
413                         }
414                 } 
415                 else if (curr == cmax) {
416                         if (fp != NULL) {
417                                 fprintf(fp, "100\n");
418                                 pclose(fp);
419                                 fp = NULL;
420                         }
421                 }
422                 else {
423                         a = (curr * 100) / cmax;
424                         if (fp != NULL) {
425                                 fprintf(fp, "%ld\n", a);
426                                 fflush(fp);
427                         }
428                 }
429                 break;
430         case UI_SILENT:
431                 break;
432
433         default:
434                 assert(1==0);   /* If we got here then the developer is a moron */
435         }
436 }
437
438
439
440 int uds_connectsock(char *sockpath)
441 {
442         int s;
443         struct sockaddr_un addr;
444
445         memset(&addr, 0, sizeof(addr));
446         addr.sun_family = AF_UNIX;
447         strncpy(addr.sun_path, sockpath, sizeof addr.sun_path);
448
449         s = socket(AF_UNIX, SOCK_STREAM, 0);
450         if (s < 0) {
451                 return(-1);
452         }
453
454         if (connect(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
455                 close(s);
456                 return(-1);
457         }
458
459         return s;
460 }
461
462
463 /*
464  * input binary data from socket
465  */
466 void serv_read(char *buf, int bytes)
467 {
468         int len, rlen;
469
470         len = 0;
471         while (len < bytes) {
472                 rlen = read(serv_sock, &buf[len], bytes - len);
473                 if (rlen < 1) {
474                         return;
475                 }
476                 len = len + rlen;
477         }
478 }
479
480
481 /*
482  * send binary to server
483  */
484 void serv_write(char *buf, int nbytes)
485 {
486         int bytes_written = 0;
487         int retval;
488         while (bytes_written < nbytes) {
489                 retval = write(serv_sock, &buf[bytes_written], nbytes - bytes_written);
490                 if (retval < 1) {
491                         return;
492                 }
493                 bytes_written = bytes_written + retval;
494         }
495 }
496
497
498
499 /*
500  * input string from socket - implemented in terms of serv_read()
501  */
502 void serv_gets(char *buf)
503 {
504         int i;
505
506         /* Read one character at a time.
507          */
508         for (i = 0;; i++) {
509                 serv_read(&buf[i], 1);
510                 if (buf[i] == '\n' || i == (SIZ-1))
511                         break;
512         }
513
514         /* If we got a long line, discard characters until the newline.
515          */
516         if (i == (SIZ-1)) {
517                 while (buf[i] != '\n') {
518                         serv_read(&buf[i], 1);
519                 }
520         }
521
522         /* Strip all trailing nonprintables (crlf)
523          */
524         buf[i] = 0;
525 }
526
527
528 /*
529  * send line to server - implemented in terms of serv_write()
530  */
531 void serv_puts(char *buf)
532 {
533         serv_write(buf, strlen(buf));
534         serv_write("\n", 1);
535 }
536
537
538 /*
539  * On systems which use xinetd, see if we can offer to install Citadel as
540  * the default telnet target.
541  */
542 void check_xinetd_entry(void) {
543         char *filename = "/etc/xinetd.d/telnet";
544         FILE *fp;
545         char buf[SIZ];
546         int already_citadel = 0;
547         int rv;
548
549         fp = fopen(filename, "r+");
550         if (fp == NULL) return;         /* Not there.  Oh well... */
551
552         while (fgets(buf, sizeof buf, fp) != NULL) {
553                 if (strstr(buf, "/citadel") != NULL) {
554                         already_citadel = 1;
555                 }
556         }
557         fclose(fp);
558         if (already_citadel) return;    /* Already set up this way. */
559
560         /* Otherwise, prompt the user to create an entry. */
561         if (getenv("CREATE_XINETD_ENTRY") != NULL) {
562                 if (strcasecmp(getenv("CREATE_XINETD_ENTRY"), "yes")) {
563                         return;
564                 }
565         }
566         else {
567                 snprintf(buf, sizeof buf,
568                          _("Setup can configure the \"xinetd\" service to automatically\n"
569                            "connect incoming telnet sessions to Citadel, bypassing the\n"
570                            "host system login: prompt.  Would you like to do this?\n"
571                          )
572                 );
573                 if (yesno(buf, 1) == 0) {
574                         return;
575                 }
576         }
577
578         fp = fopen(filename, "w");
579         fprintf(fp,
580                 "# description: telnet service for Citadel users\n"
581                 "service telnet\n"
582                 "{\n"
583                 "       disable = no\n"
584                 "       flags           = REUSE\n"
585                 "       socket_type     = stream\n"
586                 "       wait            = no\n"
587                 "       user            = root\n"
588                 "       server          = /usr/sbin/in.telnetd\n"
589                 "       server_args     = -h -L %s/citadel\n"
590                 "       log_on_failure  += USERID\n"
591                 "}\n",
592                 ctdl_bin_dir);
593         fclose(fp);
594
595         /* Now try to restart the service */
596         rv = system("/etc/init.d/xinetd restart >/dev/null 2>&1");
597         if (rv != 0) {
598                 display_error(_("failed to restart xinetd.\n"));
599         }
600 }
601
602
603
604 /*
605  * Offer to disable other MTA's
606  */
607 void disable_other_mta(const char *mta) {
608         char buf[SIZ];
609         FILE *fp;
610         int lines = 0;
611         int rv;
612
613         sprintf(buf,
614                 "/bin/ls -l /etc/rc*.d/S*%s 2>/dev/null; "
615                 "/bin/ls -l /etc/rc.d/rc*.d/S*%s 2>/dev/null",
616                 mta, mta
617         );
618         fp = popen(buf, "r");
619         if (fp == NULL) return;
620
621         while (fgets(buf, sizeof buf, fp) != NULL) {
622                 ++lines;
623         }
624         fclose(fp);
625         if (lines == 0) return;         /* Nothing to do. */
626
627         /* Offer to replace other MTA with the vastly superior Citadel :)  */
628
629         snprintf(buf, sizeof buf,
630                  "%s \"%s\" %s%s%s%s%s%s%s", 
631                  _("You appear to have the "), 
632                  mta, 
633                  _(" email program\n"
634                    "running on your system.  If you want Citadel mail\n"
635                    "connected with "), 
636                  mta,
637                  _(" you will have to manually integrate\n"
638                    "them.  It is preferable to disable "), 
639                  mta,
640                  _(", and use Citadel's\n"
641                    "SMTP, POP3, and IMAP services.\n\n"
642                    "May we disable "), 
643                  mta, 
644                  _("so that Citadel has access to ports\n"
645                    "25, 110, and 143?\n")
646                 );
647         if (yesno(buf, 1) == 0) {
648                 return;
649         }
650         
651
652         sprintf(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);
653         rv = system(buf);
654         if (rv != 0)
655                 display_error("%s %s.\n", _("failed to disable other mta"), mta);
656
657         sprintf(buf, "/etc/init.d/%s stop >/dev/null 2>&1", mta);
658         rv = system(buf);
659         if (rv != 0)
660                 display_error(" %s.\n", _("failed to disable other mta"), mta);
661 }
662
663 const char *other_mtas[] = {
664         "courier-authdaemon",
665         "courier-imap",
666         "courier-imap-ssl",
667         "courier-pop",
668         "courier-pop3",
669         "courier-pop3d",
670         "cyrmaster",
671         "cyrus",
672         "dovecot",
673         "exim",
674         "exim4",
675         "imapd",
676         "mta",
677         "pop3d",
678         "popd",
679         "postfix",
680         "qmail",
681         "saslauthd",
682         "sendmail",
683         "vmailmgrd",
684         ""
685 };
686
687 void disable_other_mtas(void)
688 {
689         int i = 0;
690         if ((getenv("ACT_AS_MTA") == NULL) || 
691             (getenv("ACT_AS_MTA") &&
692              strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
693                 /* Offer to disable other MTA's on the system. */
694                 while (!IsEmptyStr(other_mtas[i]))
695                 {
696                         disable_other_mta(other_mtas[i]);
697                         i++;
698                 }
699         }
700 }
701
702 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
703 {
704         char buf[SIZ] = "";
705         char setupmsg[SIZ];
706         char dialog_result[PATH_MAX];
707         FILE *fp = NULL;
708         int rv;
709
710         strcpy(setupmsg, "");
711
712         switch (setup_type) {
713         case UI_TEXT:
714                 title(prompt_title);
715                 printf("\n%s\n", prompt_text);
716                 printf("%s\n%s\n", _("This is currently set to:"), Target);
717                 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
718                 if (fgets(buf, sizeof buf, stdin)){
719                         buf[strlen(buf) - 1] = 0;
720                 }
721                 if (!IsEmptyStr(buf))
722                         strcpy(Target, buf);
723                 break;
724
725         case UI_DIALOG:
726                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
727                 sprintf(buf, "exec %s --nocancel --inputbox '%s' 19 72 '%s' 2>%s",
728                         getenv("CTDL_DIALOG"),
729                         prompt_text,
730                         Target,
731                         dialog_result);
732                 rv = system(buf);
733                 if (rv != 0) {
734                         fprintf(stderr, "failed to run whiptail or dialog\n");
735                 }
736                 
737                 fp = fopen(dialog_result, "r");
738                 if (fp != NULL) {
739                         if (fgets(Target, sizeof buf, fp)) {
740                                 if (Target[strlen(Target)-1] == 10) {
741                                         Target[strlen(Target)-1] = 0;
742                                 }
743                         }
744                         fclose(fp);
745                         unlink(dialog_result);
746                 }
747                 break;
748         case UI_SILENT:
749                 if (*DefValue != '\0')
750                         strcpy(Target, DefValue);
751                 break;
752         }
753 }
754
755 void set_bool_val(int msgpos, int *ip, char *DefValue) 
756 {
757         title(setup_titles[msgpos]);
758         *ip = yesno(setup_text[msgpos], *ip);
759 }
760
761 void set_str_val(int msgpos, char *Target, char *DefValue) 
762 {
763         strprompt(setup_titles[msgpos], 
764                   setup_text[msgpos], 
765                   Target, 
766                   DefValue
767         );
768 }
769
770 /* like set_str_val() but make sure we ended up with a numeric value */
771 void set_int_val(int msgpos, char *target, char *DefValue)
772 {
773         while(1) {
774                 set_str_val(msgpos, target, DefValue);
775                 if (!strcmp(target, "0")) return;
776                 if (atoi(target) != 0) return;
777         }
778 }
779
780
781 void edit_value(int curr)
782 {
783         int i;
784         struct passwd *pw;
785         char ctdluidname[256];
786         char *Value = NULL;
787
788         if (setup_type == UI_SILENT)
789         {
790                 Value = getenv(EnvNames[curr]);
791         }
792         if (Value == NULL) {
793                 Value = "";
794         }
795
796         switch (curr) {
797
798         case eSysAdminName:
799                 set_str_val(curr, configs[13], Value);
800                 break;
801
802         case eSysAdminPW:
803                 set_str_val(curr, admin_pass, Value);
804                 break;
805         
806         case eUID:
807                 if (setup_type == UI_SILENT)
808                 {               
809                         if (Value) {
810                                 sprintf(configs[69], "%d", atoi(Value));
811                         }                                       
812                 }
813                 else
814                 {
815 #ifdef __CYGWIN__
816                         strcpy(configs[69], "0");       /* work-around for Windows */
817 #else
818                         i = atoi(configs[69]);
819                         pw = getpwuid(i);
820                         if (pw == NULL) {
821                                 set_int_val(curr, configs[69], Value);
822                                 sprintf(configs[69], "%d", i);
823                         }
824                         else {
825                                 strcpy(ctdluidname, pw->pw_name);
826                                 set_str_val(curr, ctdluidname, Value);
827                                 pw = getpwnam(ctdluidname);
828                                 if (pw != NULL) {
829                                         sprintf(configs[69], "%d", pw->pw_uid);
830                                 }
831                                 else if (atoi(ctdluidname) > 0) {
832                                         sprintf(configs[69], "%d", atoi(ctdluidname));
833                                 }
834                         }
835 #endif
836                 }
837                 break;
838
839         case eIP_ADDR:
840                 set_str_val(curr, configs[37], Value);
841                 break;
842
843         case eCTDL_Port:
844                 set_int_val(curr, configs[68], Value);
845                 break;
846
847         case eAuthType:
848                 if (setup_type == UI_SILENT)
849                 {
850                         const char *auth;
851                         //config.c_auth_mode = AUTHMODE_NATIVE;
852                         auth = Value;
853                         if (auth != NULL)
854                         {
855                                 if ((strcasecmp(auth, "yes") == 0) ||
856                                     (strcasecmp(auth, "host") == 0))
857                                 {
858                                         //config.c_auth_mode = AUTHMODE_HOST;
859                                 }
860                                 else if (strcasecmp(auth, "ldap") == 0){
861                                         //config.c_auth_mode = AUTHMODE_LDAP;
862                                 }
863                                 else if ((strcasecmp(auth, "ldap_ad") == 0) ||
864                                          (strcasecmp(auth, "active directory") == 0)){
865                                         //config.c_auth_mode = AUTHMODE_LDAP_AD;
866                                 }
867                         }
868                 }
869                 else {
870                         set_int_val(curr, configs[52], Value);
871                 }
872                 break;
873
874         case eLDAP_Host:
875                 if (IsEmptyStr(configs[32])) {
876                         strcpy(configs[32], "localhost");
877                 }
878                 set_str_val(curr, configs[32], Value);
879                 break;
880
881         case eLDAP_Port:
882                 if (atoi(configs[33]) == 0) {
883                         strcpy(configs[33], "389");
884                 }
885                 set_int_val(curr, configs[33], Value);
886                 break;
887
888         case eLDAP_Base_DN:
889                 set_str_val(curr, configs[34], Value);
890                 break;
891
892         case eLDAP_Bind_DN:
893                 set_str_val(curr, configs[35], Value);
894                 break;
895
896         case eLDAP_Bind_PW:
897                 set_str_val(curr, configs[36], Value);
898                 break;
899
900         }
901 }
902
903
904
905 /*
906  * Figure out what type of user interface we're going to use
907  */
908 int discover_ui(void)
909 {
910
911         /* Use "whiptail" or "dialog" if we have it */
912         if (getenv("CTDL_DIALOG") != NULL) {
913                 return UI_DIALOG;
914         }
915                 
916         return UI_TEXT;
917 }
918
919
920
921 /*
922  * Strip "db" entries out of /etc/nsswitch.conf
923  */
924 void fixnss(void) {
925         FILE *fp_read;
926         int fd_write;
927         char buf[256];
928         char buf_nc[256];
929         char question[512];
930         int i;
931         int file_changed = 0;
932         char new_filename[64];
933         int rv;
934
935         fp_read = fopen(NSSCONF, "r");
936         if (fp_read == NULL) {
937                 return;
938         }
939
940         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
941         fd_write = mkstemp(new_filename);
942         if (fd_write < 0) {
943                 fclose(fp_read);
944                 return;
945         }
946
947         while (fgets(buf, sizeof buf, fp_read) != NULL) {
948                 strcpy(buf_nc, buf);
949                 for (i=0; i<strlen(buf_nc); ++i) {
950                         if (buf_nc[i] == '#') {
951                                 buf_nc[i] = 0;
952                         }
953                 }
954                 for (i=0; i<strlen(buf_nc); ++i) {
955                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
956                                 if (i > 0) {
957                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
958                                                 file_changed = 1;
959                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
960                                                 strcpy(&buf[i], &buf[i+2]);
961                                                 if (buf[i]==32) {
962                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
963                                                         strcpy(&buf[i], &buf[i+1]);
964                                                 }
965                                         }
966                                 }
967                         }
968                 }
969                 if (write(fd_write, buf, strlen(buf)) != strlen(buf)) {
970                         fclose(fp_read);
971                         close(fd_write);
972                         unlink(new_filename);
973                         return;
974                 }
975         }
976
977         fclose(fp_read);
978         
979         if (!file_changed) {
980                 unlink(new_filename);
981                 return;
982         }
983
984         snprintf(question, sizeof question,
985                  _(
986                          "\n"
987                          "/etc/nsswitch.conf is configured to use the 'db' module for\n"
988                          "one or more services.  This is not necessary on most systems,\n"
989                          "and it is known to crash the Citadel server when delivering\n"
990                          "mail to the Internet.\n"
991                          "\n"
992                          "Do you want this module to be automatically disabled?\n"
993                          "\n"
994                          )
995         );
996
997         if (yesno(question, 1)) {
998                 sprintf(buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
999                 rv = system(buf);
1000                 if (rv != 0) {
1001                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
1002                 }
1003                 chmod(NSSCONF, 0644);
1004         }
1005         unlink(new_filename);
1006 }
1007
1008
1009
1010 #if 0
1011                                 important_message(_("Setup finished"),
1012                                                   _("Setup of the Citadel server is complete.\n"
1013                                                     "If you will be using WebCit, please run its\n"
1014                                                     "setup program now; otherwise, run './citadel'\n"
1015                                                     "to log in.\n"));
1016                         important_message(_("Setup failed"),
1017                                           _("Setup is finished, but the Citadel server failed to start.\n"
1018                                             "Go back and check your configuration.\n")
1019                 important_message(_("Setup finished"),
1020                                   _("Setup is finished.  You may now start the server."));
1021 #endif
1022
1023
1024
1025 #define GetDefaultVALINT(CFGNAME, DEFL) GetDefaultValInt(&config.CFGNAME, "CITADEL_"#CFGNAME, DEFL)
1026 void GetDefaultValInt(int *WhereTo, const char *VarName, int DefVal)
1027 {
1028         const char *ch;
1029         if (*WhereTo == 0) *WhereTo = DefVal;
1030
1031         if ((setup_type == UI_SILENT) &&
1032             (ch = getenv(VarName), ch != NULL))
1033         {
1034                 *WhereTo = atoi(ch);
1035         }
1036 }
1037 #define GetDefaultVALCHAR(CFGNAME, DEFL) GetDefaultValChar(&config.CFGNAME, "CITADEL_"#CFGNAME, DEFL)
1038 void GetDefaultValChar(char *WhereTo, const char *VarName, char DefVal)
1039 {
1040         const char *ch;
1041         if (*WhereTo == 0) *WhereTo = DefVal;
1042
1043         if ((setup_type == UI_SILENT) &&
1044             (ch = getenv(VarName), ch != NULL))
1045         {
1046                 *WhereTo = atoi(ch);
1047         }
1048 }
1049 #define GetDefaultVALSTR(CFGNAME, DEFL) GetDefaultValStr(&config.CFGNAME[0], sizeof(config.CFGNAME), "CITADEL_"#CFGNAME, DEFL)
1050 void GetDefaultValStr(char *WhereTo, size_t nMax, const char *VarName, const char *DefVal)
1051 {
1052         const char *ch;
1053         if (*WhereTo == '\0') 
1054                 safestrncpy(WhereTo, DefVal, nMax);
1055
1056         if ((setup_type == UI_SILENT) &&
1057             (ch = getenv(VarName), ch != NULL))
1058         {
1059                 safestrncpy(WhereTo, ch, nMax);
1060         }
1061 }
1062
1063
1064 void set_default_values(void)
1065 {
1066 #if 0
1067         struct passwd *pw;
1068         struct utsname my_utsname;
1069         struct hostent *he;
1070
1071         /* Determine our host name, in case we need to use it as a default */
1072         uname(&my_utsname);
1073
1074         /* set some sample/default values in place of blanks... */
1075         GetDefaultVALSTR(c_nodename, my_utsname.nodename);
1076         strtok(config.c_nodename, ".");
1077         if (IsEmptyStr(config.c_fqdn) ) {
1078                 if ((he = gethostbyname(my_utsname.nodename)) != NULL) {
1079                         safestrncpy(config.c_fqdn, he->h_name, sizeof config.c_fqdn);
1080                 } else {
1081                         safestrncpy(config.c_fqdn, my_utsname.nodename, sizeof config.c_fqdn);
1082                 }
1083         }
1084         GetDefaultVALSTR(c_humannode, _("My System"));
1085         GetDefaultVALSTR(c_phonenum, _("US 800 555 1212"));
1086
1087         GetDefaultVALCHAR(c_initax, 4);
1088
1089         GetDefaultVALSTR(c_moreprompt, "<more>");
1090         GetDefaultVALSTR(c_twitroom, "Trashcan");
1091         GetDefaultVALSTR(c_baseroom, BASEROOM);
1092         GetDefaultVALSTR(c_aideroom, "Aide");
1093         GetDefaultVALINT(c_port_number, 504);
1094         
1095         GetDefaultVALINT(c_sleeping, 900);
1096
1097         if (config.c_ctdluid == 0) {
1098                 pw = getpwnam("citadel");
1099                 if (pw != NULL) {
1100                         config.c_ctdluid = pw->pw_uid;
1101                 }
1102         }
1103         if (config.c_ctdluid == 0) {
1104                 pw = getpwnam("bbs");
1105                 if (pw != NULL) {
1106                         config.c_ctdluid = pw->pw_uid;
1107                 }
1108         }
1109         if (config.c_ctdluid == 0) {
1110                 pw = getpwnam("guest");
1111                 if (pw != NULL) {
1112                         config.c_ctdluid = pw->pw_uid;
1113                 }
1114         }
1115         if (config.c_createax == 0) {
1116                 config.c_createax = 3;
1117         }
1118         /*
1119          * Negative values for maxsessions are not allowed.
1120          */
1121         if (config.c_maxsessions < 0) {
1122                 config.c_maxsessions = 0;
1123         }
1124         /* We need a system default message expiry policy, because this is
1125          * the top level and there's no 'higher' policy to fall back on.
1126          * By default, do not expire messages at all.
1127          */
1128         if (config.c_ep.expire_mode == 0) {
1129                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1130                 config.c_ep.expire_value = 0;
1131         }
1132
1133         /*
1134          * Default port numbers for various services
1135          */
1136         GetDefaultVALINT(c_smtp_port, 25);
1137         GetDefaultVALINT(c_pop3_port, 110);
1138         GetDefaultVALINT(c_imap_port, 143);
1139         GetDefaultVALINT(c_msa_port, 587);
1140         GetDefaultVALINT(c_smtps_port, 465);
1141         GetDefaultVALINT(c_pop3s_port, 995);
1142         GetDefaultVALINT(c_imaps_port, 993);
1143         GetDefaultVALINT(c_pftcpdict_port, -1);
1144         GetDefaultVALINT(c_managesieve_port, 2020);
1145         GetDefaultVALINT(c_xmpp_c2s_port, 5222);
1146         GetDefaultVALINT(c_xmpp_s2s_port, 5269);
1147 #endif
1148 }
1149
1150
1151
1152 int main(int argc, char *argv[])
1153 {
1154         int a, i;
1155         int curr;
1156         char buf[1024]; 
1157         char aaa[128];
1158         int info_only = 0;
1159         int relh=0;
1160         int home=0;
1161         char relhome[PATH_MAX]="";
1162         char ctdldir[PATH_MAX]=CTDLDIR;
1163         struct passwd *pw;
1164         gid_t gid;
1165         char *activity = NULL;
1166         
1167         /* set an invalid setup type */
1168         setup_type = (-1);
1169
1170         /* Check to see if we're running the web installer */
1171         if (getenv("CITADEL_INSTALLER") != NULL) {
1172                 using_web_installer = 1;
1173         }
1174
1175         /* parse command line args */
1176         for (a = 0; a < argc; ++a) {
1177                 if (!strncmp(argv[a], "-u", 2)) {
1178                         strcpy(aaa, argv[a]);
1179                         strcpy(aaa, &aaa[2]);
1180                         setup_type = atoi(aaa);
1181                 }
1182                 else if (!strcmp(argv[a], "-i")) {
1183                         info_only = 1;
1184                 }
1185                 else if (!strcmp(argv[a], "-q")) {
1186                         setup_type = UI_SILENT;
1187                 }
1188                 else if (!strncmp(argv[a], "-h", 2)) {
1189                         relh=argv[a][2]!='/';
1190                         if (!relh) {
1191                                 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
1192                         } else {
1193                                 safestrncpy(relhome, &argv[a][2], sizeof relhome);
1194                         }
1195                         home = 1;
1196                 }
1197
1198         }
1199
1200         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1201         SetTitles();
1202
1203         /* If a setup type was not specified, try to determine automatically
1204          * the best one to use out of all available types.
1205          */
1206         if (setup_type < 0) {
1207                 setup_type = discover_ui();
1208         }
1209         if (info_only == 1) {
1210                 important_message(_("Citadel Setup"), CITADEL);
1211                 exit(0);
1212         }
1213
1214         enable_home = ( relh | home );
1215
1216         if (chdir(ctdl_run_dir) != 0) {
1217                 display_error(_("Citadel Setup"), 
1218                               "%s: [%s]\n", 
1219                               _("The directory you specified does not exist"), 
1220                               ctdl_run_dir);
1221                 exit(errno);
1222         }
1223
1224
1225         /*
1226          * Connect to the running Citadel server.
1227          */
1228         serv_sock = uds_connectsock(file_citadel_admin_socket);
1229         if (serv_sock < 0) { 
1230                 display_error(
1231                         "%s\n", 
1232                         _("Setup could not connect to a running Citadel server.")
1233                 );
1234                 exit(1);
1235         }
1236
1237         /*
1238          * read the server greeting
1239          */
1240         serv_gets(buf);
1241         if (buf[0] != '2') {
1242                 display_error("%s\n", buf);
1243                 exit(2);
1244         }
1245
1246         /*
1247          * Are we connected to the correct Citadel server?
1248          */
1249         serv_puts("INFO");
1250         serv_gets(buf);
1251         if (buf[0] != '1') {
1252                 display_error("%s\n", buf);
1253                 exit(3);
1254         }
1255         a = 0;
1256         while (serv_gets(buf), strcmp(buf, "000")) {
1257                 if (a == 5) {
1258                         if (atoi(buf) != REV_LEVEL) {
1259                                 display_error("%s\n",
1260                                 _("Your setup program and Citadel server are from different versions.")
1261                                 );
1262                                 exit(4);
1263                         }
1264                 }
1265                 ++a;
1266         }
1267
1268         /*
1269          * Load the server's configuration
1270          */
1271         serv_puts("CONF GET");
1272         serv_gets(buf);
1273         if (buf[0] != '1') {
1274                 display_error("%s\n", buf);
1275                 exit(5);
1276         }
1277         memset(configs, 0, sizeof configs);
1278         a = 0;
1279         while (serv_gets(buf), strcmp(buf, "000")) {
1280                 if (a < NUM_CONFIGS) {
1281                         safestrncpy(configs[a], buf, sizeof(configs[a]));
1282                 }
1283                 ++a;
1284         }
1285
1286
1287         /*
1288          * Now begin.
1289          */
1290
1291         /* _("Citadel Setup"),  */
1292
1293         if (setup_type == UI_TEXT) {
1294                 printf("\n\n\n"
1295                         "              *** %s ***\n\n",
1296                         _("Citadel setup program")
1297                 );
1298         }
1299
1300         if (setup_type == UI_DIALOG) {
1301                 system("clear 2>/dev/null");
1302         }
1303
1304         set_default_values();
1305
1306         /* Go through a series of dialogs prompting for config info */
1307         for (curr = 1; curr < eMaxQuestions; ++curr) {
1308                 edit_value(curr);
1309                 if (    (curr == 6)
1310                         && (atoi(configs[52]) != AUTHMODE_LDAP)
1311                         && (atoi(configs[52]) != AUTHMODE_LDAP_AD)
1312                 ) {
1313                         curr += 5;      /* skip LDAP questions if we're not authenticating against LDAP */
1314                 }
1315         }
1316
1317         //config.c_setup_level = REV_LEVEL;
1318
1319         if ((pw = getpwuid(atoi(configs[69]))) == NULL) {
1320                 gid = getgid();
1321         } else {
1322                 gid = pw->pw_gid;
1323         }
1324
1325         create_run_directories(atoi(configs[69]), gid);
1326
1327         activity = _("Reconfiguring Citadel server");
1328         progress(activity, 0, NUM_CONFIGS+1);
1329         sleep(1);                                       /* Let the message appear briefly */
1330         serv_puts("CONF SET");
1331         serv_gets(buf);
1332         if (buf[0] == '4') {
1333                 for (i=0; i<NUM_CONFIGS; ++i) {
1334                         progress(activity, i+1, NUM_CONFIGS+1);
1335                         serv_puts(configs[i]);
1336                 }
1337                 serv_puts("000");
1338         }
1339         sleep(1);                                       /* Let the message appear briefly */
1340         progress(activity, NUM_CONFIGS+1, NUM_CONFIGS+1);
1341
1342 #ifndef __CYGWIN__
1343         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1344         disable_other_mtas();   /* Offer to disable other MTAs */
1345         fixnss();               /* Check for the 'db' nss and offer to disable it */
1346 #endif
1347
1348         activity = _("Setting file permissions");
1349         progress(activity, 0, 2);
1350         //chown(file_citadel_config, config.c_ctdluid, gid);
1351         progress(activity, 1, 2);
1352         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1353         progress(activity, 2, 2);
1354
1355         /*
1356          * Restart citserver
1357          */
1358         activity = _("Restarting Citadel server to apply changes");
1359         progress(activity, 0, 41);
1360
1361         serv_puts("TIME");
1362         serv_gets(buf);
1363         long original_start_time = extract_long(&buf[4], 3);
1364
1365         progress(activity, 1, 41);
1366         serv_puts("DOWN 1");
1367         progress(activity, 2, 41);
1368         serv_gets(buf);
1369         if (buf[0] != '2') {
1370                 display_error("%s\n", buf);
1371                 exit(6);
1372         }
1373
1374         close(serv_sock);
1375         serv_sock = (-1);
1376
1377         for (i=3; i<=6; ++i) {
1378                 progress(activity, i, 41);
1379                 sleep(1);
1380         }
1381
1382         for (i=7; ((i<=38) && (serv_sock < 0)) ; ++i) {
1383                 progress(activity, i, 41);
1384                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1385                 sleep(1);
1386         }
1387
1388         progress(activity, 39, 41);
1389         serv_gets(buf);
1390
1391         progress(activity, 40, 41);
1392         serv_puts("TIME");
1393         serv_gets(buf);
1394         long new_start_time = extract_long(&buf[4], 3);
1395
1396         close(serv_sock);
1397         progress(activity, 41, 41);
1398
1399         if (original_start_time == new_start_time) {
1400                 display_error("%s\n",
1401                         _("Setup failed to restart Citadel server.  Please restart it manually.")
1402                 );
1403                 exit(7);
1404         }
1405
1406         exit(0);
1407         return 0;
1408 }