*/
/*
- * $Id: ssh-security.c,v 1.8 2006/02/17 00:58:51 ktill 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.
*
*/
#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
#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.
"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 *,
+ 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;
-{
- struct ssh_handle *rh;
- struct hostent *he;
+ssh_connect(
+ const char * hostname,
+ char * (*conf_fn)(char *, void *),
+ void (*fn)(void *, security_handle_t *, security_status_t),
+ void * arg,
+ void * datap)
+{
+ int result;
+ struct sec_handle *rh;
+ char *amandad_path=NULL, *client_username=NULL, *ssh_keys=NULL;
+ char *client_port = "22";
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) {
+ char *port_str;
+ amandad_path = conf_fn("amandad_path", datap);
+ client_username = conf_fn("client_username", datap);
+ ssh_keys = conf_fn("ssh_keys", datap);
+ port_str = conf_fn("client_port", datap);
+ if (port_str && strlen(port_str) >= 1) {
+ client_port = port_str;
+ }
+ }
+ if(rh->rc->read == -1) {
+ if (runssh(rh->rs->rc, amandad_path, client_username, ssh_keys,
+ client_port) < 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
*/
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;
(*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;
-
- sshprintf(("%s: ssh: conn_get: %s\n", debug_prefix_time(NULL), hostname));
-
- for (rc = connq_first(); rc != NULL; rc = connq_next(rc)) {
- if (strcasecmp(hostname, rc->hostname) == 0)
- break;
+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;
}
- 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);
+ /* make a local copy, to munge */
+ ssh_connection = g_strdup(ssh_connection);
+
+ /* 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';
- 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);
-}
+ /* ---- everything from here on is just a warning, leaving hostname at "" */
-/*
- * 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;
+ SU_INIT(&addr, AF_INET);
- 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);
}
/*
* 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,
+ const char * client_port)
{
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;
+ char *xclient_port = (char *)client_port;
+ 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);
}
+
+ 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(!xclient_port || strlen(xclient_port) <= 1)
+ xclient_port = "22";
+
+ if(!ssh_keys || strlen(ssh_keys) <= 1) {
+ g_debug("exec: %s %s %s %s %s %s %s %s %s",
+ SSH, "SSH_OPTIONS", "-l", xclient_username, "-p", client_port,
+ rc->hostname, xamandad_path, "-auth=ssh");
+ }
+ else {
+ g_debug("exec: %s %s %s %s %s %s %s %s %s %s %s",
+ SSH, "SSH_OPTIONS", "-l", xclient_username, "-p", client_port,
+ "-i", xssh_keys, rc->hostname, xamandad_path, "-auth=ssh");
+ }
+
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]);
case 0:
dup2(wpipe[0], 0);
dup2(rpipe[1], 1);
- dup2(rpipe[1], 2);
break;
default:
rc->read = rpipe[0];
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);
-}
-
-/*
- * 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, 5);
- 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);
-
- 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;
- }
-
- 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;
+ /* drop root privs for good */
+ set_root_privs(-1);
- assert(iov != NULL);
+ safe_fd(-1, 0);
- 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;
- }
+ if(!ssh_keys || strlen(ssh_keys) <= 1) {
+ execlp(SSH, SSH, SSH_OPTIONS, "-l", xclient_username, "-p", client_port,
+ rc->hostname, xamandad_path, "-auth=ssh", (char *)NULL);
}
- return (total);
-}
-
-/*
- * 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;
+ else {
+ execlp(SSH, SSH, SSH_OPTIONS, "-l", xclient_username, "-p", client_port,
+ "-i", xssh_keys, rc->hostname, xamandad_path, "-auth=ssh",
+ (char *)NULL);
}
- sshprintf(("%s: ssh: net_read: end %d\n", debug_prefix_time(NULL), origsize));
- return ((ssize_t)origsize);
-}
+ error("error: couldn't exec %s: %s", SSH, strerror(errno));
-/*
- * 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 */