oops
[citadel.git] / citadel / utils / setup.c
1 /*
2  * Citadel setup utility
3  *
4  * Copyright (c) 1987-2019 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 void disable_other_mtas(void)
633 {
634         if ((getenv("ACT_AS_MTA") == NULL) || (getenv("ACT_AS_MTA") && strcasecmp(getenv("ACT_AS_MTA"), "yes") == 0)) {
635                 /* Offer to disable other MTA's on the system. */
636                 /* FIXME this has to be rewritten to work in the new systemd-based world. */
637         }
638 }
639
640
641 void strprompt(const char *prompt_title, const char *prompt_text, char *Target, char *DefValue)
642 {
643         char buf[SIZ] = "";
644         char setupmsg[SIZ];
645         char dialog_result[PATH_MAX];
646         FILE *fp = NULL;
647         int rv;
648
649         strcpy(setupmsg, "");
650
651         switch (setup_type) {
652         case UI_TEXT:
653                 title(prompt_title);
654                 printf("\n%s\n", prompt_text);
655                 printf("%s\n%s\n", _("This is currently set to:"), Target);
656                 printf("%s\n", _("Enter new value or press return to leave unchanged:"));
657                 if (fgets(buf, sizeof buf, stdin)) {
658                         buf[strlen(buf) - 1] = 0;
659                 }
660                 if (!IsEmptyStr(buf))
661                         strcpy(Target, buf);
662                 break;
663
664         case UI_DIALOG:
665                 CtdlMakeTempFileName(dialog_result, sizeof dialog_result);
666                 snprintf(buf, sizeof buf, "exec %s --backtitle '%s' --nocancel --inputbox '%s' 19 72 '%s' 2>%s",
667                         getenv("CTDL_DIALOG"),
668                         program_title,
669                         prompt_text,
670                         Target,
671                         dialog_result);
672                 rv = system(buf);
673                 if (rv != 0) {
674                         fprintf(stderr, "failed to run whiptail or dialog\n");
675                 }
676                 
677                 fp = fopen(dialog_result, "r");
678                 if (fp != NULL) {
679                         if (fgets(Target, sizeof buf, fp)) {
680                                 if (Target[strlen(Target)-1] == 10) {
681                                         Target[strlen(Target)-1] = 0;
682                                 }
683                         }
684                         fclose(fp);
685                         unlink(dialog_result);
686                 }
687                 break;
688         case UI_SILENT:
689                 if (*DefValue != '\0')
690                         strcpy(Target, DefValue);
691                 break;
692         }
693 }
694
695 void set_bool_val(int msgpos, int *ip, char *DefValue) 
696 {
697         title(setup_titles[msgpos]);
698         *ip = yesno(setup_text[msgpos], *ip);
699 }
700
701 void set_str_val(int msgpos, char *Target, char *DefValue) 
702 {
703         strprompt(setup_titles[msgpos], 
704                   setup_text[msgpos], 
705                   Target, 
706                   DefValue
707         );
708 }
709
710 /* like set_str_val() but for numeric values */
711 void set_int_val(int msgpos, int *target, char *default_value)
712 {
713         char buf[32];
714         sprintf(buf, "%d", *target);
715         do {
716                 set_str_val(msgpos, buf, default_value);
717         } while ( (strcmp(buf, "0")) && (atoi(buf) == 0) );
718         *target = atoi(buf);
719 }
720
721
722 void edit_value(int curr)
723 {
724         struct passwd *pw = NULL;
725         char ctdluidname[256];
726         char buf[SIZ];
727         char *default_value = NULL;
728         int ctdluid = 0;
729         int portnum = 0;
730         int auth = 0;
731         int lportnum = 0;
732
733         if (setup_type == UI_SILENT)
734         {
735                 default_value = getenv(EnvNames[curr]);
736         }
737         if (default_value == NULL) {
738                 default_value = "";
739         }
740
741         switch (curr) {
742
743         case eSysAdminName:
744                 getconf_str(admin_name, "c_sysadm");
745                 set_str_val(curr, admin_name, default_value);
746                 setconf_str("c_sysadm", admin_name);
747                 break;
748
749         case eSysAdminPW:
750                 set_str_val(curr, admin_pass, default_value);
751                 break;
752         
753         case eUID:
754                 ctdluid = getconf_int("c_ctdluid");
755                 if (setup_type == UI_SILENT)
756                 {               
757                         if (default_value) {
758                                 ctdluid = atoi(default_value);
759                         }                                       
760                 }
761                 else
762                 {
763                         pw = getpwuid(ctdluid);
764                         if (pw == NULL) {
765                                 set_int_val(curr, &ctdluid, default_value);
766                         }
767                         else {
768                                 strcpy(ctdluidname, pw->pw_name);
769                                 set_str_val(curr, ctdluidname, default_value);
770                                 pw = getpwnam(ctdluidname);
771                                 if (pw != NULL) {
772                                         ctdluid = pw->pw_uid;
773                                 }
774                                 else if (atoi(ctdluidname) > 0) {
775                                         ctdluid = atoi(ctdluidname);
776                                 }
777                         }
778 #endif
779                 }
780                 setconf_int("c_ctdluid", ctdluid);
781                 break;
782
783         case eIP_ADDR:
784                 getconf_str(buf, "c_ip_addr");
785                 set_str_val(curr, buf, default_value);
786                 setconf_str("c_ip_addr", buf);
787                 break;
788
789         case eCTDL_Port:
790                 portnum = getconf_int("c_port_number");
791                 set_int_val(curr, &portnum, default_value);
792                 setconf_int("c_port_number", portnum);
793                 break;
794
795         case eAuthType:
796                 auth = getconf_int("c_auth_mode");
797                 if (setup_type == UI_SILENT)
798                 {
799                         if ( (default_value) && (!strcasecmp(default_value, "yes")) ) auth = AUTHMODE_HOST;
800                         if ( (default_value) && (!strcasecmp(default_value, "host")) ) auth = AUTHMODE_HOST;
801                         if ( (default_value) && (!strcasecmp(default_value, "ldap")) ) auth = AUTHMODE_LDAP;
802                         if ( (default_value) && (!strcasecmp(default_value, "ldap_ad")) ) auth = AUTHMODE_LDAP_AD;
803                         if ( (default_value) && (!strcasecmp(default_value, "active directory")) ) auth = AUTHMODE_LDAP_AD;
804                 }
805                 else {
806                         set_int_val(curr, &auth, default_value);
807                 }
808                 setconf_int("c_auth_mode", auth);
809                 break;
810
811         case eLDAP_Host:
812                 getconf_str(buf, "c_ldap_host");
813                 if (IsEmptyStr(buf)) {
814                         strcpy(buf, "localhost");
815                 }
816                 set_str_val(curr, buf, default_value);
817                 setconf_str("c_ldap_host", buf);
818                 break;
819
820         case eLDAP_Port:
821                 lportnum = getconf_int("c_ldap_port");
822                 if (lportnum == 0) {
823                         lportnum = 389;
824                 }
825                 set_int_val(curr, &lportnum, default_value);
826                 setconf_int("c_ldap_port", lportnum);
827                 break;
828
829         case eLDAP_Base_DN:
830                 getconf_str(buf, "c_ldap_base_dn");
831                 set_str_val(curr, buf, default_value);
832                 setconf_str("c_ldap_base_dn", buf);
833                 break;
834
835         case eLDAP_Bind_DN:
836                 getconf_str(buf, "c_ldap_bind_dn");
837                 set_str_val(curr, buf, default_value);
838                 setconf_str("c_ldap_bind_dn", buf);
839                 break;
840
841         case eLDAP_Bind_PW:
842                 getconf_str(buf, "c_ldap_bind_pw");
843                 set_str_val(curr, buf, default_value);
844                 setconf_str("c_ldap_bind_pw", buf);
845                 break;
846         }
847 }
848
849
850
851 /*
852  * Figure out what type of user interface we're going to use
853  */
854 int discover_ui(void)
855 {
856
857         /* Use "whiptail" or "dialog" if we have it */
858         if (getenv("CTDL_DIALOG") != NULL) {
859                 return UI_DIALOG;
860         }
861                 
862         return UI_TEXT;
863 }
864
865
866
867 /*
868  * Strip "db" entries out of /etc/nsswitch.conf
869  */
870 void fixnss(void) {
871         FILE *fp_read;
872         int fd_write;
873         char buf[256];
874         char buf_nc[256];
875         char question[512];
876         int i;
877         int file_changed = 0;
878         char new_filename[64];
879         int rv;
880
881         fp_read = fopen(NSSCONF, "r");
882         if (fp_read == NULL) {
883                 return;
884         }
885
886         strcpy(new_filename, "/tmp/ctdl_fixnss_XXXXXX");
887         fd_write = mkstemp(new_filename);
888         if (fd_write < 0) {
889                 fclose(fp_read);
890                 return;
891         }
892
893         while (fgets(buf, sizeof buf, fp_read) != NULL) {
894                 strcpy(buf_nc, buf);
895                 for (i=0; buf_nc[i]; ++i) {
896                         if (buf_nc[i] == '#') {
897                                 buf_nc[i] = 0;
898                                 break;
899                         }
900                 }
901                 for (i=0; i<strlen(buf_nc); ++i) {
902                         if (!strncasecmp(&buf_nc[i], "db", 2)) {
903                                 if (i > 0) {
904                                         if ((isspace(buf_nc[i+2])) || (buf_nc[i+2]==0)) {
905                                                 file_changed = 1;
906                                                 strcpy(&buf_nc[i], &buf_nc[i+2]);
907                                                 strcpy(&buf[i], &buf[i+2]);
908                                                 if (buf[i]==32) {
909                                                         strcpy(&buf_nc[i], &buf_nc[i+1]);
910                                                         strcpy(&buf[i], &buf[i+1]);
911                                                 }
912                                         }
913                                 }
914                         }
915                 }
916                 long buflen = strlen(buf);
917                 if (write(fd_write, buf, buflen) != buflen) {
918                         fclose(fp_read);
919                         close(fd_write);
920                         unlink(new_filename);
921                         return;
922                 }
923         }
924
925         fclose(fp_read);
926         
927         if (!file_changed) {
928                 unlink(new_filename);
929                 return;
930         }
931
932         snprintf(question, sizeof question,
933                  _(
934                          "\n"
935                          "/etc/nsswitch.conf is configured to use the 'db' module for\n"
936                          "one or more services.  This is not necessary on most systems,\n"
937                          "and it is known to crash the Citadel server when delivering\n"
938                          "mail to the Internet.\n"
939                          "\n"
940                          "Do you want this module to be automatically disabled?\n"
941                          "\n"
942                          )
943         );
944
945         if (yesno(question, 1)) {
946                 snprintf(buf, sizeof buf, "/bin/mv -f %s %s", new_filename, NSSCONF);
947                 rv = system(buf);
948                 if (rv != 0) {
949                         fprintf(stderr, "failed to edit %s.\n", NSSCONF);
950                 }
951                 chmod(NSSCONF, 0644);
952         }
953         unlink(new_filename);
954 }
955
956
957 /*
958  * Messages that are no longer in use.
959  * We keep them here so we don't lose the translations if we need them later.
960  */
961 void niu_messages() {
962 important_message(_("Setup finished"),
963 _("Setup of the Citadel server is complete.\n"
964 "If you will be using WebCit, please run its\n"
965 "setup program now; otherwise, run './citadel'\n"
966 "to log in.\n"));
967 important_message(_("Setup failed"),
968 _("Setup is finished, but the Citadel server failed to start.\n"
969 "Go back and check your configuration.\n");
970 important_message(_("Setup finished"),
971 _("Setup is finished.  You may now start the server."));
972 }
973
974
975 int main(int argc, char *argv[])
976 {
977         int a, i;
978         int curr;
979         char buf[1024]; 
980         char aaa[128];
981         int relh = 0;
982         int home = 0;
983         char relhome[PATH_MAX]="";
984         char ctdldir[PATH_MAX]=CTDLDIR;
985         struct passwd *pw;
986         gid_t gid;
987         char *activity = NULL;
988         
989         /* Keep a mild groove on */
990         program_title = _("Citadel setup program");
991
992         /* set an invalid setup type */
993         setup_type = (-1);
994
995         /* parse command line args */
996         for (a = 0; a < argc; ++a) {
997                 if (!strncmp(argv[a], "-u", 2)) {
998                         strcpy(aaa, argv[a]);
999                         strcpy(aaa, &aaa[2]);
1000                         setup_type = atoi(aaa);
1001                 }
1002                 else if (!strcmp(argv[a], "-q")) {
1003                         setup_type = UI_SILENT;
1004                 }
1005                 else if (!strncmp(argv[a], "-h", 2)) {
1006                         relh=argv[a][2]!='/';
1007                         if (!relh) {
1008                                 safestrncpy(ctdl_home_directory, &argv[a][2], sizeof ctdl_home_directory);
1009                         } else {
1010                                 safestrncpy(relhome, &argv[a][2], sizeof relhome);
1011                         }
1012                         home = 1;
1013                 }
1014
1015         }
1016
1017         calc_dirs_n_files(relh, home, relhome, ctdldir, 0);
1018         SetTitles();
1019
1020         /* If a setup type was not specified, try to determine automatically
1021          * the best one to use out of all available types.
1022          */
1023         if (setup_type < 0) {
1024                 setup_type = discover_ui();
1025         }
1026
1027         enable_home = ( relh | home );
1028
1029         if (chdir(ctdl_run_dir) != 0) {
1030                 display_error("%s: [%s]\n", _("The directory you specified does not exist"), ctdl_run_dir);
1031                 exit(errno);
1032         }
1033
1034
1035         /*
1036          * Connect to the running Citadel server.
1037          */
1038         char *connectingmsg = _("Connecting to Citadel server");
1039         for (i=0; ((i<30) && (serv_sock < 0)) ; ++i) {          /* wait for server to start up */
1040                 progress(connectingmsg, i, 30);
1041                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1042                 sleep(1);
1043         }
1044         progress(connectingmsg, 30, 30);
1045
1046         if (serv_sock < 0) { 
1047                 display_error(
1048                         "%s: %s %s\n", 
1049                         _("Setup could not connect to a running Citadel server."),
1050                         strerror(errno), file_citadel_admin_socket
1051                 );
1052                 exit(1);
1053         }
1054
1055         /*
1056          * read the server greeting
1057          */
1058         serv_gets(buf);
1059         if (buf[0] != '2') {
1060                 display_error("%s\n", buf);
1061                 exit(2);
1062         }
1063
1064         /*
1065          * Are we connected to the correct Citadel server?
1066          */
1067         serv_puts("INFO");
1068         serv_gets(buf);
1069         if (buf[0] != '1') {
1070                 display_error("%s\n", buf);
1071                 exit(3);
1072         }
1073         a = 0;
1074         while (serv_gets(buf), strcmp(buf, "000")) {
1075                 if (a == 5) {
1076                         if (atoi(buf) != REV_LEVEL) {
1077                                 display_error("%s\n",
1078                                 _("Your setup program and Citadel server are from different versions.")
1079                                 );
1080                                 exit(4);
1081                         }
1082                 }
1083                 ++a;
1084         }
1085
1086         /*
1087          * Now begin.
1088          */
1089
1090
1091         if (setup_type == UI_TEXT) {
1092                 printf("\n\n\n         *** %s ***\n\n", program_title);
1093         }
1094
1095         if (setup_type == UI_DIALOG) {
1096                 system("clear 2>/dev/null");
1097         }
1098
1099         /* Go through a series of dialogs prompting for config info */
1100         for (curr = 1; curr < eMaxQuestions; ++curr) {
1101                 edit_value(curr);
1102
1103                 if (    (curr == eAuthType)
1104                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP)
1105                         && (getconf_int("c_auth_mode") != AUTHMODE_LDAP_AD)
1106                 ) {
1107                         curr += 5;      /* skip LDAP questions if we're not authenticating against LDAP */
1108                 }
1109
1110                 if (curr == eSysAdminName) {
1111                         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1112                                                 /* for native auth mode, fetch the admin's existing pw */
1113                                 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
1114                                 serv_puts(buf);
1115                                 serv_gets(buf);
1116                                 if (buf[0] == '2') {
1117                                         extract_token(admin_pass, &buf[4], 1, '|', sizeof admin_pass);
1118                                 }
1119                         }
1120                         else {
1121                                 ++curr;         /* skip the password question for non-native auth modes */
1122                         }
1123                 }
1124         }
1125
1126         if ((pw = getpwuid( getconf_int("c_ctdluid") )) == NULL) {
1127                 gid = getgid();
1128         } else {
1129                 gid = pw->pw_gid;
1130         }
1131
1132         if (create_run_directories(getconf_int("c_ctdluid"), gid) != 0) {
1133                 display_error("%s\n", _("failed to create directories"));
1134         }
1135                 
1136         activity = _("Reconfiguring Citadel server");
1137         progress(activity, 0, 5);
1138         sleep(1);                                       /* Let the message appear briefly */
1139
1140         /*
1141          * Create the administrator account.  It's ok if the command fails if this user already exists.
1142          */
1143         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1144                 progress(activity, 1, 5);
1145                 snprintf(buf, sizeof buf, "CREU %s|%s", admin_name, admin_pass);
1146                 serv_puts(buf);
1147                 progress(activity, 2, 5);
1148                 serv_gets(buf);
1149         }
1150         progress(activity, 3, 5);
1151
1152         /*
1153          * Assign the desired password and access level to the administrator account.
1154          */
1155         if (getconf_int("c_auth_mode") == AUTHMODE_NATIVE) {
1156                 snprintf(buf, sizeof buf, "AGUP %s", admin_name);
1157                 serv_puts(buf);
1158                 progress(activity, 4, 5);
1159                 serv_gets(buf);
1160                 if (buf[0] == '2') {
1161                         int admin_flags = extract_int(&buf[4], 2);
1162                         int admin_times_called = extract_int(&buf[4], 3);
1163                         int admin_msgs_posted = extract_int(&buf[4], 4);
1164                         snprintf(buf, sizeof buf, "ASUP %s|%s|%d|%d|%d|6",
1165                                 admin_name, admin_pass, admin_flags, admin_times_called, admin_msgs_posted
1166                         );
1167                         serv_puts(buf);
1168                         serv_gets(buf);
1169                 }
1170         }
1171         progress(activity, 5, 5);
1172
1173 #ifndef __CYGWIN__
1174         check_xinetd_entry();   /* Check /etc/xinetd.d/telnet */
1175         disable_other_mtas();   /* Offer to disable other MTAs */
1176         fixnss();               /* Check for the 'db' nss and offer to disable it */
1177 #endif
1178
1179         /*
1180          * Restart citserver
1181          */
1182         activity = _("Restarting Citadel server to apply changes");
1183         progress(activity, 0, 51);
1184
1185         serv_puts("TIME");
1186         serv_gets(buf);
1187         long original_start_time = extract_long(&buf[4], 3);
1188
1189         progress(activity, 1, 51);
1190         serv_puts("DOWN 1");
1191         progress(activity, 2, 51);
1192         serv_gets(buf);
1193         if (buf[0] != '2') {
1194                 display_error("%s\n", buf);
1195                 exit(6);
1196         }
1197
1198         close(serv_sock);
1199         serv_sock = (-1);
1200
1201         for (i=3; i<=6; ++i) {                                  /* wait for server to shut down */
1202                 progress(activity, i, 51);
1203                 sleep(1);
1204         }
1205
1206         for (i=7; ((i<=48) && (serv_sock < 0)) ; ++i) {         /* wait for server to start up */
1207                 progress(activity, i, 51);
1208                 serv_sock = uds_connectsock(file_citadel_admin_socket);
1209                 sleep(1);
1210         }
1211
1212         progress(activity, 49, 51);
1213         serv_gets(buf);
1214
1215         progress(activity, 50, 51);
1216         serv_puts("TIME");
1217         serv_gets(buf);
1218         long new_start_time = extract_long(&buf[4], 3);
1219
1220         close(serv_sock);
1221         progress(activity, 51, 51);
1222
1223         if (    (original_start_time == new_start_time)
1224                 || (new_start_time <= 0)
1225         ) {
1226                 display_error("%s\n", _("Setup failed to restart Citadel server.  Please restart it manually."));
1227                 exit(7);
1228         }
1229
1230         exit(0);
1231         return 0;
1232 }