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