41bed61326e04ca56a3fa92086606bd889c3ad41
[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
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; i<strlen(buf_nc); ++i) {
953                         if (buf_nc[i] == '#') {
954                                 buf_nc[i] = 0;
955                         }
956                 }
957                 for (i=0; i<strlen(buf_nc); ++i) {
958                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
959                                 if (i > 0) {
960                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
961                                                 file_changed = 1;
962                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
963                                                 strcpy(&buf[i], &buf[i+2]);
964                                                 if (buf[i]==32) {
965                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
966                                                         strcpy(&buf[i], &buf[i+1]);
967                                                 }
968                                         }
969                                 }
970                         }
971                 }
972                 if (write(fd_write, buf, strlen(buf)) != strlen(buf)) {
973                         fclose(fp_read);
974                         close(fd_write);
975                         unlink(new_filename);
976                         return;
977                 }
978         }
979
980         fclose(fp_read);
981         
982         if (!file_changed) {
983                 unlink(new_filename);
984                 return;
985         }
986
987         snprintf(question, sizeof question,
988                  _(
989                          "\n"
990                          "/etc/nsswitch.conf is configured to use the 'db' module for\n"
991                          "one or more services.  This is not necessary on most systems,\n"
992                          "and it is known to crash the Citadel server when delivering\n"
993                          "mail to the Internet.\n"
994                          "\n"
995                          "Do you want this module to be automatically disabled?\n"
996                          "\n"
997                          )
998         );
999
1000         if (yesno(question, 1)) {
1001                 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
1002                 rv = system(buf);
1003                 if (rv != 0) {
1004                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
1005                 }
1006                 chmod(NSSCONF, 0644);
1007         }
1008         unlink(new_filename);
1009 }
1010
1011
1012
1013 #if 0
1014                                 important_message(_("Setup finished"),
1015                                                   _("Setup of the Citadel server is complete.\n"
1016                                                     "If you will be using WebCit, please run its\n"
1017                                                     "setup program now; otherwise, run './citadel'\n"
1018                                                     "to log in.\n"));
1019                         important_message(_("Setup failed"),
1020                                           _("Setup is finished, but the Citadel server failed to start.\n"
1021                                             "Go back and check your configuration.\n")
1022                 important_message(_("Setup finished"),
1023                                   _("Setup is finished.  You may now start the server."));
1024 #endif
1025
1026
1027
1028 #define GetDefaultVALINT(CFGNAME, DEFL) GetDefaultValInt(&config.CFGNAME, "CITADEL_"#CFGNAME, DEFL)
1029 void GetDefaultValInt(int *WhereTo, const char *VarName, int DefVal)
1030 {
1031         const char *ch;
1032         if (*WhereTo == 0) *WhereTo = DefVal;
1033
1034         if ((setup_type == UI_SILENT) &&
1035             (ch = getenv(VarName), ch != NULL))
1036         {
1037                 *WhereTo = atoi(ch);
1038         }
1039 }
1040 #define GetDefaultVALCHAR(CFGNAME, DEFL) GetDefaultValChar(&config.CFGNAME, "CITADEL_"#CFGNAME, DEFL)
1041 void GetDefaultValChar(char *WhereTo, const char *VarName, char DefVal)
1042 {
1043         const char *ch;
1044         if (*WhereTo == 0) *WhereTo = DefVal;
1045
1046         if ((setup_type == UI_SILENT) &&
1047             (ch = getenv(VarName), ch != NULL))
1048         {
1049                 *WhereTo = atoi(ch);
1050         }
1051 }
1052 #define GetDefaultVALSTR(CFGNAME, DEFL) GetDefaultValStr(&config.CFGNAME[0], sizeof(config.CFGNAME), "CITADEL_"#CFGNAME, DEFL)
1053 void GetDefaultValStr(char *WhereTo, size_t nMax, const char *VarName, const char *DefVal)
1054 {
1055         const char *ch;
1056         if (*WhereTo == '\0') 
1057                 safestrncpy(WhereTo, DefVal, nMax);
1058
1059         if ((setup_type == UI_SILENT) &&
1060             (ch = getenv(VarName), ch != NULL))
1061         {
1062                 safestrncpy(WhereTo, ch, nMax);
1063         }
1064 }
1065
1066
1067 void set_default_values(void)
1068 {
1069 #if 0
1070         struct passwd *pw;
1071         struct utsname my_utsname;
1072         struct hostent *he;
1073
1074         /* Determine our host name, in case we need to use it as a default */
1075         uname(&my_utsname);
1076
1077         /* set some sample/default values in place of blanks... */
1078         GetDefaultVALSTR(c_nodename, my_utsname.nodename);
1079         strtok(config.c_nodename, ".");
1080         if (IsEmptyStr(config.c_fqdn) ) {
1081                 if ((he = gethostbyname(my_utsname.nodename)) != NULL) {
1082                         safestrncpy(config.c_fqdn, he->h_name, sizeof config.c_fqdn);
1083                 } else {
1084                         safestrncpy(config.c_fqdn, my_utsname.nodename, sizeof config.c_fqdn);
1085                 }
1086         }
1087         GetDefaultVALSTR(c_humannode, _("My System"));
1088         GetDefaultVALSTR(c_phonenum, _("US 800 555 1212"));
1089
1090         GetDefaultVALCHAR(c_initax, 4);
1091
1092         GetDefaultVALSTR(c_moreprompt, "<more>");
1093         GetDefaultVALSTR(c_twitroom, "Trashcan");
1094         GetDefaultVALSTR(c_baseroom, BASEROOM);
1095         GetDefaultVALSTR(c_aideroom, "Aide");
1096         GetDefaultVALINT(c_port_number, 504);
1097         
1098         GetDefaultVALINT(c_sleeping, 900);
1099
1100         if (config.c_ctdluid == 0) {
1101                 pw = getpwnam("citadel");
1102                 if (pw != NULL) {
1103                         config.c_ctdluid = pw->pw_uid;
1104                 }
1105         }
1106         if (config.c_ctdluid == 0) {
1107                 pw = getpwnam("bbs");
1108                 if (pw != NULL) {
1109                         config.c_ctdluid = pw->pw_uid;
1110                 }
1111         }
1112         if (config.c_ctdluid == 0) {
1113                 pw = getpwnam("guest");
1114                 if (pw != NULL) {
1115                         config.c_ctdluid = pw->pw_uid;
1116                 }
1117         }
1118         if (config.c_createax == 0) {
1119                 config.c_createax = 3;
1120         }
1121         /*
1122          * Negative values for maxsessions are not allowed.
1123          */
1124         if (config.c_maxsessions < 0) {
1125                 config.c_maxsessions = 0;
1126         }
1127         /* We need a system default message expiry policy, because this is
1128          * the top level and there's no 'higher' policy to fall back on.
1129          * By default, do not expire messages at all.
1130          */
1131         if (config.c_ep.expire_mode == 0) {
1132                 config.c_ep.expire_mode = EXPIRE_MANUAL;
1133                 config.c_ep.expire_value = 0;
1134         }
1135
1136         /*
1137          * Default port numbers for various services
1138          */
1139         GetDefaultVALINT(c_smtp_port, 25);
1140         GetDefaultVALINT(c_pop3_port, 110);
1141         GetDefaultVALINT(c_imap_port, 143);
1142         GetDefaultVALINT(c_msa_port, 587);
1143         GetDefaultVALINT(c_smtps_port, 465);
1144         GetDefaultVALINT(c_pop3s_port, 995);
1145         GetDefaultVALINT(c_imaps_port, 993);
1146         GetDefaultVALINT(c_pftcpdict_port, -1);
1147         GetDefaultVALINT(c_managesieve_port, 2020);
1148         GetDefaultVALINT(c_xmpp_c2s_port, 5222);
1149         GetDefaultVALINT(c_xmpp_s2s_port, 5269);
1150 #endif
1151 }
1152
1153
1154
1155 int main(int argc, char *argv[])
1156 {
1157         int a, i;
1158         int curr;
1159         char buf[1024]; 
1160         char aaa[128];
1161         int info_only = 0;
1162         int relh = 0;
1163         int home = 0;
1164         int nRetries = 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         while ((serv_sock < 0) && (nRetries < 10)) {
1236                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1237                 nRetries ++;
1238                 if (serv_sock < 0)
1239                         sleep(1);
1240         }
1241         if (serv_sock < 0) { 
1242                 display_error(
1243                         "%s: %s %s\n", 
1244                         _("Setup could not connect to a running Citadel server."),
1245                         strerror(errno), file_citadel_admin_socket
1246                 );
1247                 exit(1);
1248         }
1249
1250         /*
1251          * read the server greeting
1252          */
1253         serv_gets(buf);
1254         if (buf[0] != '2') {
1255                 display_error("%s\n", buf);
1256                 exit(2);
1257         }
1258
1259         /*
1260          * Are we connected to the correct Citadel server?
1261          */
1262         serv_puts("INFO");
1263         serv_gets(buf);
1264         if (buf[0] != '1') {
1265                 display_error("%s\n", buf);
1266                 exit(3);
1267         }
1268         a = 0;
1269         while (serv_gets(buf), strcmp(buf, "000")) {
1270                 if (a == 5) {
1271                         if (atoi(buf) != REV_LEVEL) {
1272                                 display_error("%s\n",
1273                                 _("Your setup program and Citadel server are from different versions.")
1274                                 );
1275                                 exit(4);
1276                         }
1277                 }
1278                 ++a;
1279         }
1280
1281         /*
1282          * Load the server's configuration
1283          */
1284         serv_puts("CONF GET");
1285         serv_gets(buf);
1286         if (buf[0] != '1') {
1287                 display_error("%s\n", buf);
1288                 exit(5);
1289         }
1290         memset(configs, 0, sizeof configs);
1291         a = 0;
1292         while (serv_gets(buf), strcmp(buf, "000")) {
1293                 if (a < NUM_CONFIGS) {
1294                         safestrncpy(configs[a], buf, sizeof(configs[a]));
1295                 }
1296                 ++a;
1297         }
1298
1299         /*
1300          * Now begin.
1301          */
1302
1303         /* _("Citadel Setup"),  */
1304
1305         if (setup_type == UI_TEXT) {
1306                 printf("\n\n\n         *** %s ***\n\n", program_title);
1307         }
1308
1309         if (setup_type == UI_DIALOG) {
1310                 system("clear 2>/dev/null");
1311         }
1312
1313         set_default_values();
1314
1315         /* Go through a series of dialogs prompting for config info */
1316         for (curr = 1; curr < eMaxQuestions; ++curr) {
1317                 edit_value(curr);
1318
1319                 if (    (curr == 6)
1320                         && (atoi(configs[52]) != AUTHMODE_LDAP)
1321                         && (atoi(configs[52]) != AUTHMODE_LDAP_AD)
1322                 ) {
1323                         curr += 5;      /* skip LDAP questions if we're not authenticating against LDAP */
1324                 }
1325
1326                 if (curr == eSysAdminName) {
1327                         if (atoi(configs[52]) == AUTHMODE_NATIVE) {
1328                                                 /* for native auth mode, fetch the admin's existing pw */
1329                                 snprintf(buf, sizeof buf, "AGUP %s", configs[13]);
1330                                 serv_puts(buf);
1331                                 serv_gets(buf);
1332                                 if (buf[0] == '2') {
1333                                         extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
1334                                 }
1335                         }
1336                         else {
1337                                 ++curr;         /* skip the password question for non-native auth modes */
1338                         }
1339                 }
1340         }
1341
1342         if ((pw = getpwuid(atoi(configs[69]))) == NULL) {
1343                 gid = getgid();
1344         } else {
1345                 gid = pw->pw_gid;
1346         }
1347
1348         create_run_directories(atoi(configs[69]), gid);
1349
1350         activity = _("Reconfiguring Citadel server");
1351         progress(activity, 0, NUM_CONFIGS+3);
1352         sleep(1);                                       /* Let the message appear briefly */
1353         serv_puts("CONF SET");
1354         serv_gets(buf);
1355         if (buf[0] == '4') {
1356                 for (i=0; i<NUM_CONFIGS; ++i) {
1357                         progress(activity, i+1, NUM_CONFIGS+3);
1358                         serv_puts(configs[i]);
1359                 }
1360                 serv_puts("000");
1361         }
1362         sleep(1);                                       /* Let the message appear briefly */
1363
1364         /*
1365          * Create the administrator account.  It's ok if the command fails if this user already exists.
1366          */
1367         progress(activity, NUM_CONFIGS+1, NUM_CONFIGS+3);
1368         snprintf(buf, sizeof buf, "CREU %s|%s", configs[13], admin_pass);
1369         serv_puts(buf);
1370         progress(activity, NUM_CONFIGS+2, NUM_CONFIGS+3);
1371         serv_gets(buf);
1372         progress(activity, NUM_CONFIGS+3, NUM_CONFIGS+3);
1373
1374         /*
1375          * Assign the desired password and access level to the administrator account.
1376          */
1377         snprintf(buf, sizeof buf, "AGUP %s", configs[13]);
1378         serv_puts(buf);
1379         serv_gets(buf);
1380         if (buf[0] == '2') {
1381                 int admin_flags = extract_int(&buf[4], 2);
1382                 int admin_times_called = extract_int(&buf[4], 3);
1383                 int admin_msgs_posted = extract_int(&buf[4], 4);
1384                 snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
1385                         configs[13], admin_pass, admin_flags, admin_times_called, admin_msgs_posted
1386                 );
1387                 serv_puts(buf);
1388                 serv_gets(buf);
1389         }
1390
1391 #ifndef __CYGWIN__
1392         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1393         disable_other_mtas();   /* Offer to disable other MTAs */
1394         fixnss();               /* Check for the 'db' nss and offer to disable it */
1395 #endif
1396
1397         activity = _("Setting file permissions");
1398         progress(activity, 0, 2);
1399         //chown(file_citadel_config, config.c_ctdluid, gid);
1400         progress(activity, 1, 2);
1401         chmod(file_citadel_config, S_IRUSR | S_IWUSR);
1402         progress(activity, 2, 2);
1403
1404         /*
1405          * Restart citserver
1406          */
1407         activity = _("Restarting Citadel server to apply changes");
1408         progress(activity, 0, 41);
1409
1410         serv_puts("TIME");
1411         serv_gets(buf);
1412         long original_start_time = extract_long(&buf[4], 3);
1413
1414         progress(activity, 1, 41);
1415         serv_puts("DOWN 1");
1416         progress(activity, 2, 41);
1417         serv_gets(buf);
1418         if (buf[0] != '2') {
1419                 display_error("%s\n", buf);
1420                 exit(6);
1421         }
1422
1423         close(serv_sock);
1424         serv_sock = (-1);
1425
1426         for (i=3; i<=6; ++i) {                                  /* wait for server to shut down */
1427                 progress(activity, i, 41);
1428                 sleep(1);
1429         }
1430
1431         for (i=7; ((i<=38) && (serv_sock < 0)) ; ++i) {         /* wait for server to start up */
1432                 progress(activity, i, 41);
1433                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1434                 sleep(1);
1435         }
1436
1437         progress(activity, 39, 41);
1438         serv_gets(buf);
1439
1440         progress(activity, 40, 41);
1441         serv_puts("TIME");
1442         serv_gets(buf);
1443         long new_start_time = extract_long(&buf[4], 3);
1444
1445         close(serv_sock);
1446         progress(activity, 41, 41);
1447
1448         if (    (original_start_time == new_start_time)
1449                 || (new_start_time <= 0)
1450         ) {
1451                 display_error("%s\n",
1452                         _("Setup failed to restart Citadel server.  Please restart it manually.")
1453                 );
1454                 exit(7);
1455         }
1456
1457         exit(0);
1458         return 0;
1459 }