f3630b2abc17fd1c2301b0925d257dea70bd0d51
[debian/amanda] / recover-src / amrecover.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998, 2000 University of Maryland at College Park
4  * All Rights Reserved.
5  *
6  * Permission to use, copy, modify, distribute, and sell this software and its
7  * documentation for any purpose is hereby granted without fee, provided that
8  * the above copyright notice appear in all copies and that both that
9  * copyright notice and this permission notice appear in supporting
10  * documentation, and that the name of U.M. not be used in advertising or
11  * publicity pertaining to distribution of the software without specific,
12  * written prior permission.  U.M. makes no representations about the
13  * suitability of this software for any purpose.  It is provided "as is"
14  * without express or implied warranty.
15  *
16  * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
18  * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
21  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  *
23  * Authors: the Amanda Development Team.  Its members are listed in a
24  * file named AUTHORS, in the root directory of this distribution.
25  */
26 /*
27  * $Id: amrecover.c,v 1.73.2.2 2007/01/24 18:33:30 martinea Exp $
28  *
29  * an interactive program for recovering backed-up files
30  */
31
32 #include "amanda.h"
33 #include "version.h"
34 #include "stream.h"
35 #include "amfeatures.h"
36 #include "amrecover.h"
37 #include "getfsent.h"
38 #include "dgram.h"
39 #include "util.h"
40 #include "clientconf.h"
41 #include "protocol.h"
42 #include "event.h"
43 #include "security.h"
44
45 extern int process_line(char *line);
46 int get_line(void);
47 int grab_reply(int show);
48 void sigint_handler(int signum);
49 int main(int argc, char **argv);
50
51 #define USAGE "Usage: amrecover [[-C] <config>] [-s <index-server>] [-t <tape-server>] [-d <tape-device>] [-o <clientconfigoption>]*\n"
52
53 char *config = NULL;
54 char *server_name = NULL;
55 int server_socket;
56 char *server_line = NULL;
57 char *dump_datestamp = NULL;            /* date we are restoring */
58 char *dump_hostname;                    /* which machine we are restoring */
59 char *disk_name = NULL;                 /* disk we are restoring */
60 char *mount_point = NULL;               /* where disk was mounted */
61 char *disk_path = NULL;                 /* path relative to mount point */
62 char dump_date[STR_SIZE];               /* date on which we are restoring */
63 int quit_prog;                          /* set when time to exit parser */
64 char *tape_server_name = NULL;
65 int tape_server_socket;
66 char *tape_device_name = NULL;
67 am_feature_t *our_features = NULL;
68 char *our_features_string = NULL;
69 am_feature_t *indexsrv_features = NULL;
70 am_feature_t *tapesrv_features = NULL;
71 static char *errstr = NULL;
72 char *authopt;
73 int amindexd_alive = 0;
74
75 static struct {
76     const char *name;
77     security_stream_t *fd;
78 } streams[] = {
79 #define MESGFD  0
80     { "MESG", NULL },
81 };
82 #define NSTREAMS        (int)(sizeof(streams) / sizeof(streams[0]))
83
84 static void amindexd_response(void *, pkt_t *, security_handle_t *);
85 void stop_amindexd(void);
86 char *amindexd_client_get_security_conf(char *, void *);
87
88 static char* mesg_buffer = NULL;
89 /* gets a "line" from server and put in server_line */
90 /* server_line is terminated with \0, \r\n is striped */
91 /* returns -1 if error */
92
93 int
94 get_line(void)
95 {
96     ssize_t size;
97     char *newbuf, *s;
98     void *buf;
99
100     if (!mesg_buffer)
101         mesg_buffer = stralloc("");
102  
103     while (!strstr(mesg_buffer,"\r\n")) {
104         size = security_stream_read_sync(streams[MESGFD].fd, &buf);
105         if(size < 0) {
106             return -1;
107         }
108         else if(size == 0) {
109             return -1;
110         }
111         newbuf = alloc(strlen(mesg_buffer)+size+1);
112         strncpy(newbuf, mesg_buffer, (size_t)(strlen(mesg_buffer) + size));
113         memcpy(newbuf+strlen(mesg_buffer), buf, (size_t)size);
114         newbuf[strlen(mesg_buffer)+size] = '\0';
115         amfree(mesg_buffer);
116         mesg_buffer = newbuf;
117     }
118
119     s = strstr(mesg_buffer,"\r\n");
120     *s = '\0';
121     newbuf = stralloc(s+2);
122     server_line = newstralloc(server_line, mesg_buffer);
123     amfree(mesg_buffer);
124     mesg_buffer = newbuf;
125     return 0;
126 }
127
128
129 /* get reply from server and print to screen */
130 /* handle multi-line reply */
131 /* return -1 if error */
132 /* return code returned by server always occupies first 3 bytes of global
133    variable server_line */
134 int
135 grab_reply(
136     int show)
137 {
138     do {
139         if (get_line() == -1) {
140             return -1;
141         }
142         if(show) puts(server_line);
143     } while (server_line[3] == '-');
144     if(show) fflush(stdout);
145
146     return 0;
147 }
148
149
150 /* get 1 line of reply */
151 /* returns -1 if error, 0 if last (or only) line, 1 if more to follow */
152 int
153 get_reply_line(void)
154 {
155     if (get_line() == -1)
156         return -1;
157     return server_line[3] == '-';
158 }
159
160
161 /* returns pointer to returned line */
162 char *
163 reply_line(void)
164 {
165     return server_line;
166 }
167
168
169
170 /* returns 0 if server returned an error code (ie code starting with 5)
171    and non-zero otherwise */
172 int
173 server_happy(void)
174 {
175     return server_line[0] != '5';
176 }
177
178
179 int
180 send_command(
181     char *      cmd)
182 {
183     /*
184      * NOTE: this routine is called from sigint_handler, so we must be
185      * **very** careful about what we do since there is no way to know
186      * our state at the time the interrupt happened.  For instance,
187      * do not use any stdio or malloc routines here.
188      */
189     char *buffer;
190
191     buffer = alloc(strlen(cmd)+3);
192     strncpy(buffer, cmd, strlen(cmd));
193     buffer[strlen(cmd)] = '\r';
194     buffer[strlen(cmd)+1] = '\n';
195     buffer[strlen(cmd)+2] = '\0';
196
197     if(security_stream_write(streams[MESGFD].fd, buffer, strlen(buffer)) < 0) {
198         return -1;
199     }
200     amfree(buffer);
201     return (0);
202 }
203
204
205 /* send a command to the server, get reply and print to screen */
206 int
207 converse(
208     char *      cmd)
209 {
210     if (send_command(cmd) == -1) return -1;
211     if (grab_reply(1) == -1) return -1;
212     return 0;
213 }
214
215
216 /* same as converse() but reply not echoed to stdout */
217 int
218 exchange(
219     char *      cmd)
220 {
221     if (send_command(cmd) == -1) return -1;
222     if (grab_reply(0) == -1) return -1;
223     return 0;
224 }
225
226
227 /* basic interrupt handler for when user presses ^C */
228 /* Bale out, letting server know before doing so */
229 void
230 sigint_handler(
231     int signum)
232 {
233     /*
234      * NOTE: we must be **very** careful about what we do here since there
235      * is no way to know our state at the time the interrupt happened.
236      * For instance, do not use any stdio routines here or in any called
237      * routines.  Also, use _exit() instead of exit() to make sure stdio
238      * buffer flushing is not attempted.
239      */
240     (void)signum;       /* Quiet unused parameter warning */
241
242     if (extract_restore_child_pid != -1)
243         (void)kill(extract_restore_child_pid, SIGKILL);
244     extract_restore_child_pid = -1;
245
246     if(amindexd_alive) 
247         (void)send_command("QUIT");
248
249     _exit(1);
250 }
251
252
253 void
254 clean_pathname(
255     char *      s)
256 {
257     size_t length;
258     length = strlen(s);
259
260     /* remove "/" at end of path */
261     if(length>1 && s[length-1]=='/')
262         s[length-1]='\0';
263
264     /* change "/." to "/" */
265     if(strcmp(s,"/.")==0)
266         s[1]='\0';
267
268     /* remove "/." at end of path */
269     if(strcmp(&(s[length-2]),"/.")==0)
270         s[length-2]='\0';
271 }
272
273
274 void
275 quit(void)
276 {
277     quit_prog = 1;
278     (void)converse("QUIT");
279     stop_amindexd();
280 }
281
282 char *localhost = NULL;
283
284 #ifdef DEFAULT_TAPE_SERVER
285 # define DEFAULT_TAPE_SERVER_FAILOVER (DEFAULT_TAPE_SERVER)
286 #else
287 # define DEFAULT_TAPE_SERVER_FAILOVER (NULL)
288 #endif
289
290 int
291 main(
292     int         argc,
293     char **     argv)
294 {
295     int i;
296     time_t timer;
297     char *lineread = NULL;
298     struct sigaction act, oact;
299     extern char *optarg;
300     extern int optind;
301     char *line = NULL;
302     char *conffile;
303     const security_driver_t *secdrv;
304     char *req = NULL;
305     int response_error;
306     int new_argc;
307     char **new_argv;
308     struct tm *tm;
309
310     safe_fd(-1, 0);
311
312     set_pname("amrecover");
313
314     /* Don't die when child closes pipe */
315     signal(SIGPIPE, SIG_IGN);
316
317     dbopen(DBG_SUBDIR_CLIENT);
318
319 #ifndef IGNORE_UID_CHECK
320     if (geteuid() != 0) {
321         erroutput_type |= ERR_SYSLOG;
322         error("amrecover must be run by root");
323         /*NOTREACHED*/
324     }
325 #endif
326
327     localhost = alloc(MAX_HOSTNAME_LENGTH+1);
328     if (gethostname(localhost, MAX_HOSTNAME_LENGTH) != 0) {
329         error("cannot determine local host name\n");
330         /*NOTREACHED*/
331     }
332     localhost[MAX_HOSTNAME_LENGTH] = '\0';
333
334     parse_client_conf(argc, argv, &new_argc, &new_argv);
335
336     if (new_argc > 1 && new_argv[1][0] != '-') {
337         /*
338          * If the first argument is not an option flag, then we assume
339          * it is a configuration name to match the syntax of the other
340          * Amanda utilities.
341          */
342         char **new_argv1;
343
344         new_argv1 = (char **) alloc((size_t)((new_argc + 1 + 1) * sizeof(*new_argv1)));
345         new_argv1[0] = new_argv[0];
346         new_argv1[1] = "-C";
347         for (i = 1; i < new_argc; i++) {
348             new_argv1[i + 1] = new_argv[i];
349         }
350         new_argv1[i + 1] = NULL;
351         new_argc++;
352         amfree(new_argv);
353         new_argv = new_argv1;
354     }
355     while ((i = getopt(new_argc, new_argv, "C:s:t:d:U")) != EOF) {
356         switch (i) {
357             case 'C':
358                 add_client_conf(CLN_CONF, optarg);
359                 break;
360
361             case 's':
362                 add_client_conf(CLN_INDEX_SERVER, optarg);
363                 break;
364
365             case 't':
366                 add_client_conf(CLN_TAPE_SERVER, optarg);
367                 break;
368
369             case 'd':
370                 add_client_conf(CLN_TAPEDEV, optarg);
371                 break;
372
373             case 'U':
374             case '?':
375                 (void)printf(USAGE);
376                 return 0;
377         }
378     }
379     if (optind != new_argc) {
380         (void)fprintf(stderr, USAGE);
381         exit(1);
382     }
383
384     our_features = am_init_feature_set();
385     our_features_string = am_feature_to_string(our_features);
386
387     conffile = vstralloc(CONFIG_DIR, "/", "amanda-client.conf", NULL);
388     if (read_clientconf(conffile) > 0) {
389         error("error reading conffile: %s", conffile);
390         /*NOTREACHED*/
391     }
392     amfree(conffile);
393
394     config = stralloc(client_getconf_str(CLN_CONF));
395
396     conffile = vstralloc(CONFIG_DIR, "/", config, "/", "amanda-client.conf",
397                          NULL);
398     if (read_clientconf(conffile) > 0) {
399         error("error reading conffile: %s", conffile);
400         /*NOTREACHED*/
401     }
402     amfree(conffile);
403
404     dbrename(config, DBG_SUBDIR_CLIENT);
405
406     report_bad_client_arg();
407
408     server_name = NULL;
409     if (client_getconf_seen(CLN_INDEX_SERVER) == -2) { /* command line argument */
410         server_name = client_getconf_str(CLN_INDEX_SERVER);
411     }
412     if (!server_name) {
413         server_name = getenv("AMANDA_SERVER");
414         if (server_name) {
415             printf("Using index server from environment AMANDA_SERVER (%s)\n", server_name);
416         }
417     }
418     if (!server_name) {
419         server_name = client_getconf_str(CLN_INDEX_SERVER);
420     }
421     if (!server_name) {
422         error("No index server set");
423         /*NOTREACHED*/
424     }
425     server_name = stralloc(server_name);
426
427     tape_server_name = NULL;
428     if (client_getconf_seen(CLN_TAPE_SERVER) == -2) { /* command line argument */
429         tape_server_name = client_getconf_str(CLN_TAPE_SERVER);
430     }
431     if (!tape_server_name) {
432         tape_server_name = getenv("AMANDA_TAPE_SERVER");
433         if (!tape_server_name) {
434             tape_server_name = getenv("AMANDA_TAPESERVER");
435             if (tape_server_name) {
436                 printf("Using tape server from environment AMANDA_TAPESERVER (%s)\n", tape_server_name);
437             }
438         } else {
439             printf("Using tape server from environment AMANDA_TAPE_SERVER (%s)\n", tape_server_name);
440         }
441     }
442     if (!tape_server_name) {
443         tape_server_name = client_getconf_str(CLN_TAPE_SERVER);
444     }
445     if (!tape_server_name) {
446         error("No tape server set");
447         /*NOTREACHED*/
448     }
449     tape_server_name = stralloc(tape_server_name);
450
451     amfree(tape_device_name);
452     tape_device_name = client_getconf_str(CLN_TAPEDEV);
453     if (tape_device_name)
454         tape_device_name = stralloc(tape_device_name);
455
456     authopt = stralloc(client_getconf_str(CLN_AUTH));
457
458
459     amfree(disk_name);
460     amfree(mount_point);
461     amfree(disk_path);
462     dump_date[0] = '\0';
463
464     /* Don't die when child closes pipe */
465     signal(SIGPIPE, SIG_IGN);
466
467     /* set up signal handler */
468     act.sa_handler = sigint_handler;
469     sigemptyset(&act.sa_mask);
470     act.sa_flags = 0;
471     if (sigaction(SIGINT, &act, &oact) != 0) {
472         error("error setting signal handler: %s", strerror(errno));
473         /*NOTREACHED*/
474     }
475
476     protocol_init();
477
478     /* We assume that amindexd support fe_amindexd_options_features */
479     /*                             and fe_amindexd_options_auth     */
480     /* We should send a noop to really know                         */
481     req = vstralloc("SERVICE amindexd\n",
482                     "OPTIONS ", "features=", our_features_string, ";",
483                                 "auth=", authopt, ";",
484                     "\n", NULL);
485
486     secdrv = security_getdriver(authopt);
487     if (secdrv == NULL) {
488         error("no '%s' security driver available for host '%s'",
489             authopt, server_name);
490         /*NOTREACHED*/
491     }
492
493     protocol_sendreq(server_name, secdrv, amindexd_client_get_security_conf,
494                      req, STARTUP_TIMEOUT, amindexd_response, &response_error);
495
496     amfree(req);
497     protocol_run();
498
499     printf("AMRECOVER Version %s. Contacting server on %s ...\n",
500            version(), server_name);
501
502     if(response_error != 0) {
503         fprintf(stderr,"%s\n",errstr);
504         exit(1);
505     }
506
507 #if 0
508     /*
509      * We may need root privilege again later for a reserved port to
510      * the tape server, so we will drop down now but might have to
511      * come back later.
512      */
513     setegid(getgid());
514     seteuid(getuid());
515 #endif
516
517     /* get server's banner */
518     if (grab_reply(1) == -1) {
519         aclose(server_socket);
520         exit(1);
521     }
522     if (!server_happy()) {
523         dbclose();
524         aclose(server_socket);
525         exit(1);
526     }
527
528     /* try to get the features from the server */
529     {
530         char *their_feature_string = NULL;
531
532         line = stralloc2("FEATURES ", our_features_string);
533         if(exchange(line) == 0) {
534             their_feature_string = stralloc(server_line+13);
535             indexsrv_features = am_string_to_feature(their_feature_string);
536         }
537         else {
538             indexsrv_features = am_set_default_feature_set();
539         }
540         amfree(their_feature_string);
541         amfree(line);
542     }
543
544     /* set the date of extraction to be today */
545     (void)time(&timer);
546     tm = localtime(&timer);
547     if (tm) 
548         strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
549     else
550         error("BAD DATE");
551
552     printf("Setting restore date to today (%s)\n", dump_date);
553     line = stralloc2("DATE ", dump_date);
554     if (converse(line) == -1) {
555         aclose(server_socket);
556         exit(1);
557     }
558     amfree(line);
559
560     line = stralloc2("SCNF ", config);
561     if (converse(line) == -1) {
562         aclose(server_socket);
563         exit(1);
564     }
565     amfree(line);
566
567     if (server_happy()) {
568         /* set host we are restoring to this host by default */
569         amfree(dump_hostname);
570         set_host(localhost);
571         if (dump_hostname)
572             printf("Use the setdisk command to choose dump disk to recover\n");
573         else
574             printf("Use the sethost command to choose a host to recover\n");
575
576     }
577
578     quit_prog = 0;
579     do {
580         if ((lineread = readline("amrecover> ")) == NULL) {
581             clearerr(stdin);
582             putchar('\n');
583             break;
584         }
585         if (lineread[0] != '\0') 
586         {
587             add_history(lineread);
588             process_line(lineread);     /* act on line's content */
589         }
590         amfree(lineread);
591     } while (!quit_prog);
592
593     dbclose();
594
595     aclose(server_socket);
596     return 0;
597 }
598
599 static void
600 amindexd_response(
601     void *datap,
602     pkt_t *pkt,
603     security_handle_t *sech)
604 {
605     int ports[NSTREAMS], *response_error = datap, i;
606     char *p;
607     char *tok;
608     char *extra = NULL;
609
610     assert(response_error != NULL);
611     assert(sech != NULL);
612
613     if (pkt == NULL) {
614         errstr = newvstralloc(errstr, "[request failed: ",
615                              security_geterror(sech), "]", NULL);
616         *response_error = 1;
617         return;
618     }
619
620     if (pkt->type == P_NAK) {
621 #if defined(PACKET_DEBUG)
622         fprintf(stderr, "got nak response:\n----\n%s\n----\n\n", pkt->body);
623 #endif
624
625         tok = strtok(pkt->body, " ");
626         if (tok == NULL || strcmp(tok, "ERROR") != 0)
627             goto bad_nak;
628
629         tok = strtok(NULL, "\n");
630         if (tok != NULL) {
631             errstr = newvstralloc(errstr, "NAK: ", tok, NULL);
632             *response_error = 1;
633         } else {
634 bad_nak:
635             errstr = newstralloc(errstr, "request NAK");
636             *response_error = 2;
637         }
638         return;
639     }
640
641     if (pkt->type != P_REP) {
642         errstr = newvstralloc(errstr, "received strange packet type ",
643                               pkt_type2str(pkt->type), ": ", pkt->body, NULL);
644         *response_error = 1;
645         return;
646     }
647
648 #if defined(PACKET_DEBUG)
649     fprintf(stderr, "got response:\n----\n%s\n----\n\n", pkt->body);
650 #endif
651
652     for(i = 0; i < NSTREAMS; i++) {
653         ports[i] = -1;
654         streams[i].fd = NULL;
655     }
656
657     p = pkt->body;
658     while((tok = strtok(p, " \n")) != NULL) {
659         p = NULL;
660
661         /*
662          * Error response packets have "ERROR" followed by the error message
663          * followed by a newline.
664          */
665         if (strcmp(tok, "ERROR") == 0) {
666             tok = strtok(NULL, "\n");
667             if (tok == NULL)
668                 tok = "[bogus error packet]";
669             errstr = newstralloc(errstr, tok);
670             *response_error = 2;
671             return;
672         }
673
674
675         /*
676          * Regular packets have CONNECT followed by three streams
677          */
678         if (strcmp(tok, "CONNECT") == 0) {
679
680             /*
681              * Parse the three stream specifiers out of the packet.
682              */
683             for (i = 0; i < NSTREAMS; i++) {
684                 tok = strtok(NULL, " ");
685                 if (tok == NULL || strcmp(tok, streams[i].name) != 0) {
686                     extra = vstralloc("CONNECT token is \"",
687                                       tok ? tok : "(null)",
688                                       "\": expected \"",
689                                       streams[i].name,
690                                       "\"",
691                                       NULL);
692                     goto parse_error;
693                 }
694                 tok = strtok(NULL, " \n");
695                 if (tok == NULL || sscanf(tok, "%d", &ports[i]) != 1) {
696                     extra = vstralloc("CONNECT ",
697                                       streams[i].name,
698                                       " token is \"",
699                                       tok ? tok : "(null)",
700                                       "\": expected a port number",
701                                       NULL);
702                     goto parse_error;
703                 }
704             }
705             continue;
706         }
707
708         /*
709          * OPTIONS [options string] '\n'
710          */
711         if (strcmp(tok, "OPTIONS") == 0) {
712             tok = strtok(NULL, "\n");
713             if (tok == NULL) {
714                 extra = stralloc("OPTIONS token is missing");
715                 goto parse_error;
716             }
717 /*
718             tok_end = tok + strlen(tok);
719             while((p = strchr(tok, ';')) != NULL) {
720                 *p++ = '\0';
721 #define sc "features="
722                 if(strncmp(tok, sc, sizeof(sc)-1) == 0) {
723                     tok += sizeof(sc) - 1;
724 #undef sc
725                     am_release_feature_set(their_features);
726                     if((their_features = am_string_to_feature(tok)) == NULL) {
727                         errstr = newvstralloc(errstr,
728                                               "OPTIONS: bad features value: ",
729                                               tok,
730                                               NULL);
731                         goto parse_error;
732                     }
733                 }
734                 tok = p;
735             }
736 */
737             continue;
738         }
739 /*
740         extra = vstralloc("next token is \"",
741                           tok ? tok : "(null)",
742                           "\": expected \"CONNECT\", \"ERROR\" or \"OPTIONS\"",
743                           NULL);
744         goto parse_error;
745 */
746     }
747
748     /*
749      * Connect the streams to their remote ports
750      */
751     for (i = 0; i < NSTREAMS; i++) {
752 /*@i@*/ if (ports[i] == -1)
753             continue;
754         streams[i].fd = security_stream_client(sech, ports[i]);
755         if (streams[i].fd == NULL) {
756             errstr = newvstralloc(errstr,
757                         "[could not connect ", streams[i].name, " stream: ",
758                         security_geterror(sech), "]", NULL);
759             goto connect_error;
760         }
761     }
762     /*
763      * Authenticate the streams
764      */
765     for (i = 0; i < NSTREAMS; i++) {
766         if (streams[i].fd == NULL)
767             continue;
768         if (security_stream_auth(streams[i].fd) < 0) {
769             errstr = newvstralloc(errstr,
770                 "[could not authenticate ", streams[i].name, " stream: ",
771                 security_stream_geterror(streams[i].fd), "]", NULL);
772             goto connect_error;
773         }
774     }
775
776     /*
777      * The MESGFD and DATAFD streams are mandatory.  If we didn't get
778      * them, complain.
779      */
780     if (streams[MESGFD].fd == NULL) {
781         errstr = newstralloc(errstr, "[couldn't open MESG streams]");
782         goto connect_error;
783     }
784
785     /* everything worked */
786     *response_error = 0;
787     amindexd_alive = 1;
788     return;
789
790 parse_error:
791     errstr = newvstralloc(errstr,
792                           "[parse of reply message failed: ",
793                           extra ? extra : "(no additional information)",
794                           "]",
795                           NULL);
796     amfree(extra);
797     *response_error = 2;
798     return;
799
800 connect_error:
801     stop_amindexd();
802     *response_error = 1;
803 }
804
805 /*
806  * This is called when everything needs to shut down so event_loop()
807  * will exit.
808  */
809 void
810 stop_amindexd(void)
811 {
812     int i;
813
814     amindexd_alive = 0;
815     for (i = 0; i < NSTREAMS; i++) {
816         if (streams[i].fd != NULL) {
817             security_stream_close(streams[i].fd);
818             streams[i].fd = NULL;
819         }
820     }
821 }
822
823 char *
824 amindexd_client_get_security_conf(
825     char *      string,
826     void *      arg)
827 {
828     (void)arg;  /* Quiet unused parameter warning */
829
830     if(!string || !*string)
831         return(NULL);
832
833     if(strcmp(string, "auth")==0) {
834         return(client_getconf_str(CLN_AUTH));
835     }
836     else if(strcmp(string, "ssh_keys")==0) {
837         return(client_getconf_str(CLN_SSH_KEYS));
838     }
839     return(NULL);
840 }