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