Imported Upstream version 2.4.5
[debian/amanda] / client-src / amandad.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 1991-1998 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  * Author: James da Silva, Systems Design and Analysis Group
24  *                         Computer Science Department
25  *                         University of Maryland at College Park
26  */
27 /*
28  * $Id: amandad.c,v 1.32.2.4.4.1.2.6.2.1 2004/02/13 14:01:07 martinea Exp $
29  *
30  * handle client-host side of Amanda network communications, including
31  * security checks, execution of the proper service, and acking the
32  * master side
33  */
34
35 #include "amanda.h"
36 #include "clock.h"
37 #include "dgram.h"
38 #include "amfeatures.h"
39 #include "version.h"
40 #include "protocol.h"
41 #include "util.h"
42 #include "client_util.h"
43
44 #define RECV_TIMEOUT 30
45 #define ACK_TIMEOUT  10         /* XXX should be configurable */
46 #define MAX_RETRIES   5
47
48 /* 
49  * Here are the services that we understand.
50  */
51 struct service_s {
52     char *name;
53     int flags;
54 #       define NONE             0
55 #       define IS_INTERNAL      1       /* service is internal */
56 #       define NEED_KEYPIPE     2       /* pass kerberos key in pipe */
57 #       define NO_AUTH          4       /* doesn't need authentication */
58 } service_table[] = {
59     { "noop",           IS_INTERNAL },
60     { "sendsize",       NONE },
61     { "sendbackup",     NEED_KEYPIPE },
62     { "sendfsinfo",     NONE },
63     { "selfcheck",      NONE },
64     { NULL, NONE }
65 };
66
67
68 int max_retry_count = MAX_RETRIES;
69 int ack_timeout     = ACK_TIMEOUT;
70
71 #ifdef KRB4_SECURITY
72 #  include "amandad-krb4.c"
73 #endif
74
75 /* local functions */
76 int main P((int argc, char **argv));
77 void sendack P((pkt_t *hdr, pkt_t *msg));
78 void sendnak P((pkt_t *hdr, pkt_t *msg, char *str));
79 void setup_rep P((pkt_t *hdr, pkt_t *msg, int partial_rep));
80 char *strlower P((char *str));
81
82 int main(argc, argv)
83 int argc;
84 char **argv;
85 {
86     int n;
87     int fd;
88     char *errstr = NULL;
89     unsigned long malloc_hist_1, malloc_size_1;
90     unsigned long malloc_hist_2, malloc_size_2;
91     char *pgm = "amandad";              /* in case argv[0] is not set */
92
93     /* in_msg: The first incoming request.
94        dup_msg: Any other incoming message.
95        out_msg: Standard, i.e. non-repeated, ACK and REP.
96        rej_msg: Any other outgoing message.
97      */
98     pkt_t in_msg, out_msg, out_pmsg, rej_msg, dup_msg;
99     char *cmd = NULL, *base = NULL;
100     char *noop_file = NULL;
101     char **vp;
102     char *s;
103     ssize_t s_len;
104     int retry_count, rc, reqlen;
105     int req_pipe[2], rep_pipe[2];
106     int dglen = 0;
107     char number[NUM_STR_SIZE];
108     am_feature_t *our_features = NULL;
109     char *our_feature_string = NULL;
110     int send_partial_reply = 0;
111
112     struct service_s *servp;
113     fd_set insock;
114
115     for(fd = 3; fd < FD_SETSIZE; fd++) {
116         /*
117          * Make sure nobody spoofs us with a lot of extra open files
118          * that would cause an open we do to get a very high file
119          * descriptor, which in turn might be used as an index into
120          * an array (e.g. an fd_set).
121          */
122         close(fd);
123     }
124
125     safe_cd();
126
127     /*
128      * When called via inetd, it is not uncommon to forget to put the
129      * argv[0] value on the config line.  On some systems (e.g. Solaris)
130      * this causes argv and/or argv[0] to be NULL, so we have to be
131      * careful getting our name.
132      */
133     if (argc >= 1 && argv != NULL && argv[0] != NULL) {
134         if((pgm = strrchr(argv[0], '/')) != NULL) {
135             pgm++;
136         } else {
137             pgm = argv[0];
138         }
139     }
140
141     set_pname(pgm);
142
143     malloc_size_1 = malloc_inuse(&malloc_hist_1);
144
145     erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
146
147 #ifdef FORCE_USERID
148
149     /* we'd rather not run as root */
150     if(geteuid() == 0) {
151 #ifdef KRB4_SECURITY
152         if(client_uid == (uid_t) -1) {
153             error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
154         }
155
156         /*
157          * if we're using kerberos security, we'll need to be root in
158          * order to get at the machine's srvtab entry, so we hang on to
159          * some root privledges for now.  We give them up entirely later.
160          */
161         setegid(client_gid);
162         seteuid(client_uid);
163 #else
164         initgroups(CLIENT_LOGIN, client_gid);
165         setgid(client_gid);
166         setuid(client_uid);
167 #endif  /* KRB4_SECURITY */
168     }
169 #endif  /* FORCE_USERID */
170
171     /* initialize */
172
173     dbopen();
174     {
175         int db_fd = dbfd();
176         if(db_fd != -1) {
177             dup2(db_fd, 1);
178             dup2(db_fd, 2);
179         }
180     }
181
182     startclock();
183
184     dbprintf(("%s: version %s\n", get_pname(), version()));
185     for(vp = version_info; *vp != NULL; vp++)
186         dbprintf(("%s: %s", debug_prefix(NULL), *vp));
187
188     if (! (argc >= 1 && argv != NULL && argv[0] != NULL)) {
189         dbprintf(("%s: WARNING: argv[0] not defined: check inetd.conf\n",
190                   debug_prefix(NULL)));
191     }
192
193     our_features = am_init_feature_set();
194     our_feature_string = am_feature_to_string(our_features);
195
196     dgram_zero(&in_msg.dgram); 
197     dgram_socket(&in_msg.dgram, 0);
198
199     dgram_zero(&dup_msg.dgram);
200     dgram_socket(&dup_msg.dgram, 0);
201
202     dgram_zero(&out_msg.dgram);
203     dgram_socket(&out_msg.dgram, 0);
204
205     dgram_zero(&out_pmsg.dgram);
206     dgram_socket(&out_pmsg.dgram, 0);
207
208     dgram_zero(&rej_msg.dgram);
209     dgram_socket(&rej_msg.dgram, 0);
210
211     dgram_zero(&rej_msg.dgram);
212     dgram_socket(&rej_msg.dgram, 0);
213
214     /* set up input and response pipes */
215
216 #ifdef KRB4_SECURITY
217     if(argc >= 2 && strcmp(argv[1], "-krb4") == 0) {
218         krb4_auth = 1;
219         dbprintf(("%s: using krb4 security\n", debug_prefix(NULL)));
220     }
221     else {
222         dbprintf(("%s: using bsd security\n", debug_prefix(NULL)));
223         krb4_auth = 0;
224     }
225 #endif
226
227     /* get request packet and attempt to parse it */
228
229     if((n = dgram_recv(&in_msg.dgram, RECV_TIMEOUT, &in_msg.peer)) <= 0) {
230         char *s;
231
232         if (n == 0) {
233             s = "timeout";
234         } else {
235             s = strerror(errno);
236         }
237         error("error receiving message: %s", s);
238     }
239
240     dbprintf(("%s: got packet:\n--------\n%s--------\n\n",
241               debug_prefix_time(NULL), in_msg.dgram.cur));
242
243     parse_pkt_header(&in_msg);
244     if(in_msg.type != P_REQ && in_msg.type != P_NAK && in_msg.type != P_ACK) {
245         /* XXX */
246         dbprintf(("%s: this is a %s packet, nak'ing it\n", 
247                   debug_prefix_time(NULL),
248                   in_msg.type == P_BOGUS? "bogus" : "unexpected"));
249         if(in_msg.type != P_BOGUS) {
250             parse_errmsg = newvstralloc(parse_errmsg,"unexpected ",
251                 in_msg.type == P_ACK? "ack ":
252                 in_msg.type == P_REP? "rep ": "",
253                 "packet", NULL);
254         }
255         sendnak(&in_msg, &rej_msg, parse_errmsg);
256         dbclose();
257         return 1;
258     }
259     if(in_msg.type != P_REQ) {
260         dbprintf(("%s: strange, this is not a request packet\n",
261                   debug_prefix_time(NULL)));
262         dbclose();
263         return 1;
264     }
265
266     /* lookup service */
267
268     for(servp = service_table; servp->name != NULL; servp++)
269         if(strcmp(servp->name, in_msg.service) == 0) break;
270
271     if(servp->name == NULL) {
272         errstr = newstralloc2(errstr, "unknown service: ", in_msg.service);
273         sendnak(&in_msg, &rej_msg, errstr);
274         dbclose();
275         return 1;
276     }
277
278     if((servp->flags & IS_INTERNAL) != 0) {
279         cmd = stralloc(servp->name);
280     } else {
281         base = newstralloc(base, servp->name);
282         cmd = newvstralloc(cmd, libexecdir, "/", base, versionsuffix(), NULL);
283
284         if(access(cmd, X_OK) == -1) {
285             dbprintf(("%s: execute access to \"%s\" denied\n",
286                       debug_prefix_time(NULL), cmd));
287             errstr = newvstralloc(errstr,
288                                   "service ", base, " unavailable",
289                                   NULL);
290             amfree(base);
291             sendnak(&in_msg, &rej_msg, errstr);
292             dbclose();
293             return 1;
294         }
295         amfree(base);
296     }
297
298     /* everything looks ok initially, send ACK */
299
300     sendack(&in_msg, &out_msg);
301
302     /* 
303      * handle security check: this could take a long time, so it is 
304      * done after the initial ack.
305      */
306
307 #if defined(KRB4_SECURITY)
308     /*
309      * we need to be root to access the srvtab file, but only if we started
310      * out that way.
311      */
312     setegid(getgid());
313     seteuid(getuid());
314 #endif /* KRB4_SECURITY */
315
316     amfree(errstr);
317     if(!(servp->flags & NO_AUTH)
318        && !security_ok(&in_msg.peer, in_msg.security, in_msg.cksum, &errstr)) {
319         /* XXX log on authlog? */
320         setup_rep(&in_msg, &out_msg, 0);
321         ap_snprintf(out_msg.dgram.cur,
322                     sizeof(out_msg.dgram.data)-out_msg.dgram.len,
323                     "ERROR %s\n", errstr);
324         out_msg.dgram.len = strlen(out_msg.dgram.data);
325         goto send_response;
326     }
327
328 #if defined(KRB4_SECURITY) && defined(FORCE_USERID)
329
330     /*
331      * we held on to a root uid earlier for accessing files; since we're
332      * done doing anything requiring root, we can completely give it up.
333      */
334
335     if(geteuid() == 0) {
336         if(client_uid == (uid_t) -1) {
337             error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
338         }
339         initgroups(CLIENT_LOGIN, client_gid);
340         setgid(client_gid);
341         setuid(client_uid);
342     }
343
344 #endif  /* KRB4_SECURITY && FORCE_USERID */
345
346     dbprintf(("%s: running service \"%s\"\n", debug_prefix_time(NULL), cmd));
347
348     if(strcmp(servp->name, "noop") == 0) {
349         ap_snprintf(number, sizeof(number), "%ld", (long)getpid());
350         noop_file = vstralloc(AMANDA_TMPDIR,
351                               "/",
352                               get_pname(),
353                               ".noop.",
354                               number,
355                               NULL);
356         rep_pipe[0] = open(noop_file, O_RDWR|O_EXCL|O_CREAT);
357         if(rep_pipe[0] < 0) {
358             error("cannot open \"%s\": %s", noop_file, strerror(errno));
359         }
360         (void)unlink(noop_file);
361         s = vstralloc("OPTIONS features=", our_feature_string, ";\n", NULL);
362         s_len = strlen(s);
363         if(fullwrite(rep_pipe[0], s, s_len) != s_len) {
364             error("cannot write %d bytes to %s", s_len, noop_file);
365         }
366         amfree(noop_file);
367         amfree(s);
368         (void)lseek(rep_pipe[0], (off_t)0, SEEK_SET);
369     } else {
370         if(strcmp(servp->name, "sendsize") == 0) {
371             if(strncmp(in_msg.dgram.cur,"OPTIONS ",8) == 0) {
372                 g_option_t *g_options;
373                 char *option_str, *p;
374
375                 option_str = stralloc(in_msg.dgram.cur+8);
376                 p = strchr(option_str,'\n');
377                 if(p) *p = '\0';
378
379                 g_options = parse_g_options(option_str, 0);
380                 if(am_has_feature(g_options->features, fe_partial_estimate)) {
381                     send_partial_reply = 1;
382                 }
383                 amfree(option_str);
384             }
385         }
386         if(pipe(req_pipe) == -1 || pipe(rep_pipe) == -1)
387             error("pipe: %s", strerror(errno));
388
389         /* spawn first child to handle the request */
390
391         switch(fork()) {
392         case -1: error("could not fork for %s: %s", cmd, strerror(errno));
393
394         default:                /* parent */
395
396             break; 
397
398         case 0:         /* child */
399
400             aclose(req_pipe[1]); 
401             aclose(rep_pipe[0]);
402
403             dup2(req_pipe[0], 0);
404             dup2(rep_pipe[1], 1);
405
406             /* modification by BIS@BBN 4/25/2003:
407              * close these file descriptors BEFORE doing pipe magic
408              * for transferring session key; inside transfer_session_key
409              * is a dup2 to move a pipe to KEY_PIPE, which collided
410              * with req_pipe[0]; when req_pipe[0] was closed after the
411              * call to transfer_session_key, then KEY_PIPE ended up
412              * being closed. */
413             aclose(req_pipe[0]);
414             aclose(rep_pipe[1]);
415
416 #ifdef  KRB4_SECURITY
417             transfer_session_key();
418 #endif
419
420             /* run service */
421
422             execle(cmd, cmd, NULL, safe_env());
423             error("could not exec %s: %s", cmd, strerror(errno));
424         }
425         amfree(cmd);
426
427         aclose(req_pipe[0]);
428         aclose(rep_pipe[1]);
429
430         /* spawn second child to handle writing the packet to the first child */
431
432         switch(fork()) {
433         case -1: error("could not fork for %s: %s", cmd, strerror(errno));
434
435         default:                /* parent */
436
437             break;
438
439         case 0:         /* child */
440
441             aclose(rep_pipe[0]);
442             reqlen = strlen(in_msg.dgram.cur);
443             if((rc = fullwrite(req_pipe[1], in_msg.dgram.cur, reqlen)) != reqlen) {
444                 if(rc < 0) {
445                     error("write to child pipe: %s", strerror(errno));
446                 } else {
447                     error("write to child pipe: %d instead of %d", rc, reqlen);
448                 }
449             }
450             aclose(req_pipe[1]);
451             exit(0);
452         }
453
454         aclose(req_pipe[1]);
455     }
456
457     setup_rep(&in_msg, &out_msg, 0);
458     if(send_partial_reply) {
459         setup_rep(&in_msg, &out_pmsg, 1);
460     }
461 #ifdef KRB4_SECURITY
462     add_mutual_authenticator(&out_msg.dgram);
463     add_mutual_authenticator(&out_pmsg.dgram);
464 #endif
465
466     while(1) {
467
468         FD_ZERO(&insock);
469         FD_SET(rep_pipe[0], &insock);
470
471         if((servp->flags & IS_INTERNAL) != 0) {
472             n = 0;
473         } else {
474             FD_SET(0, &insock);
475             n = select(rep_pipe[0] + 1,
476                        (SELECT_ARG_TYPE *)&insock,
477                        NULL,
478                        NULL,
479                        NULL);
480         }
481         if(n < 0) {
482             error("select failed: %s", strerror(errno));
483         }
484
485         if(FD_ISSET(rep_pipe[0], &insock)) {
486             if(dglen >= MAX_DGRAM) {
487                 error("more than %d bytes received from child", MAX_DGRAM);
488             }
489             rc = read(rep_pipe[0], out_msg.dgram.cur+dglen, MAX_DGRAM-dglen);
490             if(rc <= 0) {
491                 if (rc < 0) {
492                     error("reading response pipe: %s", strerror(errno));
493                 }
494                 break;
495             }
496             else {
497                 if(send_partial_reply) {
498                     strncpy(out_pmsg.dgram.cur+dglen, out_msg.dgram.cur+dglen, rc);
499                     out_pmsg.dgram.len += rc;
500                     out_pmsg.dgram.data[out_pmsg.dgram.len] = '\0';
501                     dbprintf(("%s: sending PREP packet:\n----\n%s----\n\n",
502                               debug_prefix_time(NULL), out_pmsg.dgram.data));
503                     dgram_send_addr(in_msg.peer, &out_pmsg.dgram);
504                 }
505                 dglen += rc;
506             }
507         }
508         if(!FD_ISSET(0,&insock))
509             continue;
510
511         if((n = dgram_recv(&dup_msg.dgram, RECV_TIMEOUT, &dup_msg.peer)) <= 0) {
512             char *s;
513
514             if (n == 0) {
515                 s = "timeout";
516             } else {
517                 s = strerror(errno);
518             }
519             error("error receiving message: %s", s);
520         }
521
522         /* 
523          * Under normal conditions, the master will resend the REQ packet
524          * to be sure we are still alive.  It expects an ACK back right away.
525          *
526          * XXX- Arguably we should parse and security check the new packet, 
527          * only sending an ACK if it passes and the request is identical to
528          * the original one.  However, that's too much work for now. :-) 
529          *
530          * It should suffice to ACK whenever the sender is identical.
531          */
532         dbprintf(("%s: got packet:\n----\n%s----\n\n",
533                   debug_prefix_time(NULL), dup_msg.dgram.data));
534         parse_pkt_header(&dup_msg);
535         if(dup_msg.peer.sin_addr.s_addr == in_msg.peer.sin_addr.s_addr &&
536            dup_msg.peer.sin_port == in_msg.peer.sin_port) {
537             if(dup_msg.type == P_REQ) {
538                 dbprintf(("%s: received dup P_REQ packet, ACKing it\n",
539                           debug_prefix_time(NULL)));
540                 sendack(&in_msg, &rej_msg);
541             }
542             else {
543                 dbprintf(("%s: it is not a P_REQ, ignoring it\n",
544                           debug_prefix_time(NULL)));
545             }
546         }
547         else {
548             dbprintf(("%s: received other packet, NAKing it\n",
549                       debug_prefix_time(NULL)));
550             dbprintf(("  addr: peer %s dup %s, port: peer %d dup %d\n",
551                       inet_ntoa(in_msg.peer.sin_addr),
552                       inet_ntoa(dup_msg.peer.sin_addr),
553                       (int)ntohs(in_msg.peer.sin_port),
554                       (int)ntohs(dup_msg.peer.sin_port)));
555             /* XXX dup_msg filled in? */
556             sendnak(&dup_msg, &rej_msg, "amandad busy");
557         }
558
559     }
560
561     /* XXX reap child?  log if non-zero status?  don't respond if non zero? */
562     /* setup header for out_msg */
563
564     out_msg.dgram.len += dglen;
565     out_msg.dgram.data[out_msg.dgram.len] = '\0';
566     aclose(rep_pipe[0]);
567
568 send_response:
569
570     retry_count = 0;
571
572     while(retry_count < max_retry_count) {
573         if(!retry_count)
574             dbprintf(("%s: sending REP packet:\n----\n%s----\n\n",
575                       debug_prefix_time(NULL), out_msg.dgram.data));
576         dgram_send_addr(in_msg.peer, &out_msg.dgram);
577         if((n = dgram_recv(&dup_msg.dgram, ack_timeout, &dup_msg.peer)) <= 0) {
578             char *s;
579
580             if (n == 0) {
581                 s = "timeout";
582             } else {
583                 s = strerror(errno);
584             }
585
586             /* timed out or error, try again */
587             retry_count++;
588
589             dbprintf(("%s: waiting for ack: %s", debug_prefix_time(NULL), s));
590             if(retry_count < max_retry_count) 
591                 dbprintf((", retrying\n"));
592             else 
593                 dbprintf((", giving up!\n"));
594
595             continue;
596         }
597         dbprintf(("%s: got packet:\n----\n%s----\n\n",
598                   debug_prefix_time(NULL), dup_msg.dgram.data));
599         parse_pkt_header(&dup_msg);
600
601         
602         if(dup_msg.peer.sin_addr.s_addr == in_msg.peer.sin_addr.s_addr &&
603            dup_msg.peer.sin_port == in_msg.peer.sin_port) {
604             if(dup_msg.type == P_ACK)
605                 break;
606             else
607                 dbprintf(("%s: it is not an ack\n", debug_prefix_time(NULL)));
608         }
609         else {
610             dbprintf(("%s: weird, it is not a proper ack\n",
611                       debug_prefix_time(NULL)));
612             dbprintf(("  addr: peer %s dup %s, port: peer %d dup %d\n",
613                       inet_ntoa(in_msg.peer.sin_addr),
614                       inet_ntoa(dup_msg.peer.sin_addr),
615                       (int)ntohs(in_msg.peer.sin_port),
616                       (int)ntohs(dup_msg.peer.sin_port)));
617         }               
618     }
619     /* XXX log if retry count exceeded */
620
621     amfree(cmd);
622     amfree(noop_file);
623     amfree(our_feature_string);
624     am_release_feature_set(our_features);
625     our_features = NULL;
626     malloc_size_2 = malloc_inuse(&malloc_hist_2);
627
628     if(malloc_size_1 != malloc_size_2) {
629 #if defined(USE_DBMALLOC)
630         malloc_list(dbfd(), malloc_hist_1, malloc_hist_2);
631 #endif
632     }
633
634     dbclose();
635     return 0;
636 }
637
638
639 /* -------- */
640
641 void sendack(hdr, msg)
642 pkt_t *hdr;
643 pkt_t *msg;
644 {
645     /* XXX this isn't very safe either: handle could be bogus */
646     ap_snprintf(msg->dgram.data, sizeof(msg->dgram.data),
647                 "Amanda %d.%d ACK HANDLE %s SEQ %d\n",
648                 VERSION_MAJOR, VERSION_MINOR,
649                 hdr->handle ? hdr->handle : "",
650                 hdr->sequence);
651     msg->dgram.len = strlen(msg->dgram.data);
652     dbprintf(("%s: sending ack:\n----\n%s----\n\n",
653               debug_prefix_time(NULL), msg->dgram.data));
654     dgram_send_addr(hdr->peer, &msg->dgram);
655 }
656
657 void sendnak(hdr, msg, str)
658 pkt_t *hdr;
659 pkt_t *msg;
660 char *str;
661 {
662     /* XXX this isn't very safe either: handle could be bogus */
663     ap_snprintf(msg->dgram.data, sizeof(msg->dgram.data),
664                 "Amanda %d.%d NAK HANDLE %s SEQ %d\nERROR %s\n",
665                 VERSION_MAJOR, VERSION_MINOR,
666                 hdr->handle ? hdr->handle : "",
667                 hdr->sequence, str ? str : "UNKNOWN");
668
669     msg->dgram.len = strlen(msg->dgram.data);
670     dbprintf(("%s: sending nack:\n----\n%s----\n\n",
671               debug_prefix_time(NULL), msg->dgram.data));
672     dgram_send_addr(hdr->peer, &msg->dgram);
673 }
674
675 void setup_rep(hdr, msg, partial_rep)
676 pkt_t *hdr;
677 pkt_t *msg;
678 int partial_rep;
679 {
680     /* XXX this isn't very safe either: handle could be bogus */
681     ap_snprintf(msg->dgram.data, sizeof(msg->dgram.data),
682                 "Amanda %d.%d %s HANDLE %s SEQ %d\n",
683                 VERSION_MAJOR, VERSION_MINOR,
684                 partial_rep == 0 ? "REP" : "PREP", 
685                 hdr->handle ? hdr->handle : "",
686                 hdr->sequence);
687
688     msg->dgram.len = strlen(msg->dgram.data);
689     msg->dgram.cur = msg->dgram.data + msg->dgram.len;
690
691 }
692
693 /* -------- */
694
695 char *strlower(str)
696 char *str;
697 {
698     char *s;
699     for(s=str; *s; s++)
700         if(isupper((int)*s)) *s = tolower(*s);
701     return str;
702 }