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