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