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