+++ /dev/null
-/*
- * Amanda, The Advanced Maryland Automatic Network Disk Archiver
- * Copyright (c) 1991-1999 University of Maryland at College Park
- * All Rights Reserved.
- *
- * Permission to use, copy, modify, distribute, and sell this software and its
- * documentation for any purpose is hereby granted without fee, provided that
- * the above copyright notice appear in all copies and that both that
- * copyright notice and this permission notice appear in supporting
- * documentation, and that the name of U.M. not be used in advertising or
- * publicity pertaining to distribution of the software without specific,
- * written prior permission. U.M. makes no representations about the
- * suitability of this software for any purpose. It is provided "as is"
- * without express or implied warranty.
- *
- * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
- * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
- * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
- * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * Authors: the Amanda Development Team. Its members are listed in a
- * file named AUTHORS, in the root directory of this distribution.
- */
-
-/*
- * $Id: amandad.c,v 1.62.2.2 2006/05/08 11:50:16 martinea Exp $
- *
- * handle client-host side of Amanda network communications, including
- * security checks, execution of the proper service, and acking the
- * master side
- */
-
-/*#define AMANDAD_DEBUG*/
-
-#include "amanda.h"
-#include "amandad.h"
-#include "clock.h"
-#include "event.h"
-#include "amfeatures.h"
-#include "packet.h"
-#include "version.h"
-#include "queue.h"
-#include "security.h"
-#include "stream.h"
-#include "util.h"
-#include "client_util.h"
-
-#define REP_TIMEOUT (6*60*60) /* secs for service to reply */
-#define ACK_TIMEOUT 10 /* XXX should be configurable */
-#define MAX_REP_RETRIES 5
-
-/*
- * These are the actions for entering the state machine
- */
-typedef enum { A_START, A_RECVPKT, A_RECVREP, A_PENDING, A_FINISH, A_CONTINUE,
- A_SENDNAK, A_TIMEOUT } action_t;
-
-/*
- * This is a state in the state machine. It is a function pointer to
- * the function that actually implements the state.
- */
-struct active_service;
-typedef action_t (*state_t) P((struct active_service *, action_t, pkt_t *));
-
-/*
- * This structure describes an active running service.
- *
- * An active service is something running that we have received
- * a request for. This structure holds info on that service, including
- * file descriptors for data, etc, as well as the security handle
- * for communications with the amanda server.
- */
-struct active_service {
- char *cmd; /* name of command we ran */
- char *arguments; /* arguments we sent it */
- security_handle_t *security_handle; /* remote server */
- state_t state; /* how far this has progressed */
- pid_t pid; /* pid of subprocess */
- int send_partial_reply; /* send PREP packet */
- int reqfd; /* pipe to write requests */
- int repfd; /* pipe to read replies */
- event_handle_t *ev_repfd; /* read event handle for repfd */
- event_handle_t *ev_reptimeout; /* timeout for rep data */
- pkt_t rep_pkt; /* rep packet we're sending out */
- char repbuf[MAX_PACKET]; /* buffer to read the rep into */
- int repbufsize; /* length of repbuf */
- int repretry; /* times we'll retry sending the rep */
- /*
- * General user streams to the process, and their equivalent
- * network streams.
- */
- struct datafd_handle {
- int fd; /* pipe to child process */
- event_handle_t *ev_handle; /* it's read event handle */
- security_stream_t *netfd; /* stream to amanda server */
- struct active_service *as; /* pointer back to our enclosure */
- } data[DATA_FD_COUNT];
- char databuf[NETWORK_BLOCK_BYTES]; /* buffer to relay netfd data in */
- TAILQ_ENTRY(active_service) tq; /* queue handle */
-};
-
-/*
- * Here are the services that we allow.
- */
-static const char *services[] = {
- "noop",
- "sendsize",
- "sendbackup",
- "selfcheck",
-};
-#define NSERVICES (sizeof(services) / sizeof(services[0]))
-
-/*
- * Queue of outstanding requests that we are running.
- */
-static struct {
- TAILQ_HEAD(, active_service) tailq;
- int qlength;
-} serviceq = {
- TAILQ_HEAD_INITIALIZER(serviceq.tailq), 0
-};
-
-/*
- * Data for dbmalloc to check for memory leaks
- */
-#ifdef USE_DBMALLOC
-static struct {
- struct {
- unsigned long size, hist;
- } start, end;
-} dbmalloc_info;
-#endif
-
-int ack_timeout = ACK_TIMEOUT;
-
-int main P((int argc, char **argv));
-
-static int allocstream P((struct active_service *, int));
-static void exit_check P((void *));
-static void protocol_accept P((security_handle_t *, pkt_t *));
-static void state_machine P((struct active_service *, action_t, pkt_t *));
-
-static action_t s_sendack P((struct active_service *, action_t, pkt_t *));
-static action_t s_repwait P((struct active_service *, action_t, pkt_t *));
-static action_t s_processrep P((struct active_service *, action_t, pkt_t *));
-static action_t s_sendrep P((struct active_service *, action_t, pkt_t *));
-static action_t s_ackwait P((struct active_service *, action_t, pkt_t *));
-
-static void repfd_recv P((void *));
-static void timeout_repfd P((void *));
-static void protocol_recv P((void *, pkt_t *, security_status_t));
-static void process_netfd P((void *));
-static struct active_service *service_new P((security_handle_t *,
- const char *, const char *));
-static void service_delete P((struct active_service *));
-static int writebuf P((struct active_service *, const void *, size_t));
-static int do_sendpkt P((security_handle_t *handle, pkt_t *pkt));
-
-#ifdef AMANDAD_DEBUG
-static const char *state2str P((state_t));
-static const char *action2str P((action_t));
-#endif
-
-int
-main(argc, argv)
- int argc;
- char **argv;
-{
- int i, in, out;
- const security_driver_t *secdrv;
- int no_exit = 0;
- char *pgm = "amandad"; /* in case argv[0] is not set */
-
- safe_fd(-1, 0);
- safe_cd();
-
- if(argv == NULL) {
- error("argv == NULL\n");
- }
-
- /*
- * When called via inetd, it is not uncommon to forget to put the
- * argv[0] value on the config line. On some systems (e.g. Solaris)
- * this causes argv and/or argv[0] to be NULL, so we have to be
- * careful getting our name.
- */
- if (argc >= 1 && argv != NULL && argv[0] != NULL) {
- if((pgm = strrchr(argv[0], '/')) != NULL) {
- pgm++;
- } else {
- pgm = argv[0];
- }
- }
-
- set_pname(pgm);
-
- /* Don't die when child closes pipe */
- signal(SIGPIPE, SIG_IGN);
-
-#ifdef USE_DBMALLOC
- dbmalloc_info.start.size = malloc_inuse(&dbmalloc_info.start.hist);
-#endif
-
- erroutput_type = (ERR_INTERACTIVE|ERR_SYSLOG);
-
-#ifdef FORCE_USERID
- /* we'd rather not run as root */
- if (geteuid() == 0) {
- if(client_uid == (uid_t) -1) {
- error("error [cannot find user %s in passwd file]\n", CLIENT_LOGIN);
- }
- initgroups(CLIENT_LOGIN, client_gid);
- setgid(client_gid);
- setegid(client_gid);
- seteuid(client_uid);
- }
-#endif /* FORCE_USERID */
-
- /*
- * ad-hoc argument parsing
- *
- * We accept -auth=[authentication type]
- * -no-exit
-#ifdef AMANDAD_DEBUG
- * -tcp=[port]
- * -udp=[port]
-#endif
- */
- secdrv = NULL;
- in = 0; out = 1; /* default to stdin/stdout */
- for (i = 1; i < argc; i++) {
- /*
- * accept -krb4 as an alias for -auth=krb4 (for compatibility)
- */
- if (strcmp(argv[i], "-krb4") == 0) {
- argv[i] = "-auth=krb4";
- /* FALLTHROUGH */
- }
-
- /*
- * Get a driver for a security type specified after -auth=
- */
- if (strncmp(argv[i], "-auth=", strlen("-auth=")) == 0) {
- argv[i] += strlen("-auth=");
- secdrv = security_getdriver(argv[i]);
- if (secdrv == NULL)
- error("no driver for security type '%s'", argv[i]);
- continue;
- }
-
- /*
- * If -no-exit is specified, always run even after requests have
- * been satisfied.
- */
- if (strcmp(argv[i], "-no-exit") == 0) {
- no_exit = 1;
- continue;
- }
-
-#ifdef AMANDAD_DEBUG
- /*
- * Allow us to directly bind to a udp port for debugging.
- * This may only apply to some security types.
- */
- if (strncmp(argv[i], "-udp=", strlen("-udp=")) == 0) {
- struct sockaddr_in sin;
-
- argv[i] += strlen("-udp=");
- in = out = socket(AF_INET, SOCK_DGRAM, 0);
- if (in < 0)
- error("can't create dgram socket: %s\n", strerror(errno));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
- sin.sin_port = htons(atoi(argv[i]));
- if (bind(in, (struct sockaddr *)&sin, sizeof(sin)) < 0)
- error("can't bind to port %d: %s\n", atoi(argv[i]),
- strerror(errno));
- }
- /*
- * Ditto for tcp ports.
- */
- if (strncmp(argv[i], "-tcp=", strlen("-tcp=")) == 0) {
- struct sockaddr_in sin;
- int sock, n;
-
- argv[i] += strlen("-tcp=");
- sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock < 0)
- error("can't create tcp socket: %s\n", strerror(errno));
- n = 1;
- setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&n, sizeof(n));
- sin.sin_family = AF_INET;
- sin.sin_addr.s_addr = INADDR_ANY;
- sin.sin_port = htons(atoi(argv[i]));
- if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0)
- error("can't bind to port %d: %s\n", atoi(argv[i]),
- strerror(errno));
- listen(sock, 10);
- n = sizeof(sin);
- in = out = accept(sock, (struct sockaddr *)&sin, &n);
- }
-#endif
- }
-
- /*
- * If no security type specified, use BSD
- */
- if (secdrv == NULL) {
- secdrv = security_getdriver("BSD");
- if (secdrv == NULL)
- error("no driver for default security type 'BSD'");
- }
-
- /* initialize */
-
- dbopen();
- {
- /* this lameness is for error() */
- int db_fd = dbfd();
- if(db_fd != -1) {
- dup2(db_fd, 2);
- }
- }
-
- startclock();
-
- dbprintf(("%s: version %s\n", get_pname(), version()));
- for (i = 0; version_info[i] != NULL; i++) {
- dbprintf(("%s: %s", debug_prefix(NULL), version_info[i]));
- }
-
- if (! (argc >= 1 && argv != NULL && argv[0] != NULL)) {
- dbprintf(("%s: WARNING: argv[0] not defined: check inetd.conf\n",
- debug_prefix(NULL)));
- }
-
- /*
- * Schedule to call protocol_accept() when new security handles
- * are created on stdin.
- */
- security_accept(secdrv, in, out, protocol_accept);
-
- /*
- * Schedule an event that will try to exit every 30 seconds if there
- * are no requests outstanding.
- */
- (void)event_register(30, EV_TIME, exit_check, &no_exit);
-
- /*
- * Call event_loop() with an arg of 0, telling it to block until all
- * events are completed.
- */
- event_loop(0);
-
- /* NOTREACHED */
- exit(1); /* appease gcc/lint */
-}
-
-/*
- * This runs periodically and checks to see if we have any active services
- * still running. If we don't, then we quit.
- */
-static void
-exit_check(cookie)
- void *cookie;
-{
- int no_exit;
-
- assert(cookie != NULL);
- no_exit = *(int *)cookie;
-
- /*
- * If things are still running, then don't exit.
- */
- if (serviceq.qlength > 0)
- return;
-
- /*
- * If the caller asked us to never exit, then we're done
- */
- if (no_exit)
- return;
-
-#ifdef USE_DBMALLOC
- dbmalloc_info.end.size = malloc_inuse(&dbmalloc_info.end.hist);
-
- if (dbmalloc_info.start.size != dbmalloc_info.end.size) {
- malloc_list(dbfd(), dbmalloc_info.start.hist,
- dbmalloc_info.end.hist);
- }
-#endif
-
- dbclose();
- exit(0);
-}
-
-/*
- * Handles new incoming protocol handles. This is a callback for
- * security_accept(), which gets called when new handles are detected.
- */
-static void
-protocol_accept(handle, pkt)
- security_handle_t *handle;
- pkt_t *pkt;
-{
- pkt_t pkt_out;
- struct active_service *as;
- char *pktbody, *tok, *service, *arguments;
- int i;
-
- /*
- * If pkt is NULL, then there was a problem with the new connection.
- */
- if (pkt == NULL) {
- dbprintf(("%s: accept error: %s\n",
- debug_prefix_time(NULL), security_geterror(handle)));
- pkt_init(&pkt_out, P_NAK, "ERROR %s\n", security_geterror(handle));
- do_sendpkt(handle, &pkt_out);
- security_close(handle);
- return;
- }
-
- dbprintf(("%s: accept recv %s pkt:\n<<<<<\n%s>>>>>\n",
- debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
-
- /*
- * If this is not a REQ packet, just forget about it.
- */
- if (pkt->type != P_REQ) {
- dbprintf(("%s: received unexpected %s packet:\n<<<<<\n%s>>>>>\n\n",
- debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
- security_close(handle);
- return;
- }
-
- pktbody = service = arguments = NULL;
- as = NULL;
-
- /*
- * Parse out the service and arguments
- */
-
- pktbody = stralloc(pkt->body);
-
- tok = strtok(pktbody, " ");
- if (tok == NULL)
- goto badreq;
- if (strcmp(tok, "SERVICE") != 0)
- goto badreq;
-
- tok = strtok(NULL, " \n");
- if (tok == NULL)
- goto badreq;
- service = stralloc(tok);
-
- /* we call everything else 'arguments' */
- tok = strtok(NULL, "");
- if (tok == NULL)
- goto badreq;
- arguments = stralloc(tok);
-
- /* see if it's one we allow */
- for (i = 0; i < NSERVICES; i++)
- if (strcmp(services[i], service) == 0)
- break;
- if (i == NSERVICES) {
- dbprintf(("%s: %s: invalid service\n",
- debug_prefix_time(NULL), service));
- pkt_init(&pkt_out, P_NAK, "ERROR %s: invalid service\n", service);
- goto send_pkt_out;
- }
-
- service = newvstralloc(service,
- libexecdir, "/", service, versionsuffix(),
- NULL);
- if (access(service, X_OK) < 0) {
- dbprintf(("%s: can't execute %s: %s\n",
- debug_prefix_time(NULL), service, strerror(errno)));
- pkt_init(&pkt_out, P_NAK, "ERROR execute access to \"%s\" denied\n",
- service);
- goto send_pkt_out;
- }
-
- /* see if its already running */
- for (as = TAILQ_FIRST(&serviceq.tailq); as != NULL;
- as = TAILQ_NEXT(as, tq)) {
- if (strcmp(as->cmd, service) == 0 &&
- strcmp(as->arguments, arguments) == 0) {
- dbprintf(("%s: %s %s: already running, acking req\n",
- debug_prefix_time(NULL), service, arguments));
- pkt_init(&pkt_out, P_ACK, "");
- goto send_pkt_out;
- }
- }
-
- /*
- * create a new service instance, and send the arguments down
- * the request pipe.
- */
- dbprintf(("%s: creating new service: %s\n%s\n",
- debug_prefix_time(NULL), service, arguments));
- as = service_new(handle, service, arguments);
- if (writebuf(as, arguments, strlen(arguments)) < 0) {
- const char *errmsg = strerror(errno);
- dbprintf(("%s: error sending arguments to %s: %s\n",
- debug_prefix_time(NULL), service, errmsg));
- pkt_init(&pkt_out, P_NAK, "ERROR error writing arguments to %s: %s\n",
- service, errmsg);
- goto send_pkt_out;
- }
- aclose(as->reqfd);
-
- amfree(pktbody);
- amfree(service);
- amfree(arguments);
-
- /*
- * Move to the sendack state, and start up the state
- * machine.
- */
- as->state = s_sendack;
- state_machine(as, A_START, NULL);
- return;
-
-badreq:
- pkt_init(&pkt_out, P_NAK, "ERROR invalid REQ\n");
- dbprintf(("%s: received invalid %s packet:\n<<<<<\n%s>>>>>\n\n",
- debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
-
-send_pkt_out:
- amfree(pktbody);
- amfree(service);
- amfree(arguments);
- if(as) service_delete(as);
- do_sendpkt(handle, &pkt_out);
- security_close(handle);
-}
-
-/*
- * Handles incoming protocol packets. Routes responses to the proper
- * running service.
- */
-static void
-state_machine(as, action, pkt)
- struct active_service *as;
- action_t action;
- pkt_t *pkt;
-{
- action_t retaction;
- state_t curstate;
- pkt_t nak;
-
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: state_machine: %X entering\n",
- debug_prefix_time(NULL), (unsigned int)as));
-#endif
- for (;;) {
- curstate = as->state;
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: state_machine: %X curstate=%s action=%s\n",
- debug_prefix_time(NULL), (unsigned int)as,
- state2str(curstate), action2str(action)));
-#endif
- retaction = (*curstate)(as, action, pkt);
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: state_machine: %X curstate=%s returned %s (nextstate=%s)\n",
- debug_prefix_time(NULL),
- (unsigned int)as, state2str(curstate), action2str(retaction),
- state2str(as->state)));
-#endif
-
- switch (retaction) {
- /*
- * State has queued up and is now blocking on input.
- */
- case A_PENDING:
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: state_machine: %X leaving (A_PENDING)\n",
- debug_prefix_time(NULL), (unsigned int)as));
-#endif
- return;
-
- /*
- * service has switched states. Loop.
- */
- case A_CONTINUE:
- break;
-
- /*
- * state has determined that the packet it received was bogus.
- * Send a nak, and return.
- */
- case A_SENDNAK:
- dbprintf(("%s: received unexpected %s packet\n",
- debug_prefix_time(NULL), pkt_type2str(pkt->type)));
- dbprintf(("<<<<<\n%s----\n\n", pkt->body));
- pkt_init(&nak, P_NAK, "ERROR unexpected packet type %s\n",
- pkt_type2str(pkt->type));
- do_sendpkt(as->security_handle, &nak);
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: state_machine: %X leaving (A_SENDNAK)\n",
- debug_prefix_time(NULL), (unsigned int)as));
-#endif
- return;
-
- /*
- * Service is done. Remove it and finish.
- */
- case A_FINISH:
- service_delete(as);
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: state_machine: %X leaving (A_FINISH)\n",
- debug_prefix_time(NULL), (unsigned int)as));
-#endif
- return;
-
- default:
- assert(0);
- break;
- }
- }
- /* NOTREACHED */
-}
-
-/*
- * This state just sends an ack. After that, we move to the repwait
- * state to wait for REP data to arrive from the subprocess.
- */
-static action_t
-s_sendack(as, action, pkt)
- struct active_service *as;
- action_t action;
- pkt_t *pkt;
-{
- pkt_t ack;
-
- pkt_init(&ack, P_ACK, "");
- if (do_sendpkt(as->security_handle, &ack) < 0) {
- dbprintf(("%s: error sending ACK: %s\n",
- debug_prefix_time(NULL), security_geterror(as->security_handle)));
- return (A_FINISH);
- }
-
- /*
- * move to the repwait state
- * Setup a listener for data on the reply fd, but also
- * listen for packets over the wire, as the server may
- * poll us if we take a long time.
- * Setup a timeout that will fire if it takes too long to
- * receive rep data.
- */
- as->state = s_repwait;
- as->ev_repfd = event_register(as->repfd, EV_READFD, repfd_recv, as);
- as->ev_reptimeout = event_register(REP_TIMEOUT, EV_TIME,
- timeout_repfd, as);
- security_recvpkt(as->security_handle, protocol_recv, as, -1);
- return (A_PENDING);
-}
-
-/*
- * This is the repwait state. We have responded to the initial REQ with
- * an ACK, and we are now waiting for the process we spawned to pass us
- * data to send in a REP.
- */
-static action_t
-s_repwait(as, action, pkt)
- struct active_service *as;
- action_t action;
- pkt_t *pkt;
-{
- int n;
-
- /*
- * We normally shouldn't receive any packets while waiting
- * for our REP data, but in some cases we do.
- */
- if (action == A_RECVPKT) {
- assert(pkt != NULL);
- /*
- * Another req for something that's running. Just send an ACK
- * and go back and wait for more data.
- */
- if (pkt->type == P_REQ) {
- dbprintf(("%s: received dup P_REQ packet, ACKing it\n",
- debug_prefix_time(NULL)));
- pkt_init(&as->rep_pkt, P_ACK, "");
- do_sendpkt(as->security_handle, &as->rep_pkt);
- return (A_PENDING);
- }
- /* something unexpected. Nak it */
- return (A_SENDNAK);
- }
-
- if (action == A_TIMEOUT) {
- pkt_init(&as->rep_pkt, P_NAK, "ERROR timeout on reply pipe\n");
- dbprintf(("%s: %s timed out waiting for REP data\n",
- debug_prefix_time(NULL), as->cmd));
- do_sendpkt(as->security_handle, &as->rep_pkt);
- return (A_FINISH);
- }
-
- assert(action == A_RECVREP);
-
- /*
- * If the read fails, consider the process dead, and remove it.
- * Always save room for nul termination.
- */
- if (as->repbufsize + 1 >= sizeof(as->repbuf)) {
- dbprintf(("%s: more than %d bytes in reply\n",
- debug_prefix_time(NULL), sizeof(as->repbuf)));
- dbprintf(("%s: reply so far:\n%s\n", debug_prefix(NULL), as->repbuf));
- pkt_init(&as->rep_pkt, P_NAK, "ERROR more than %d bytes in reply\n",
- sizeof(as->repbuf));
- do_sendpkt(as->security_handle, &as->rep_pkt);
- return (A_FINISH);
- }
- do {
- n = read(as->repfd, as->repbuf + as->repbufsize,
- sizeof(as->repbuf) - as->repbufsize - 1);
- } while ((n < 0) && ((errno == EINTR) || (errno == EAGAIN)));
- if (n < 0) {
- const char *errstr = strerror(errno);
- dbprintf(("%s: read error on reply pipe: %s\n",
- debug_prefix_time(NULL), errstr));
- pkt_init(&as->rep_pkt, P_NAK, "ERROR read error on reply pipe: %s\n",
- errstr);
- do_sendpkt(as->security_handle, &as->rep_pkt);
- return (A_FINISH);
- }
- /*
- * If we got some data, go back and wait for more, or EOF. Nul terminate
- * the buffer first.
- */
- as->repbuf[n + as->repbufsize] = '\0';
- if (n > 0) {
- as->repbufsize += n;
- if(as->send_partial_reply) {
- pkt_init(&as->rep_pkt, P_PREP, "%s", as->repbuf);
- do_sendpkt(as->security_handle, &as->rep_pkt);
- pkt_init(&as->rep_pkt, P_REP, "");
- }
-
- return (A_PENDING);
- }
-
- /*
- * If we got 0, then we hit EOF. Process the data and release
- * the timeout.
- */
- assert(n == 0);
-
- assert(as->ev_repfd != NULL);
- event_release(as->ev_repfd);
- as->ev_repfd = NULL;
-
- assert(as->ev_reptimeout != NULL);
- event_release(as->ev_reptimeout);
- as->ev_reptimeout = NULL;
-
- as->state = s_processrep;
- aclose(as->repfd);
- return (A_CONTINUE);
-}
-
-/*
- * After we have read in all of the rep data, we process it and send
- * it out as a REP packet.
- */
-static action_t
-s_processrep(as, action, pkt)
- struct active_service *as;
- action_t action;
- pkt_t *pkt;
-{
- char *tok, *repbuf;
-
- /*
- * Copy the rep lines into the outgoing packet.
- *
- * If this line is a CONNECT, translate it
- * Format is "CONNECT <tag> <handle> <tag> <handle> etc...
- * Example:
- *
- * CONNECT DATA 4 MESG 5 INDEX 6
- *
- * The tags are arbitrary. The handles are in the DATA_FD pool.
- * We need to map these to security streams and pass them back
- * to the amanda server. If the handle is -1, then we don't map.
- */
- repbuf = stralloc(as->repbuf);
- pkt_init(&as->rep_pkt, P_REP, "");
- tok = strtok(repbuf, " ");
- if (tok == NULL)
- goto error;
- if (strcmp(tok, "CONNECT") == 0) {
- char *line, *nextbuf;
-
- /* Save the entire line */
- line = strtok(NULL, "\n");
- /* Save the buf following the line */
- nextbuf = strtok(NULL, "");
-
- if (line == NULL || nextbuf == NULL)
- goto error;
-
- pkt_cat(&as->rep_pkt, "CONNECT");
-
- /* loop over the id/handle pairs */
- for (;;) {
- /* id */
- tok = strtok(line, " ");
- line = NULL; /* keep working from line */
- if (tok == NULL)
- break;
- pkt_cat(&as->rep_pkt, " %s", tok);
-
- /* handle */
- tok = strtok(NULL, " \n");
- if (tok == NULL)
- goto error;
- /* convert the handle into something the server can process */
- pkt_cat(&as->rep_pkt, " %d", allocstream(as, atoi(tok)));
- }
- pkt_cat(&as->rep_pkt, "\n%s", nextbuf);
- } else {
-error:
- pkt_cat(&as->rep_pkt, "%s", as->repbuf);
- }
-
- /*
- * We've setup our REP packet in as->rep_pkt. Now move to the transmission
- * state.
- */
- as->state = s_sendrep;
- as->repretry = MAX_REP_RETRIES;
- amfree(repbuf);
- return (A_CONTINUE);
-}
-
-/*
- * This is the state where we send the REP we just collected from our child.
- */
-static action_t
-s_sendrep(as, action, pkt)
- struct active_service *as;
- action_t action;
- pkt_t *pkt;
-{
-
- /*
- * Transmit it and move to the ack state.
- */
- do_sendpkt(as->security_handle, &as->rep_pkt);
- security_recvpkt(as->security_handle, protocol_recv, as, ACK_TIMEOUT);
- as->state = s_ackwait;
- return (A_PENDING);
-}
-
-/*
- * This is the state in which we wait for the server to ACK the REP
- * we just sent it.
- */
-static action_t
-s_ackwait(as, action, pkt)
- struct active_service *as;
- action_t action;
- pkt_t *pkt;
-{
- struct datafd_handle *dh;
- int npipes;
-
- /*
- * If we got a timeout, try again, but eventually give up.
- */
- if (action == A_TIMEOUT) {
- if (--as->repretry > 0) {
- as->state = s_sendrep;
- return (A_CONTINUE);
- }
- dbprintf(("%s: timeout waiting for ACK for our REP\n",
- debug_prefix_time(NULL)));
- return (A_FINISH);
- }
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: received ACK, now opening streams\n",
- debug_prefix_time(NULL)));
-#endif
-
- assert(action == A_RECVPKT);
- if (pkt->type != P_ACK)
- return (A_SENDNAK);
-
- /*
- * Got the ack, now open the pipes
- */
- for (dh = &as->data[0]; dh < &as->data[DATA_FD_COUNT]; dh++) {
- if (dh->netfd == NULL)
- continue;
- if (security_stream_accept(dh->netfd) < 0) {
- dbprintf(("%s: stream %d accept failed: %s\n",
- debug_prefix_time(NULL),
- dh - &as->data[0], security_geterror(as->security_handle)));
- security_stream_close(dh->netfd);
- dh->netfd = NULL;
- }
- /* setup an event for reads from it */
- dh->ev_handle = event_register(dh->fd, EV_READFD, process_netfd, dh);
- }
-
- /*
- * Pipes are open, so auth them. Count them at the same time.
- */
- for (npipes = 0, dh = &as->data[0]; dh < &as->data[DATA_FD_COUNT]; dh++) {
- if (dh->netfd == NULL)
- continue;
- if (security_stream_auth(dh->netfd) < 0) {
- security_stream_close(dh->netfd);
- dh->netfd = NULL;
- event_release(dh->ev_handle);
- dh->ev_handle = NULL;
- } else {
- npipes++;
- }
- }
-
- /*
- * If no pipes are open, then we're done. Otherwise, just start running.
- * The event handlers on all of the pipes will take it from here.
- */
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: at end of s_ackwait, npipes is %d\n",
- debug_prefix_time(NULL), npipes));
-#endif
- if (npipes == 0)
- return (A_FINISH);
- else {
- security_close(as->security_handle);
- as->security_handle = NULL;
- return (A_PENDING);
- }
-}
-
-/*
- * Called when a repfd has received data
- */
-static void
-repfd_recv(cookie)
- void *cookie;
-{
- struct active_service *as = cookie;
-
- assert(as != NULL);
- assert(as->ev_repfd != NULL);
-
- state_machine(as, A_RECVREP, NULL);
-}
-
-/*
- * Called when a repfd has timed out
- */
-static void
-timeout_repfd(cookie)
- void *cookie;
-{
- struct active_service *as = cookie;
-
- assert(as != NULL);
- assert(as->ev_reptimeout != NULL);
-
- state_machine(as, A_TIMEOUT, NULL);
-}
-
-/*
- * Called when a handle has received data
- */
-static void
-protocol_recv(cookie, pkt, status)
- void *cookie;
- pkt_t *pkt;
- security_status_t status;
-{
- struct active_service *as = cookie;
-
- assert(as != NULL);
-
- switch (status) {
- case S_OK:
- dbprintf(("%s: received %s pkt:\n<<<<<\n%s>>>>>\n",
- debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
- state_machine(as, A_RECVPKT, pkt);
- break;
- case S_TIMEOUT:
- dbprintf(("%s: timeout\n", debug_prefix_time(NULL)));
- state_machine(as, A_TIMEOUT, NULL);
- break;
- case S_ERROR:
- dbprintf(("%s: receive error: %s\n",
- debug_prefix_time(NULL), security_geterror(as->security_handle)));
- break;
- }
-}
-
-/*
- * This is a generic relay function that just reads data from one of
- * the process's pipes and passes it up the equivalent security_stream_t
- */
-static void
-process_netfd(cookie)
- void *cookie;
-{
- pkt_t nak;
- struct datafd_handle *dh = cookie;
- struct active_service *as = dh->as;
- int n;
-
- do {
- n = read(dh->fd, as->databuf, sizeof(as->databuf));
- } while ((n < 0) && ((errno == EINTR) || (errno == EAGAIN)));
-
- /*
- * Process has died.
- */
- if (n < 0) {
- pkt_init(&nak, P_NAK, "ERROR data descriptor %d broken: %s\n",
- dh->fd, strerror(errno));
- goto sendnak;
- }
- /*
- * Process has closed the pipe. Just remove this event handler.
- * If all pipes are closed, shut down this service.
- */
- if (n == 0) {
- event_release(dh->ev_handle);
- dh->ev_handle = NULL;
- security_stream_close(dh->netfd);
- dh->netfd = NULL;
- for (dh = &as->data[0]; dh < &as->data[DATA_FD_COUNT]; dh++) {
- if (dh->netfd != NULL)
- return;
- }
- service_delete(as);
- return;
- }
- if (security_stream_write(dh->netfd, as->databuf, n) < 0) {
- /* stream has croaked */
- pkt_init(&nak, P_NAK, "ERROR write error on stream %d: %s\n",
- security_stream_id(dh->netfd),
- security_stream_geterror(dh->netfd));
- goto sendnak;
- }
- return;
-
-sendnak:
- do_sendpkt(as->security_handle, &nak);
- service_delete(as);
-}
-
-
-/*
- * Convert a local stream handle (DATA_FD...) into something that
- * can be sent to the amanda server.
- *
- * Returns a number that should be sent to the server in the REP packet.
- */
-static int
-allocstream(as, handle)
- struct active_service *as;
- int handle;
-{
- struct datafd_handle *dh;
-
- /* if the handle is -1, then we don't bother */
- if (handle < 0)
- return (-1);
-
- /* make sure the handle's kosher */
- if (handle < DATA_FD_OFFSET || handle >= DATA_FD_OFFSET + DATA_FD_COUNT)
- return (-1);
-
- /* get a pointer into our handle array */
- dh = &as->data[handle - DATA_FD_OFFSET];
-
- /* make sure we're not already using the net handle */
- if (dh->netfd != NULL)
- return (-1);
-
- /* allocate a stream from the security layer and return */
- dh->netfd = security_stream_server(as->security_handle);
- if (dh->netfd == NULL) {
- dbprintf(("%s: couldn't open stream to server: %s\n",
- debug_prefix_time(NULL), security_geterror(as->security_handle)));
- return (-1);
- }
-
- /*
- * convert the stream into a numeric id that can be sent to the
- * remote end.
- */
- return (security_stream_id(dh->netfd));
-}
-
-/*
- * Create a new service instance
- */
-static struct active_service *
-service_new(security_handle, cmd, arguments)
- security_handle_t *security_handle;
- const char *cmd, *arguments;
-{
- int data[DATA_FD_COUNT + 2][2], i;
- struct active_service *as;
- pid_t pid;
- int newfd;
-
- assert(security_handle != NULL);
- assert(cmd != NULL);
- assert(arguments != NULL);
-
- /* a plethora of pipes */
- for (i = 0; i < DATA_FD_COUNT + 2; i++)
- if (pipe(data[i]) < 0)
- error("pipe: %s", strerror(errno));
-
- switch(pid = fork()) {
- case -1:
- error("could not fork service %s: %s", cmd, strerror(errno));
- default:
- /*
- * The parent. Close the far ends of our pipes and return.
- */
- as = alloc(sizeof(*as));
- as->cmd = stralloc(cmd);
- as->arguments = stralloc(arguments);
- as->security_handle = security_handle;
- as->state = NULL;
- as->pid = pid;
- as->send_partial_reply = 0;
- if(strcmp(cmd+(strlen(cmd)-8), "sendsize") == 0) {
- g_option_t *g_options;
- char *option_str, *p;
-
- option_str = stralloc(as->arguments+8);
- p = strchr(option_str,'\n');
- if(p) *p = '\0';
-
- g_options = parse_g_options(option_str, 1);
- if(am_has_feature(g_options->features, fe_partial_estimate)) {
- as->send_partial_reply = 1;
- }
- amfree(g_options);
- amfree(option_str);
- }
-
- /* write to the request pipe */
- aclose(data[0][0]);
- as->reqfd = data[0][1];
-
- /*
- * read from the reply pipe
- */
- as->repfd = data[1][0];
- aclose(data[1][1]);
- as->ev_repfd = NULL;
- as->repbufsize = 0;
- as->repretry = 0;
-
- /*
- * read from the rest of the general-use pipes
- * (netfds are opened as the client requests them)
- */
- for (i = 0; i < DATA_FD_COUNT; i++) {
- aclose(data[i + 2][1]);
- as->data[i].fd = data[i + 2][0];
- as->data[i].ev_handle = NULL;
- as->data[i].netfd = NULL;
- as->data[i].as = as;
- }
-
- /* add it to the service queue */
- /* increment the active service count */
- TAILQ_INSERT_TAIL(&serviceq.tailq, as, tq);
- serviceq.qlength++;
-
- return (as);
- case 0:
- /*
- * The child. Put our pipes in their advertised locations
- * and start up.
- */
-#ifdef FORCE_USERID
- seteuid((uid_t)0);
- setuid(client_uid);
-#endif
-
- /*
- * The data stream is stdin in the new process
- */
- if (dup2(data[0][0], 0) < 0) {
- error("dup %d to %d failed: %s\n", data[0][0], 0,
- strerror(errno));
- }
- aclose(data[0][0]);
- aclose(data[0][1]);
-
- /*
- * The reply stream is stdout
- */
- if (dup2(data[1][1], 1) < 0) {
- error("dup %d to %d failed: %s\n", data[1][1], 1,
- strerror(errno));
- }
- aclose(data[1][0]);
- aclose(data[1][1]);
-
- /*
- * The rest start at the offset defined in amandad.h, and continue
- * through the internal defined.
- */
- for (i = 0; i < DATA_FD_COUNT; i++)
- aclose(data[i + 2][0]);
-
- /*
- * Make sure they are not open in the range DATA_FD_OFFSET to
- * DATA_FD_OFFSET + DATA_FD_COUNT - 1
- */
- for (i = 0; i < DATA_FD_COUNT; i++) {
- while(data[i + 2][1] >= DATA_FD_OFFSET &&
- data[i + 2][1] <= DATA_FD_OFFSET + DATA_FD_COUNT - 1) {
- newfd = dup(data[i + 2][1]);
- if(newfd == -1)
- error("Can't dup out off DATA_FD range");
- data[i + 2][1] = newfd;
- }
- }
- for (i = 0; i < DATA_FD_COUNT; i++)
- close(DATA_FD_OFFSET + i);
-
- for (i = 0; i < DATA_FD_COUNT; i++) {
- if (dup2(data[i + 2][1], i + DATA_FD_OFFSET) < 0) {
- error("dup %d to %d failed: %s\n", data[i + 2][1],
- i + DATA_FD_OFFSET, strerror(errno));
- }
- aclose(data[i + 2][1]);
- }
-
- execle(cmd, cmd, NULL, safe_env());
- error("could not exec service %s: %s", cmd, strerror(errno));
- }
- /* NOTREACHED */
- return NULL;
-}
-
-/*
- * Unallocate a service instance
- */
-static void
-service_delete(as)
- struct active_service *as;
-{
- int i;
- struct datafd_handle *dh;
-
-#ifdef AMANDAD_DEBUG
- dbprintf(("%s: closing service: %s\n",
- debug_prefix_time(NULL), (as->cmd)?as->cmd:"??UNKONWN??"));
-#endif
-
- assert(as != NULL);
-
- assert(as->cmd != NULL);
- amfree(as->cmd);
-
- assert(as->arguments != NULL);
- amfree(as->arguments);
-
- if (as->reqfd != -1)
- aclose(as->reqfd);
- if (as->repfd != -1)
- aclose(as->repfd);
-
- if (as->ev_repfd != NULL)
- event_release(as->ev_repfd);
- if (as->ev_reptimeout != NULL)
- event_release(as->ev_reptimeout);
-
- for (i = 0; i < DATA_FD_COUNT; i++) {
- dh = &as->data[i];
-
- aclose(dh->fd);
-
- if (dh->netfd != NULL)
- security_stream_close(dh->netfd);
-
- if (dh->ev_handle != NULL)
- event_release(dh->ev_handle);
- }
-
- if (as->security_handle != NULL)
- security_close(as->security_handle);
-
- assert(as->pid > 0);
- kill(as->pid, SIGTERM);
- waitpid(as->pid, NULL, WNOHANG);
-
- TAILQ_REMOVE(&serviceq.tailq, as, tq);
- assert(serviceq.qlength > 0);
- serviceq.qlength--;
-
- amfree(as);
-}
-
-/*
- * Like 'fullwrite', but does the work in a child process so pipelines
- * do not hang.
- */
-static int
-writebuf(as, bufp, size)
- struct active_service *as;
- const void *bufp;
- size_t size;
-{
- int pid;
-
- switch (pid=fork()) {
- case -1:
- return -1;
-
- default:
- waitpid(pid, NULL, WNOHANG);
- return 0; /* this is the parent */
-
- case 0:
- break; /* this is the child */
- }
- aclose (as->repfd); /* make sure we are not a reader */
- exit (fullwrite(as->reqfd, bufp, size) != size);
-}
-
-static int
-do_sendpkt(handle, pkt)
- security_handle_t *handle;
- pkt_t *pkt;
-{
- dbprintf(("%s: sending %s pkt:\n<<<<<\n%s>>>>>\n",
- debug_prefix_time(NULL), pkt_type2str(pkt->type), pkt->body));
- return security_sendpkt(handle, pkt);
-}
-
-#ifdef AMANDAD_DEBUG
-/*
- * Convert a state into a string
- */
-static const char *
-state2str(state)
- state_t state;
-{
- static const struct {
- state_t state;
- const char str[13];
- } states[] = {
-#define X(state) { state, stringize(state) }
- X(s_sendack),
- X(s_repwait),
- X(s_processrep),
- X(s_sendrep),
- X(s_ackwait),
-#undef X
- };
- int i;
-
- for (i = 0; i < sizeof(states) / sizeof(states[0]); i++)
- if (state == states[i].state)
- return (states[i].str);
- return ("INVALID STATE");
-}
-
-/*
- * Convert an action into a string
- */
-static const char *
-action2str(action)
- action_t action;
-{
- static const struct {
- action_t action;
- const char str[12];
- } actions[] = {
-#define X(action) { action, stringize(action) }
- X(A_START),
- X(A_RECVPKT),
- X(A_RECVREP),
- X(A_PENDING),
- X(A_FINISH),
- X(A_CONTINUE),
- X(A_SENDNAK),
- X(A_TIMEOUT),
-#undef X
- };
- int i;
-
- for (i = 0; i < sizeof(actions) / sizeof(actions[0]); i++)
- if (action == actions[i].action)
- return (actions[i].str);
- return ("UNKNOWN ACTION");
-}
-#endif /* AMANDAD_DEBUG */