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