X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=common-src%2Fssh-security.c;h=bed501be996151a31ca54560b8b1513b7de975e9;hb=b116e9366c7b2ea2c2eb53b0a13df4090e176235;hp=83a7a2c732eb15314408a5e708d20c117782be69;hpb=94c03cae686e4196a345d72452fda2a5203768ce;p=debian%2Famanda diff --git a/common-src/ssh-security.c b/common-src/ssh-security.c index 83a7a2c..bed501b 100644 --- a/common-src/ssh-security.c +++ b/common-src/ssh-security.c @@ -25,7 +25,7 @@ */ /* - * $Id: ssh-security.c,v 1.8.2.1 2006/04/11 11:11:16 martinea Exp $ + * $Id: ssh-security.c,v 1.23 2006/08/21 20:17:10 martinea Exp $ * * ssh-security.c - security and transport over ssh or a ssh-like command. * @@ -34,32 +34,13 @@ */ #include "amanda.h" +#include "util.h" #include "event.h" #include "packet.h" -#include "queue.h" #include "security.h" +#include "security-util.h" +#include "sockaddr-util.h" #include "stream.h" -#include "version.h" - -#ifdef SSH_SECURITY - -/*#define SSH_DEBUG*/ - -#ifdef SSH_DEBUG -#define sshprintf(x) dbprintf(x) -#else -#define sshprintf(x) -#endif - -/* - * Path to the ssh binary. This should be configurable. - */ -#define SSH_PATH "/usr/bin/ssh" - -/* - * Arguments to ssh. This should also be configurable - */ -#define SSH_ARGS "-x", "-l", CLIENT_LOGIN /* * Number of seconds ssh has to start up @@ -72,85 +53,16 @@ #define H_TAKEN -1 /* ssh_conn->tok was already read */ #define H_EOF -2 /* this connection has been shut down */ -/* - * This is a ssh connection to a host. We should only have - * one connection per host. - */ -struct ssh_conn { - int read, write; /* pipes to ssh */ - pid_t pid; /* pid of ssh process */ - char pkt[NETWORK_BLOCK_BYTES]; /* last pkt read */ - unsigned long pktlen; /* len of above */ - struct { /* buffer read() calls */ - char buf[STREAM_BUFSIZE]; /* buffer */ - size_t left; /* unread data */ - ssize_t size; /* size of last read */ - } readbuf; - event_handle_t *ev_read; /* read (EV_READFD) handle */ - int ev_read_refcnt; /* number of readers */ - char hostname[MAX_HOSTNAME_LENGTH+1]; /* host we're talking to */ - char *errmsg; /* error passed up */ - int refcnt; /* number of handles using */ - int handle; /* last proto handle read */ - TAILQ_ENTRY(ssh_conn) tq; /* queue handle */ -}; - - -struct ssh_stream; - -/* - * This is the private handle data. - */ -struct ssh_handle { - security_handle_t sech; /* MUST be first */ - char *hostname; /* ptr to rc->hostname */ - struct ssh_stream *rs; /* virtual stream we xmit over */ - - union { - void (*recvpkt) P((void *, pkt_t *, security_status_t)); - /* func to call when packet recvd */ - void (*connect) P((void *, security_handle_t *, security_status_t)); - /* func to call when connected */ - } fn; - void *arg; /* argument to pass function */ - event_handle_t *ev_timeout; /* timeout handle for recv */ -}; - -/* - * This is the internal security_stream data for ssh. - */ -struct ssh_stream { - security_stream_t secstr; /* MUST be first */ - struct ssh_conn *rc; /* physical connection */ - int handle; /* protocol handle */ - event_handle_t *ev_read; /* read (EV_WAIT) event handle */ - void (*fn) P((void *, void *, ssize_t)); /* read event fn */ - void *arg; /* arg for previous */ -}; - /* * Interface functions */ -static int ssh_sendpkt P((void *, pkt_t *)); -static int ssh_stream_accept P((void *)); -static int ssh_stream_auth P((void *)); -static int ssh_stream_id P((void *)); -static int ssh_stream_write P((void *, const void *, size_t)); -static void *ssh_stream_client P((void *, int)); -static void *ssh_stream_server P((void *)); -static void ssh_accept P((int, int, - void (*)(security_handle_t *, pkt_t *))); -static void ssh_close P((void *)); -static void ssh_connect P((const char *, - char *(*)(char *, void *), - void (*)(void *, security_handle_t *, security_status_t), void *)); -static void ssh_recvpkt P((void *, - void (*)(void *, pkt_t *, security_status_t), void *, int)); -static void ssh_recvpkt_cancel P((void *)); -static void ssh_stream_close P((void *)); -static void ssh_stream_read P((void *, void (*)(void *, void *, ssize_t), - void *)); -static void ssh_stream_read_cancel P((void *)); +static void ssh_connect(const char *, char *(*)(char *, void *), + void (*)(void *, security_handle_t *, security_status_t), + void *, void *); +static void ssh_accept(const security_driver_t *driver, char *(*conf_fn)(char *, void *), + int in, int out, + void (*fn)(security_handle_t *, pkt_t *), + void *datap); /* * This is our interface to the outside world. @@ -159,125 +71,100 @@ const security_driver_t ssh_security_driver = { "SSH", ssh_connect, ssh_accept, - ssh_close, - ssh_sendpkt, - ssh_recvpkt, - ssh_recvpkt_cancel, - ssh_stream_server, - ssh_stream_accept, - ssh_stream_client, - ssh_stream_close, - ssh_stream_auth, - ssh_stream_id, - ssh_stream_write, - ssh_stream_read, - ssh_stream_read_cancel, -}; - -/* - * This is a queue of open connections - */ -static struct { - TAILQ_HEAD(, ssh_conn) tailq; - int qlength; -} connq = { - TAILQ_HEAD_INITIALIZER(connq.tailq), 0 + sec_get_authenticated_peer_name_hostname, + sec_close, + stream_sendpkt, + stream_recvpkt, + stream_recvpkt_cancel, + tcpma_stream_server, + tcpma_stream_accept, + tcpma_stream_client, + tcpma_stream_close, + sec_stream_auth, + sec_stream_id, + tcpm_stream_write, + tcpm_stream_read, + tcpm_stream_read_sync, + tcpm_stream_read_cancel, + tcpm_close_connection, + NULL, + NULL }; -#define connq_first() TAILQ_FIRST(&connq.tailq) -#define connq_next(rc) TAILQ_NEXT(rc, tq) -#define connq_append(rc) do { \ - TAILQ_INSERT_TAIL(&connq.tailq, rc, tq); \ - connq.qlength++; \ -} while (0) -#define connq_remove(rc) do { \ - assert(connq.qlength > 0); \ - TAILQ_REMOVE(&connq.tailq, rc, tq); \ - connq.qlength--; \ -} while (0) static int newhandle = 1; -/* - * This is a function that should be called if a new security_handle_t is - * created. If NULL, no new handles are created. - * It is passed the new handle and the received pkt - */ -static void (*accept_fn) P((security_handle_t *, pkt_t *)); - /* * Local functions */ -static void connect_callback P((void *)); -static void connect_timeout P((void *)); -static int send_token P((struct ssh_conn *, int, const void *, size_t)); -static int recv_token P((struct ssh_conn *, int)); -static void recvpkt_callback P((void *, void *, ssize_t)); -static void recvpkt_timeout P((void *)); -static void stream_read_callback P((void *)); - -static int runssh P((struct ssh_conn *)); -static struct ssh_conn *conn_get P((const char *)); -static void conn_put P((struct ssh_conn *)); -static void conn_read P((struct ssh_conn *)); -static void conn_read_cancel P((struct ssh_conn *)); -static void conn_read_callback P((void *)); -static int net_writev P((int, struct iovec *, int)); -static ssize_t net_read P((struct ssh_conn *, void *, size_t, int)); -static int net_read_fillbuf P((struct ssh_conn *, int, int)); -static void parse_pkt P((pkt_t *, const void *, size_t)); - +static int runssh(struct tcp_conn *, const char *, const char *, const char *); /* * ssh version of a security handle allocator. Logically sets * up a network "connection". */ static void -ssh_connect(hostname, conf_fn, fn, arg) - const char *hostname; - char *(*conf_fn) P((char *, void *)); - void (*fn) P((void *, security_handle_t *, security_status_t)); - void *arg; +ssh_connect( + const char * hostname, + char * (*conf_fn)(char *, void *), + void (*fn)(void *, security_handle_t *, security_status_t), + void * arg, + void * datap) { - struct ssh_handle *rh; - struct hostent *he; + int result; + struct sec_handle *rh; + char *amandad_path=NULL, *client_username=NULL, *ssh_keys=NULL; assert(fn != NULL); assert(hostname != NULL); - sshprintf(("%s: ssh: ssh_connect: %s\n", debug_prefix_time(NULL), hostname)); + auth_debug(1, "ssh_connect: %s\n", hostname); - rh = alloc(sizeof(*rh)); + rh = g_new0(struct sec_handle, 1); security_handleinit(&rh->sech, &ssh_security_driver); rh->hostname = NULL; rh->rs = NULL; rh->ev_timeout = NULL; + rh->rc = NULL; - if ((he = gethostbyname(hostname)) == NULL) { + /* get the canonical hostname */ + rh->hostname = NULL; + if ((result = resolve_hostname(hostname, 0, NULL, &rh->hostname)) != 0 + || rh->hostname == NULL) { security_seterror(&rh->sech, - "%s: could not resolve hostname", hostname); + _("ssh_security could not find canonical name for '%s': %s"), + hostname, gai_strerror(result)); (*fn)(arg, &rh->sech, S_ERROR); return; } - rh->hostname = he->h_name; /* will be replaced */ - rh->rs = ssh_stream_client(rh, newhandle++); + rh->rs = tcpma_stream_client(rh, newhandle++); + rh->rc->conf_fn = conf_fn; + rh->rc->datap = datap; if (rh->rs == NULL) goto error; - rh->hostname = rh->rs->rc->hostname; + amfree(rh->hostname); + rh->hostname = stralloc(rh->rs->rc->hostname); - if (rh->rs->rc->pid < 0) { - /* - * We need to open a new connection. - * - * XXX need to eventually limit number of outgoing connections here. - */ - if (runssh(rh->rs->rc) < 0) { - security_seterror(&rh->sech, - "can't connect to %s: %s", hostname, rh->rs->rc->errmsg); + /* + * We need to open a new connection. + * + * XXX need to eventually limit number of outgoing connections here. + */ + if(conf_fn) { + amandad_path = conf_fn("amandad_path", datap); + client_username = conf_fn("client_username", datap); + ssh_keys = conf_fn("ssh_keys", datap); + } + if(rh->rc->read == -1) { + if (runssh(rh->rs->rc, amandad_path, client_username, ssh_keys) < 0) { + security_seterror(&rh->sech, _("can't connect to %s: %s"), + hostname, rh->rs->rc->errmsg); goto error; } + rh->rc->refcnt++; } + /* * The socket will be opened async so hosts that are down won't * block everything. We need to register a write event @@ -288,10 +175,10 @@ ssh_connect(hostname, conf_fn, fn, arg) */ rh->fn.connect = fn; rh->arg = arg; - rh->rs->ev_read = event_register(rh->rs->rc->write, EV_WRITEFD, - connect_callback, rh); - rh->ev_timeout = event_register(CONNECT_TIMEOUT, EV_TIME, - connect_timeout, rh); + rh->rs->ev_read = event_register((event_id_t)rh->rs->rc->write, EV_WRITEFD, + sec_connect_callback, rh); + rh->ev_timeout = event_register((event_id_t)CONNECT_TIMEOUT, EV_TIME, + sec_connect_timeout, rh); return; @@ -299,195 +186,97 @@ error: (*fn)(arg, &rh->sech, S_ERROR); } -/* - * Called when a ssh connection is finished connecting and is ready - * to be authenticated. - */ -static void -connect_callback(cookie) - void *cookie; -{ - struct ssh_handle *rh = cookie; - - event_release(rh->rs->ev_read); - rh->rs->ev_read = NULL; - event_release(rh->ev_timeout); - rh->ev_timeout = NULL; - - (*rh->fn.connect)(rh->arg, &rh->sech, S_OK); -} - -/* - * Called if a connection times out before completion. - */ +/* like sec_accept, but first it gets the remote system's hostname */ static void -connect_timeout(cookie) - void *cookie; -{ - struct ssh_handle *rh = cookie; - - event_release(rh->rs->ev_read); - rh->rs->ev_read = NULL; - event_release(rh->ev_timeout); - rh->ev_timeout = NULL; - - (*rh->fn.connect)(rh->arg, &rh->sech, S_TIMEOUT); -} - -/* - * Setup to handle new incoming connections - */ -static void -ssh_accept(in, out, fn) - int in, out; - void (*fn) P((security_handle_t *, pkt_t *)); -{ - struct ssh_conn *rc; - - rc = conn_get("unknown"); - rc->read = in; - rc->write = out; - accept_fn = fn; - conn_read(rc); -} - -/* - * Locate an existing connection to the given host, or create a new, - * unconnected entry if none exists. The caller is expected to check - * for the lack of a connection (rc->read == -1) and set one up. - */ -static struct ssh_conn * -conn_get(hostname) - const char *hostname; -{ - struct ssh_conn *rc; +ssh_accept( + const security_driver_t *driver, + char *(*conf_fn)(char *, void *), + int in, + int out, + void (*fn)(security_handle_t *, pkt_t *), + void *datap) +{ + struct sec_handle *rh; + struct tcp_conn *rc = sec_tcp_conn_get("", 0); + char *ssh_connection, *p; + char *errmsg = NULL; + sockaddr_union addr; + int result; + + /* "Accepting" an SSH connection means that amandad was invoked via sshd, so + * we should have anSSH_CONNECTION env var. If not, then this probably isn't + * a real SSH connection and we should bail out. */ + ssh_connection = getenv("SSH_CONNECTION"); + if (!ssh_connection) { + errmsg = g_strdup("$SSH_CONNECTION not set - was amandad started by sshd?"); + goto error; + } - sshprintf(("%s: ssh: conn_get: %s\n", debug_prefix_time(NULL), hostname)); + /* make a local copy, to munge */ + ssh_connection = g_strdup(ssh_connection); - for (rc = connq_first(); rc != NULL; rc = connq_next(rc)) { - if (strcasecmp(hostname, rc->hostname) == 0) - break; + /* strip off the first component - the ASCII IP address */ + if ((p = strchr(ssh_connection, ' ')) == NULL) { + errmsg = g_strdup("$SSH_CONNECTION malformed"); + goto error; } + *p = '\0'; - if (rc != NULL) { - rc->refcnt++; - sshprintf(("%s: ssh: conn_get: exists, refcnt to %s is now %d\n", debug_prefix_time(NULL), - rc->hostname, rc->refcnt)); - return (rc); - } + /* ---- everything from here on is just a warning, leaving hostname at "" */ - sshprintf(("%s: ssh: conn_get: creating new handle\n", debug_prefix_time(NULL))); - /* - * We can't be creating a new handle if we are the client - */ - assert(accept_fn == NULL); - rc = alloc(sizeof(*rc)); - rc->read = rc->write = -1; - rc->pid = -1; - rc->readbuf.left = 0; - rc->readbuf.size = 0; - rc->ev_read = NULL; - strncpy(rc->hostname, hostname, sizeof(rc->hostname) - 1); - rc->hostname[sizeof(rc->hostname) - 1] = '\0'; - rc->errmsg = NULL; - rc->refcnt = 1; - rc->handle = -1; - connq_append(rc); - return (rc); -} + SU_INIT(&addr, AF_INET); -/* - * Delete a reference to a connection, and close it if it is the last - * reference. - */ -static void -conn_put(rc) - struct ssh_conn *rc; -{ - amwait_t status; - - assert(rc->refcnt > 0); - --rc->refcnt; - sshprintf(("%s: ssh: conn_put: decrementing refcnt for %s to %d\n", debug_prefix_time(NULL), - rc->hostname, rc->refcnt)); - if (rc->refcnt > 0) { - return; - } - sshprintf(("%s: ssh: conn_put: closing connection to %s\n", debug_prefix_time(NULL), rc->hostname)); - if (rc->read != -1) - aclose(rc->read); - if (rc->write != -1) - aclose(rc->write); - if (rc->pid != -1) { - waitpid(rc->pid, &status, WNOHANG); + /* turn the string address into a sockaddr */ + if ((result = str_to_sockaddr(ssh_connection, &addr)) != 1) { + if (result == 0) { + g_warning("Could not parse peer address %s", ssh_connection); + } else { + g_warning("Parsing peer address %s: %s", ssh_connection, gai_strerror(result)); + } + goto done; } - if (rc->ev_read != NULL) - event_release(rc->ev_read); - if (rc->errmsg != NULL) - amfree(rc->errmsg); - connq_remove(rc); - amfree(rc); -} - -/* - * Turn on read events for a conn. Or, increase a ev_read_refcnt if we are - * already receiving read events. - */ -static void -conn_read(rc) - struct ssh_conn *rc; -{ - if (rc->ev_read != NULL) { - rc->ev_read_refcnt++; - sshprintf(("%s: ssh: conn_read: incremented ev_read_refcnt to %d for %s\n", debug_prefix_time(NULL), - rc->ev_read_refcnt, rc->hostname)); - return; + /* find the hostname */ + result = getnameinfo((struct sockaddr *)&addr, SS_LEN(&addr), + rc->hostname, sizeof(rc->hostname), NULL, 0, 0); + if (result != 0) { + g_warning("Could not get hostname for SSH client %s: %s", ssh_connection, + gai_strerror(result)); + goto done; } - sshprintf(("%s: ssh: conn_read registering event handler for %s\n", debug_prefix_time(NULL), - rc->hostname)); - rc->ev_read = event_register(rc->read, EV_READFD, conn_read_callback, rc); - rc->ev_read_refcnt = 1; -} - -static void -conn_read_cancel(rc) - struct ssh_conn *rc; -{ - --rc->ev_read_refcnt; - sshprintf(("%s: ssh: conn_read_cancel: decremented ev_read_refcnt to %d for %s\n", debug_prefix_time(NULL), - rc->ev_read_refcnt, rc->hostname)); - if(rc->ev_read_refcnt > 0) { - return; + /* and verify it */ + if (check_name_give_sockaddr(rc->hostname, + (struct sockaddr *)&addr, &errmsg) < 0) { + rc->hostname[0] = '\0'; /* null out the bad hostname */ + g_warning("Checking SSH client DNS: %s", errmsg); + amfree(errmsg); + goto done; } - sshprintf(("%s: ssh: conn_read_cancel: releasing event handler for %s\n", debug_prefix_time(NULL), - rc->hostname)); - event_release(rc->ev_read); - rc->ev_read = NULL; -} -/* - * frees a handle allocated by the above - */ -static void -ssh_close(inst) - void *inst; -{ - struct ssh_handle *rh = inst; +done: + if (ssh_connection) + g_free(ssh_connection); - assert(rh != NULL); + rc->read = in; + rc->write = out; + rc->accept_fn = fn; + rc->driver = driver; + rc->conf_fn = conf_fn; + rc->datap = datap; + sec_tcp_conn_read(rc); + return; - sshprintf(("%s: ssh: closing handle to %s\n", debug_prefix_time(NULL), rh->hostname)); +error: + if (ssh_connection) + g_free(ssh_connection); - if (rh->rs != NULL) { - /* This may be null if we get here on an error */ - ssh_recvpkt_cancel(rh); - security_stream_close(&rh->rs->secstr); - } - /* keep us from getting here again */ - rh->sech.driver = NULL; - amfree(rh); + /* make up a fake handle for the error */ + rh = g_new0(struct sec_handle, 1); + security_handleinit(&rh->sech, driver); + security_seterror((security_handle_t*)rh, "ssh_accept: %s", errmsg); + amfree(errmsg); + (*fn)(&rh->sech, NULL); } /* @@ -495,19 +284,27 @@ ssh_close(inst) * Returns negative on error, with an errmsg in rc->errmsg. */ static int -runssh(rc) - struct ssh_conn *rc; +runssh( + struct tcp_conn * rc, + const char * amandad_path, + const char * client_username, + const char * ssh_keys) { int rpipe[2], wpipe[2]; - char *amandad_path; + char *xamandad_path = (char *)amandad_path; + char *xclient_username = (char *)client_username; + char *xssh_keys = (char *)ssh_keys; + memset(rpipe, -1, SIZEOF(rpipe)); + memset(wpipe, -1, SIZEOF(wpipe)); if (pipe(rpipe) < 0 || pipe(wpipe) < 0) { - rc->errmsg = newvstralloc("pipe: ", strerror(errno), NULL); + rc->errmsg = newvstrallocf(rc->errmsg, _("pipe: %s"), strerror(errno)); return (-1); } + switch (rc->pid = fork()) { case -1: - rc->errmsg = newvstralloc("fork: ", strerror(errno), NULL); + rc->errmsg = newvstrallocf(rc->errmsg, _("fork: %s"), strerror(errno)); aclose(rpipe[0]); aclose(rpipe[1]); aclose(wpipe[0]); @@ -516,7 +313,6 @@ runssh(rc) case 0: dup2(wpipe[0], 0); dup2(rpipe[1], 1); - dup2(rpipe[1], 2); break; default: rc->read = rpipe[0]; @@ -526,714 +322,27 @@ runssh(rc) return (0); } - safe_fd(-1, 0); - - amandad_path = vstralloc(libexecdir, "/", "amandad", versionsuffix(), - NULL); - execlp(SSH_PATH, SSH_PATH, SSH_ARGS, rc->hostname, amandad_path, - "-auth=ssh", NULL); - error("error: couldn't exec %s: %s", SSH_PATH, strerror(errno)); - - /* should nerver go here, shut up compiler warning */ - return(-1); -} - -/* - * Transmit a packet. - */ -static int -ssh_sendpkt(cookie, pkt) - void *cookie; - pkt_t *pkt; -{ - char buf[sizeof(pkt_t)]; - struct ssh_handle *rh = cookie; - size_t len; - - assert(rh != NULL); - assert(pkt != NULL); - - sshprintf(("%s: ssh: sendpkt: enter\n", debug_prefix_time(NULL))); - - len = strlen(pkt->body) + 2; - buf[0] = (char)pkt->type; - strcpy(&buf[1], pkt->body); - - sshprintf(("%s: ssh: sendpkt: %s (%d) pkt_t (len %d) contains:\n\n\"%s\"\n\n", debug_prefix_time(NULL), - pkt_type2str(pkt->type), pkt->type, strlen(pkt->body), pkt->body)); - - if (ssh_stream_write(rh->rs, buf, len) < 0) { - security_seterror(&rh->sech, security_stream_geterror(&rh->rs->secstr)); - return (-1); - } - return (0); -} - -/* - * Set up to receive a packet asyncronously, and call back when - * it has been read. - */ -static void -ssh_recvpkt(cookie, fn, arg, timeout) - void *cookie, *arg; - void (*fn) P((void *, pkt_t *, security_status_t)); - int timeout; -{ - struct ssh_handle *rh = cookie; - - assert(rh != NULL); - - sshprintf(("%s: ssh: recvpkt registered for %s\n", debug_prefix_time(NULL), rh->hostname)); - - /* - * Reset any pending timeout on this handle - */ - if (rh->ev_timeout != NULL) - event_release(rh->ev_timeout); - - /* - * Negative timeouts mean no timeout - */ - if (timeout < 0) - rh->ev_timeout = NULL; - else - rh->ev_timeout = event_register(timeout, EV_TIME, recvpkt_timeout, rh); - - rh->fn.recvpkt = fn; - rh->arg = arg; - ssh_stream_read(rh->rs, recvpkt_callback, rh); -} - -/* - * Remove a async receive request from the queue - */ -static void -ssh_recvpkt_cancel(cookie) - void *cookie; -{ - struct ssh_handle *rh = cookie; - - sshprintf(("%s: ssh: cancelling recvpkt for %s\n", debug_prefix_time(NULL), rh->hostname)); - - assert(rh != NULL); - - ssh_stream_read_cancel(rh->rs); - if (rh->ev_timeout != NULL) { - event_release(rh->ev_timeout); - rh->ev_timeout = NULL; - } -} - -/* - * This is called when a handle is woken up because data read off of the - * net is for it. - */ -static void -recvpkt_callback(cookie, buf, bufsize) - void *cookie, *buf; - ssize_t bufsize; -{ - pkt_t pkt; - struct ssh_handle *rh = cookie; - - assert(rh != NULL); - - /* - * We need to cancel the recvpkt request before calling - * the callback because the callback may reschedule us. - */ - ssh_recvpkt_cancel(rh); - - switch (bufsize) { - case 0: - security_seterror(&rh->sech, - "EOF on read from %s", rh->hostname); - (*rh->fn.recvpkt)(rh->arg, NULL, S_ERROR); - return; - case -1: - security_seterror(&rh->sech, security_stream_geterror(&rh->rs->secstr)); - (*rh->fn.recvpkt)(rh->arg, NULL, S_ERROR); - return; - default: - break; - } - - parse_pkt(&pkt, buf, bufsize); - sshprintf(("%s: ssh: received %s packet (%d) from %s, contains:\n\n\"%s\"\n\n", debug_prefix_time(NULL), - pkt_type2str(pkt.type), pkt.type, rh->hostname, pkt.body)); - (*rh->fn.recvpkt)(rh->arg, &pkt, S_OK); -} - -/* - * This is called when a handle times out before receiving a packet. - */ -static void -recvpkt_timeout(cookie) - void *cookie; -{ - struct ssh_handle *rh = cookie; - - assert(rh != NULL); - - sshprintf(("%s: ssh: recvpkt timeout for %s\n", debug_prefix_time(NULL), rh->hostname)); - - ssh_recvpkt_cancel(rh); - (*rh->fn.recvpkt)(rh->arg, NULL, S_TIMEOUT); -} - -/* - * Create the server end of a stream. For ssh, this means setup a stream - * object and allocate a new handle for it. - */ -static void * -ssh_stream_server(h) - void *h; -{ - struct ssh_handle *rh = h; - struct ssh_stream *rs; - - assert(rh != NULL); - - rs = alloc(sizeof(*rs)); - security_streaminit(&rs->secstr, &ssh_security_driver); - rs->rc = conn_get(rh->hostname); - /* - * Stream should already be setup! - */ - if (rs->rc->read < 0) { - conn_put(rs->rc); - amfree(rs); - security_seterror(&rh->sech, "lost connection to %s", rh->hostname); - return (NULL); - } - rh->hostname = rs->rc->hostname; - /* - * so as not to conflict with the amanda server's handle numbers, - * we start at 5000 and work down - */ - rs->handle = 5000 - newhandle++; - rs->ev_read = NULL; - sshprintf(("%s: ssh: stream_server: created stream %d\n", debug_prefix_time(NULL), rs->handle)); - return (rs); -} - -/* - * Accept an incoming connection on a stream_server socket - * Nothing needed for ssh. - */ -static int -ssh_stream_accept(s) - void *s; -{ - - return (0); -} + /* drop root privs for good */ + set_root_privs(-1); -/* - * Return a connected stream. For ssh, this means setup a stream - * with the supplied handle. - */ -static void * -ssh_stream_client(h, id) - void *h; - int id; -{ - struct ssh_handle *rh = h; - struct ssh_stream *rs; - - assert(rh != NULL); - - if (id <= 0) { - security_seterror(&rh->sech, - "%d: invalid security stream id", id); - return (NULL); - } - - rs = alloc(sizeof(*rs)); - security_streaminit(&rs->secstr, &ssh_security_driver); - rs->handle = id; - rs->ev_read = NULL; - rs->rc = conn_get(rh->hostname); - - sshprintf(("%s: ssh: stream_client: connected to stream %d\n", debug_prefix_time(NULL), id)); - - return (rs); -} - -/* - * Close and unallocate resources for a stream. - */ -static void -ssh_stream_close(s) - void *s; -{ - struct ssh_stream *rs = s; - - assert(rs != NULL); - - sshprintf(("%s: ssh: stream_close: closing stream %d\n", debug_prefix_time(NULL), rs->handle)); - - ssh_stream_read_cancel(rs); - conn_put(rs->rc); - amfree(rs); -} - -/* - * Authenticate a stream - * Nothing needed for ssh. The connection is authenticated by sshd - * on startup. - */ -static int -ssh_stream_auth(s) - void *s; -{ - - return (0); -} - -/* - * Returns the stream id for this stream. This is just the local - * port. - */ -static int -ssh_stream_id(s) - void *s; -{ - struct ssh_stream *rs = s; - - assert(rs != NULL); - - return (rs->handle); -} - -/* - * Write a chunk of data to a stream. Blocks until completion. - */ -static int -ssh_stream_write(s, buf, size) - void *s; - const void *buf; - size_t size; -{ - struct ssh_stream *rs = s; - - assert(rs != NULL); - - sshprintf(("%s: ssh: stream_write: writing %d bytes to %s:%d\n", debug_prefix_time(NULL), size, - rs->rc->hostname, rs->handle)); - - if (send_token(rs->rc, rs->handle, buf, size) < 0) { - security_stream_seterror(&rs->secstr, rs->rc->errmsg); - return (-1); - } - return (0); -} - -/* - * Submit a request to read some data. Calls back with the given - * function and arg when completed. - */ -static void -ssh_stream_read(s, fn, arg) - void *s, *arg; - void (*fn) P((void *, void *, ssize_t)); -{ - struct ssh_stream *rs = s; - - assert(rs != NULL); - - /* - * Only one read request can be active per stream. - */ - if (rs->ev_read == NULL) { - rs->ev_read = event_register((event_id_t)rs->rc, EV_WAIT, - stream_read_callback, rs); - conn_read(rs->rc); - } - rs->fn = fn; - rs->arg = arg; -} - -/* - * Cancel a previous stream read request. It's ok if we didn't have a read - * scheduled. - */ -static void -ssh_stream_read_cancel(s) - void *s; -{ - struct ssh_stream *rs = s; - - assert(rs != NULL); - - if (rs->ev_read != NULL) { - event_release(rs->ev_read); - rs->ev_read = NULL; - conn_read_cancel(rs->rc); - } -} - -/* - * Callback for ssh_stream_read - */ -static void -stream_read_callback(arg) - void *arg; -{ - struct ssh_stream *rs = arg; - assert(rs != NULL); - - sshprintf(("%s: ssh: stream_read_callback: handle %d\n", debug_prefix_time(NULL), rs->handle)); - - /* - * Make sure this was for us. If it was, then blow away the handle - * so it doesn't get claimed twice. Otherwise, leave it alone. - * - * If the handle is EOF, pass that up to our callback. - */ - if (rs->rc->handle == rs->handle) { - sshprintf(("%s: ssh: stream_read_callback: it was for us\n", debug_prefix_time(NULL))); - rs->rc->handle = H_TAKEN; - } else if (rs->rc->handle != H_EOF) { - sshprintf(("%s: ssh: stream_read_callback: not for us\n", debug_prefix_time(NULL))); - return; - } - - /* - * Remove the event first, and then call the callback. - * We remove it first because we don't want to get in their - * way if they reschedule it. - */ - ssh_stream_read_cancel(rs); - - if (rs->rc->pktlen == 0) { - sshprintf(("%s: ssh: stream_read_callback: EOF\n", debug_prefix_time(NULL))); - (*rs->fn)(rs->arg, NULL, 0); - return; - } - sshprintf(("%s: ssh: stream_read_callback: read %ld bytes from %s:%d\n", debug_prefix_time(NULL), - rs->rc->pktlen, rs->rc->hostname, rs->handle)); - (*rs->fn)(rs->arg, rs->rc->pkt, rs->rc->pktlen); -} - -/* - * The callback for the netfd for the event handler - * Determines if this packet is for this security handle, - * and does the real callback if so. - */ -static void -conn_read_callback(cookie) - void *cookie; -{ - struct ssh_conn *rc = cookie; - struct ssh_handle *rh; - pkt_t pkt; - int rval; - - assert(cookie != NULL); - - sshprintf(("%s: ssh: conn_read_callback\n",debug_prefix_time(NULL))); - - /* Read the data off the wire. If we get errors, shut down. */ - rval = recv_token(rc, 60); - sshprintf(("%s: ssh: conn_read_callback: recv_token returned %d\n", debug_prefix_time(NULL), rval)); - if (rval <= 0) { - rc->pktlen = 0; - rc->handle = H_EOF; - rval = event_wakeup((event_id_t)rc); - sshprintf(("%s: ssh: conn_read_callback: event_wakeup return %d\n", debug_prefix_time(NULL), rval)); - /* delete our 'accept' reference */ - if (accept_fn != NULL) - conn_put(rc); - accept_fn = NULL; - return; - } - - /* If there are events waiting on this handle, we're done */ - rval = event_wakeup((event_id_t)rc); - sshprintf(("%s: ssh: conn_read_callback: event_wakeup return %d\n", debug_prefix_time(NULL), rval)); - if (rval > 0) - return; - - /* If there is no accept fn registered, then drop the packet */ - if (accept_fn == NULL) - return; - - rh = alloc(sizeof(*rh)); - security_handleinit(&rh->sech, &ssh_security_driver); - rh->hostname = rc->hostname; - rh->rs = ssh_stream_client(rh, rc->handle); - rh->ev_timeout = NULL; - - sshprintf(("%s: ssh: new connection\n", debug_prefix_time(NULL))); - parse_pkt(&pkt, rc->pkt, rc->pktlen); - sshprintf(("%s: ssh: calling accept_fn\n", debug_prefix_time(NULL))); - (*accept_fn)(&rh->sech, &pkt); -} - -static void -parse_pkt(pkt, buf, bufsize) - pkt_t *pkt; - const void *buf; - size_t bufsize; -{ - const unsigned char *bufp = buf; - - sshprintf(("%s: ssh: parse_pkt: parsing buffer of %d bytes\n", debug_prefix_time(NULL), bufsize)); - - pkt->type = (pktype_t)*bufp++; - bufsize--; - - if (bufsize == 0) { - pkt->body[0] = '\0'; - } else { - if (bufsize > sizeof(pkt->body) - 1) - bufsize = sizeof(pkt->body) - 1; - memcpy(pkt->body, bufp, bufsize); - pkt->body[sizeof(pkt->body) - 1] = '\0'; - } - - sshprintf(("%s: ssh: parse_pkt: %s (%d): \"%s\"\n", debug_prefix_time(NULL), pkt_type2str(pkt->type), - pkt->type, pkt->body)); -} - - -/* - * Transmits a chunk of data over a ssh_handle, adding - * the necessary headers to allow the remote end to decode it. - */ -static int -send_token(rc, handle, buf, len) - struct ssh_conn *rc; - int handle; - const void *buf; - size_t len; -{ - unsigned int netlength, nethandle; - struct iovec iov[3]; - - sshprintf(("%s: ssh: send_token: handle %d writing %d bytes to %s\n", debug_prefix_time(NULL), handle, len, - rc->hostname)); - - assert(sizeof(netlength) == 4); - - /* - * Format is: - * 32 bit length (network byte order) - * 32 bit handle (network byte order) - * data - */ - netlength = htonl(len); - iov[0].iov_base = (void *)&netlength; - iov[0].iov_len = sizeof(netlength); - - nethandle = htonl(handle); - iov[1].iov_base = (void *)&nethandle; - iov[1].iov_len = sizeof(nethandle); - - iov[2].iov_base = (void *)buf; - iov[2].iov_len = len; - - if (net_writev(rc->write, iov, 3) < 0) { - rc->errmsg = newvstralloc(rc->errmsg, "ssh write error to ", - rc->hostname, ": ", strerror(errno), NULL); - return (-1); - } - return (0); -} - -static int -recv_token(rc, timeout) - struct ssh_conn *rc; - int timeout; -{ - unsigned int netint; - - assert(sizeof(netint) == 4); - - assert(rc->read >= 0); - - sshprintf(("%s: ssh: recv_token: reading from %s\n", debug_prefix_time(NULL), rc->hostname)); - - switch (net_read(rc, &netint, sizeof(netint), timeout)) { - case -1: - rc->errmsg = newvstralloc(rc->errmsg, "recv error: ", strerror(errno), - NULL); - sshprintf(("%s: ssh: recv_token: A return(-1)\n", debug_prefix_time(NULL))); - return (-1); - case 0: - rc->pktlen = 0; - sshprintf(("%s: ssh: recv_token: A return(0)\n", debug_prefix_time(NULL))); - return (0); - default: - break; - } - rc->pktlen = ntohl(netint); - if (rc->pktlen > sizeof(rc->pkt)) { - rc->errmsg = newstralloc(rc->errmsg, "recv error: huge packet"); - sshprintf(("%s: ssh: recv_token: B return(-1)\n", debug_prefix_time(NULL))); - return (-1); - } - - switch (net_read(rc, &netint, sizeof(netint), timeout)) { - case -1: - rc->errmsg = newvstralloc(rc->errmsg, "recv error: ", strerror(errno), - NULL); - sshprintf(("%s: ssh: recv_token: C return(-1)\n", debug_prefix_time(NULL))); - return (-1); - case 0: - rc->pktlen = 0; - sshprintf(("%s: ssh: recv_token: D return(0)\n", debug_prefix_time(NULL))); - return (0); - default: - break; - } - rc->handle = ntohl(netint); + safe_fd(-1, 0); - switch (net_read(rc, rc->pkt, rc->pktlen, timeout)) { - case -1: - rc->errmsg = newvstralloc(rc->errmsg, "recv error: ", strerror(errno), - NULL); - sshprintf(("%s: ssh: recv_token: E return(-1)\n", debug_prefix_time(NULL))); - return (-1); - case 0: - rc->pktlen = 0; - break; - default: - break; + if(!xamandad_path || strlen(xamandad_path) <= 1) + xamandad_path = vstralloc(amlibexecdir, "/", "amandad", NULL); + if(!xclient_username || strlen(xclient_username) <= 1) + xclient_username = CLIENT_LOGIN; + if(!ssh_keys || strlen(ssh_keys) <= 1) { + execlp(SSH, SSH, SSH_OPTIONS, "-l", xclient_username, + rc->hostname, xamandad_path, "-auth=ssh", "amdump", "amindexd", + "amidxtaped", (char *)NULL); } - - sshprintf(("%s: ssh: recv_token: read %ld bytes from %s\n", debug_prefix_time(NULL), rc->pktlen, - rc->hostname)); - sshprintf(("%s: ssh: recv_token: end %d\n", debug_prefix_time(NULL),rc->pktlen)); - return (rc->pktlen); -} - -/* - * Writes out the entire iovec - */ -static int -net_writev(fd, iov, iovcnt) - int fd, iovcnt; - struct iovec *iov; -{ - int delta, n, total; - - assert(iov != NULL); - - total = 0; - while (iovcnt > 0) { - /* - * Write the iovec - */ - total += n = writev(fd, iov, iovcnt); - if (n < 0) - return (-1); - if (n == 0) { - errno = EIO; - return (-1); - } - /* - * Iterate through each iov. Figure out what we still need - * to write out. - */ - for (; n > 0; iovcnt--, iov++) { - /* 'delta' is the bytes written from this iovec */ - delta = n < iov->iov_len ? n : iov->iov_len; - /* subtract from the total num bytes written */ - n -= delta; - assert(n >= 0); - /* subtract from this iovec */ - iov->iov_len -= delta; - iov->iov_base = (char *)iov->iov_base + delta; - /* if this iovec isn't empty, run the writev again */ - if (iov->iov_len > 0) - break; - } + else { + execlp(SSH, SSH, SSH_OPTIONS, "-l", xclient_username, + "-i", xssh_keys, rc->hostname, xamandad_path, "-auth=ssh", + "amdump", "amindexd", "amidxtaped", (char *)NULL); } - return (total); -} + error("error: couldn't exec %s: %s", SSH, strerror(errno)); -/* - * Like read(), but waits until the entire buffer has been filled. - */ -static ssize_t -net_read(rc, vbuf, origsize, timeout) - struct ssh_conn *rc; - void *vbuf; - size_t origsize; - int timeout; -{ - char *buf = vbuf, *off; /* ptr arith */ - int nread; - size_t size = origsize; - - sshprintf(("%s: ssh: net_read: begin %d\n", debug_prefix_time(NULL), origsize)); - while (size > 0) { - sshprintf(("%s: ssh: net_read: while %d\n", debug_prefix_time(NULL), size)); - if (rc->readbuf.left == 0) { - if (net_read_fillbuf(rc, timeout, size) < 0) { - sshprintf(("%s: ssh: net_read: end retrun(-1)\n", debug_prefix_time(NULL))); - return (-1); - } - if (rc->readbuf.size == 0) { - sshprintf(("%s: ssh: net_read: end retrun(0)\n", debug_prefix_time(NULL))); - return (0); - } - } - nread = min(rc->readbuf.left, size); - off = rc->readbuf.buf + rc->readbuf.size - rc->readbuf.left; - memcpy(buf, off, nread); - - buf += nread; - size -= nread; - rc->readbuf.left -= nread; - } - sshprintf(("%s: ssh: net_read: end %d\n", debug_prefix_time(NULL), origsize)); - return ((ssize_t)origsize); -} - -/* - * net_read likes to do a lot of little reads. Buffer it. - */ -static int -net_read_fillbuf(rc, timeout, size) - struct ssh_conn *rc; - int timeout; - int size; -{ - fd_set readfds; - struct timeval tv; - if(size > sizeof(rc->readbuf.buf)) size = sizeof(rc->readbuf.buf); - - sshprintf(("%s: ssh: net_read_fillbuf: begin\n", debug_prefix_time(NULL))); - FD_ZERO(&readfds); - FD_SET(rc->read, &readfds); - tv.tv_sec = timeout; - tv.tv_usec = 0; - switch (select(rc->read + 1, &readfds, NULL, NULL, &tv)) { - case 0: - errno = ETIMEDOUT; - /* FALLTHROUGH */ - case -1: - sshprintf(("%s: ssh: net_read_fillbuf: case -1\n", debug_prefix_time(NULL))); - return (-1); - case 1: - sshprintf(("%s: ssh: net_read_fillbuf: case 1\n", debug_prefix_time(NULL))); - assert(FD_ISSET(rc->read, &readfds)); - break; - default: - sshprintf(("%s: ssh: net_read_fillbuf: case default\n", debug_prefix_time(NULL))); - assert(0); - break; - } - rc->readbuf.left = 0; - rc->readbuf.size = read(rc->read, rc->readbuf.buf, size); - if (rc->readbuf.size < 0) - return (-1); - rc->readbuf.left = rc->readbuf.size; - sshprintf(("%s: ssh: net_read_fillbuf: end %d\n", debug_prefix_time(NULL),rc->readbuf.size)); - return (0); + /* should never go here, shut up compiler warning */ + return(-1); } - -#endif /* SSH_SECURITY */