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