Imported Upstream version 2.5.1
[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 2006/07/25 18:27:57 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                 //config = newstralloc(config, optarg);
360                 break;
361
362             case 's':
363                 add_client_conf(CLN_INDEX_SERVER, optarg);
364                 //server_name = newstralloc(server_name, optarg);
365                 break;
366
367             case 't':
368                 add_client_conf(CLN_TAPE_SERVER, optarg);
369                 //tape_server_name = newstralloc(tape_server_name, optarg);
370                 break;
371
372             case 'd':
373                 add_client_conf(CLN_TAPEDEV, optarg);
374                 //tape_device_name = newstralloc(tape_device_name, optarg);
375                 break;
376
377             case 'U':
378             case '?':
379                 (void)printf(USAGE);
380                 return 0;
381         }
382     }
383     if (optind != new_argc) {
384         (void)fprintf(stderr, USAGE);
385         exit(1);
386     }
387
388     our_features = am_init_feature_set();
389     our_features_string = am_feature_to_string(our_features);
390
391     conffile = vstralloc(CONFIG_DIR, "/", "amanda-client.conf", NULL);
392     if (read_clientconf(conffile) > 0) {
393         error("error reading conffile: %s", conffile);
394         /*NOTREACHED*/
395     }
396     amfree(conffile);
397
398     config = stralloc(client_getconf_str(CLN_CONF));
399
400     conffile = vstralloc(CONFIG_DIR, "/", config, "/", "amanda-client.conf",
401                          NULL);
402     if (read_clientconf(conffile) > 0) {
403         error("error reading conffile: %s", conffile);
404         /*NOTREACHED*/
405     }
406     amfree(conffile);
407
408     dbrename(config, DBG_SUBDIR_CLIENT);
409
410     report_bad_client_arg();
411
412     amfree(server_name);
413     server_name = getenv("AMANDA_SERVER");
414     if(!server_name) server_name = client_getconf_str(CLN_INDEX_SERVER);
415     server_name = stralloc(server_name);
416
417     amfree(tape_server_name);
418     tape_server_name = getenv("AMANDA_TAPESERVER");
419     if(!tape_server_name) tape_server_name = client_getconf_str(CLN_TAPE_SERVER);
420     tape_server_name = stralloc(tape_server_name);
421
422     amfree(tape_device_name);
423     tape_device_name = client_getconf_str(CLN_TAPEDEV);
424     if (tape_device_name)
425         tape_device_name = stralloc(tape_device_name);
426
427     authopt = stralloc(client_getconf_str(CLN_AUTH));
428
429
430     amfree(disk_name);
431     amfree(mount_point);
432     amfree(disk_path);
433     dump_date[0] = '\0';
434
435     /* Don't die when child closes pipe */
436     signal(SIGPIPE, SIG_IGN);
437
438     /* set up signal handler */
439     act.sa_handler = sigint_handler;
440     sigemptyset(&act.sa_mask);
441     act.sa_flags = 0;
442     if (sigaction(SIGINT, &act, &oact) != 0) {
443         error("error setting signal handler: %s", strerror(errno));
444         /*NOTREACHED*/
445     }
446
447     protocol_init();
448
449     /* We assume that amindexd support fe_amindexd_options_features */
450     /*                             and fe_amindexd_options_auth     */
451     /* We should send a noop to really know                         */
452     req = vstralloc("SERVICE amindexd\n",
453                     "OPTIONS ", "features=", our_features_string, ";",
454                                 "auth=", authopt, ";",
455                     "\n", NULL);
456
457     secdrv = security_getdriver(authopt);
458     if (secdrv == NULL) {
459         error("no '%s' security driver available for host '%s'",
460             authopt, server_name);
461         /*NOTREACHED*/
462     }
463
464     protocol_sendreq(server_name, secdrv, amindexd_client_get_security_conf,
465                      req, STARTUP_TIMEOUT, amindexd_response, &response_error);
466
467     amfree(req);
468     protocol_run();
469
470     printf("AMRECOVER Version %s. Contacting server on %s ...\n",
471            version(), server_name);
472
473     if(response_error != 0) {
474         fprintf(stderr,"%s\n",errstr);
475         exit(1);
476     }
477
478 #if 0
479     /*
480      * We may need root privilege again later for a reserved port to
481      * the tape server, so we will drop down now but might have to
482      * come back later.
483      */
484     setegid(getgid());
485     seteuid(getuid());
486 #endif
487
488     /* get server's banner */
489     if (grab_reply(1) == -1) {
490         aclose(server_socket);
491         exit(1);
492     }
493     if (!server_happy()) {
494         dbclose();
495         aclose(server_socket);
496         exit(1);
497     }
498
499     /* try to get the features from the server */
500     {
501         char *their_feature_string = NULL;
502
503         line = stralloc2("FEATURES ", our_features_string);
504         if(exchange(line) == 0) {
505             their_feature_string = stralloc(server_line+13);
506             indexsrv_features = am_string_to_feature(their_feature_string);
507         }
508         else {
509             indexsrv_features = am_set_default_feature_set();
510         }
511         amfree(their_feature_string);
512         amfree(line);
513     }
514
515     /* set the date of extraction to be today */
516     (void)time(&timer);
517     tm = localtime(&timer);
518     if (tm) 
519         strftime(dump_date, sizeof(dump_date), "%Y-%m-%d", tm);
520     else
521         error("BAD DATE");
522
523     printf("Setting restore date to today (%s)\n", dump_date);
524     line = stralloc2("DATE ", dump_date);
525     if (converse(line) == -1) {
526         aclose(server_socket);
527         exit(1);
528     }
529     amfree(line);
530
531     line = stralloc2("SCNF ", config);
532     if (converse(line) == -1) {
533         aclose(server_socket);
534         exit(1);
535     }
536     amfree(line);
537
538     if (server_happy()) {
539         /* set host we are restoring to this host by default */
540         amfree(dump_hostname);
541         set_host(localhost);
542         if (dump_hostname)
543             printf("Use the setdisk command to choose dump disk to recover\n");
544         else
545             printf("Use the sethost command to choose a host to recover\n");
546
547     }
548
549     quit_prog = 0;
550     do {
551         if ((lineread = readline("amrecover> ")) == NULL) {
552             clearerr(stdin);
553             putchar('\n');
554             break;
555         }
556         if (lineread[0] != '\0') 
557         {
558             add_history(lineread);
559             process_line(lineread);     /* act on line's content */
560         }
561         amfree(lineread);
562     } while (!quit_prog);
563
564     dbclose();
565
566     aclose(server_socket);
567     return 0;
568 }
569
570 static void
571 amindexd_response(
572     void *datap,
573     pkt_t *pkt,
574     security_handle_t *sech)
575 {
576     int ports[NSTREAMS], *response_error = datap, i;
577     char *p;
578     char *tok;
579     char *extra = NULL;
580
581     assert(response_error != NULL);
582     assert(sech != NULL);
583
584     if (pkt == NULL) {
585         errstr = newvstralloc(errstr, "[request failed: ",
586                              security_geterror(sech), "]", NULL);
587         *response_error = 1;
588         return;
589     }
590
591     if (pkt->type == P_NAK) {
592 #if defined(PACKET_DEBUG)
593         fprintf(stderr, "got nak response:\n----\n%s\n----\n\n", pkt->body);
594 #endif
595
596         tok = strtok(pkt->body, " ");
597         if (tok == NULL || strcmp(tok, "ERROR") != 0)
598             goto bad_nak;
599
600         tok = strtok(NULL, "\n");
601         if (tok != NULL) {
602             errstr = newvstralloc(errstr, "NAK: ", tok, NULL);
603             *response_error = 1;
604         } else {
605 bad_nak:
606             errstr = newstralloc(errstr, "request NAK");
607             *response_error = 2;
608         }
609         return;
610     }
611
612     if (pkt->type != P_REP) {
613         errstr = newvstralloc(errstr, "received strange packet type ",
614                               pkt_type2str(pkt->type), ": ", pkt->body, NULL);
615         *response_error = 1;
616         return;
617     }
618
619 #if defined(PACKET_DEBUG)
620     fprintf(stderr, "got response:\n----\n%s\n----\n\n", pkt->body);
621 #endif
622
623     for(i = 0; i < NSTREAMS; i++) {
624         ports[i] = -1;
625         streams[i].fd = NULL;
626     }
627
628     p = pkt->body;
629     while((tok = strtok(p, " \n")) != NULL) {
630         p = NULL;
631
632         /*
633          * Error response packets have "ERROR" followed by the error message
634          * followed by a newline.
635          */
636         if (strcmp(tok, "ERROR") == 0) {
637             tok = strtok(NULL, "\n");
638             if (tok == NULL)
639                 tok = "[bogus error packet]";
640             errstr = newstralloc(errstr, tok);
641             *response_error = 2;
642             return;
643         }
644
645
646         /*
647          * Regular packets have CONNECT followed by three streams
648          */
649         if (strcmp(tok, "CONNECT") == 0) {
650
651             /*
652              * Parse the three stream specifiers out of the packet.
653              */
654             for (i = 0; i < NSTREAMS; i++) {
655                 tok = strtok(NULL, " ");
656                 if (tok == NULL || strcmp(tok, streams[i].name) != 0) {
657                     extra = vstralloc("CONNECT token is \"",
658                                       tok ? tok : "(null)",
659                                       "\": expected \"",
660                                       streams[i].name,
661                                       "\"",
662                                       NULL);
663                     goto parse_error;
664                 }
665                 tok = strtok(NULL, " \n");
666                 if (tok == NULL || sscanf(tok, "%d", &ports[i]) != 1) {
667                     extra = vstralloc("CONNECT ",
668                                       streams[i].name,
669                                       " token is \"",
670                                       tok ? tok : "(null)",
671                                       "\": expected a port number",
672                                       NULL);
673                     goto parse_error;
674                 }
675             }
676             continue;
677         }
678
679         /*
680          * OPTIONS [options string] '\n'
681          */
682         if (strcmp(tok, "OPTIONS") == 0) {
683             tok = strtok(NULL, "\n");
684             if (tok == NULL) {
685                 extra = stralloc("OPTIONS token is missing");
686                 goto parse_error;
687             }
688 /*
689             tok_end = tok + strlen(tok);
690             while((p = strchr(tok, ';')) != NULL) {
691                 *p++ = '\0';
692 #define sc "features="
693                 if(strncmp(tok, sc, sizeof(sc)-1) == 0) {
694                     tok += sizeof(sc) - 1;
695 #undef sc
696                     am_release_feature_set(their_features);
697                     if((their_features = am_string_to_feature(tok)) == NULL) {
698                         errstr = newvstralloc(errstr,
699                                               "OPTIONS: bad features value: ",
700                                               tok,
701                                               NULL);
702                         goto parse_error;
703                     }
704                 }
705                 tok = p;
706             }
707 */
708             continue;
709         }
710 /*
711         extra = vstralloc("next token is \"",
712                           tok ? tok : "(null)",
713                           "\": expected \"CONNECT\", \"ERROR\" or \"OPTIONS\"",
714                           NULL);
715         goto parse_error;
716 */
717     }
718
719     /*
720      * Connect the streams to their remote ports
721      */
722     for (i = 0; i < NSTREAMS; i++) {
723 /*@i@*/ if (ports[i] == -1)
724             continue;
725         streams[i].fd = security_stream_client(sech, ports[i]);
726         if (streams[i].fd == NULL) {
727             errstr = newvstralloc(errstr,
728                         "[could not connect ", streams[i].name, " stream: ",
729                         security_geterror(sech), "]", NULL);
730             goto connect_error;
731         }
732     }
733     /*
734      * Authenticate the streams
735      */
736     for (i = 0; i < NSTREAMS; i++) {
737         if (streams[i].fd == NULL)
738             continue;
739         if (security_stream_auth(streams[i].fd) < 0) {
740             errstr = newvstralloc(errstr,
741                 "[could not authenticate ", streams[i].name, " stream: ",
742                 security_stream_geterror(streams[i].fd), "]", NULL);
743             goto connect_error;
744         }
745     }
746
747     /*
748      * The MESGFD and DATAFD streams are mandatory.  If we didn't get
749      * them, complain.
750      */
751     if (streams[MESGFD].fd == NULL) {
752         errstr = newstralloc(errstr, "[couldn't open MESG streams]");
753         goto connect_error;
754     }
755
756     /* everything worked */
757     *response_error = 0;
758     amindexd_alive = 1;
759     return;
760
761 parse_error:
762     errstr = newvstralloc(errstr,
763                           "[parse of reply message failed: ",
764                           extra ? extra : "(no additional information)",
765                           "]",
766                           NULL);
767     amfree(extra);
768     *response_error = 2;
769     return;
770
771 connect_error:
772     stop_amindexd();
773     *response_error = 1;
774 }
775
776 /*
777  * This is called when everything needs to shut down so event_loop()
778  * will exit.
779  */
780 void
781 stop_amindexd(void)
782 {
783     int i;
784
785     amindexd_alive = 0;
786     for (i = 0; i < NSTREAMS; i++) {
787         if (streams[i].fd != NULL) {
788             security_stream_close(streams[i].fd);
789             streams[i].fd = NULL;
790         }
791     }
792 }
793
794 char *
795 amindexd_client_get_security_conf(
796     char *      string,
797     void *      arg)
798 {
799     (void)arg;  /* Quiet unused parameter warning */
800
801     if(!string || !*string)
802         return(NULL);
803
804     if(strcmp(string, "auth")==0) {
805         return(client_getconf_str(CLN_AUTH));
806     }
807     else if(strcmp(string, "ssh_keys")==0) {
808         return(client_getconf_str(CLN_SSH_KEYS));
809     }
810     return(NULL);
811 }