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