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