/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1999 University of Maryland
* 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: security-util.c,v 1.25 2006/07/22 12:04:47 martinea Exp $
*
* sec-security.c - security and transport over sec or a sec-like command.
*
* XXX still need to check for initial keyword on connect so we can skip
* over shell garbage and other stuff that sec might want to spew out.
*/
#include "amanda.h"
#include "amutil.h"
#include "event.h"
#include "packet.h"
#include "shm-ring.h"
#include "security.h"
#include "security-util.h"
#include "stream.h"
#include "sockaddr-util.h"
/*
* This is a queue of open connections
*/
GSList *connq = NULL;
static int newhandle = 1;
static event_id_t newevent = 1;
/*
* Local functions
*/
static void sec_tcp_conn_put(struct tcp_conn *rc);
static void recvpkt_callback(void *, void *, ssize_t);
static void stream_read_callback(void *);
static void stream_read_sync_callback(void *);
static void stream_read_to_shm_ring_callback(void *);
static void sec_tcp_conn_read_cancel(struct tcp_conn *);
static void sec_tcp_conn_read_callback(void *);
static void tcpm_send_token_helper(struct tcp_conn *rc, int handle,
const void *buf, size_t len,
struct iovec **iov, int *nb_iov,
char **envbuf, ssize_t *encsize);
static void tcpm_send_token_callback(void *cookie);
/*
* Authenticate a stream
* Nothing needed for sec. The connection is authenticated by secd
* on startup.
*/
int
sec_stream_auth(
void * s)
{
(void)s; /* Quiet unused parameter warning */
return (0);
}
/*
* Returns the stream id for this stream. This is just the local
* port.
*/
int
sec_stream_id(
void * s)
{
struct sec_stream *rs = s;
assert(rs != NULL);
return (rs->handle);
}
/*
* Setup to handle new incoming connections
*/
void
sec_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 tcp_conn *rc;
rc = sec_tcp_conn_get("localhost", "localhost",0); /* no hostname yet */
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);
}
/*
* frees a handle allocated by the above
*/
void
sec_close(
void * inst)
{
struct sec_handle *rh = inst;
assert(rh != NULL);
auth_debug(1, _("sec: closing handle to %s\n"), rh->hostname);
if (rh->rs != NULL) {
/* This may be null if we get here on an error */
stream_recvpkt_cancel(rh);
security_stream_close(&rh->rs->secstr);
}
/* keep us from getting here again */
rh->sech.driver = NULL;
amfree(rh->dle_hostname);
amfree(rh->hostname);
amfree(rh);
}
/*
* Called when a sec connection is finished connecting and is ready
* to be authenticated.
*/
void
sec_connect_callback(
void * cookie)
{
struct sec_handle *rh = cookie;
g_mutex_lock(security_mutex);
event_release(rh->rs->rc->ev_write);
rh->rs->rc->ev_write = NULL;
if (rh->ev_timeout) {
event_release(rh->ev_timeout);
rh->ev_timeout = NULL;
}
g_mutex_unlock(security_mutex);
(*rh->fn.connect)(rh->arg, &rh->sech, S_OK);
}
/*
* Called if a connection times out before completion.
*/
void
sec_connect_timeout(
void * cookie)
{
struct sec_handle *rh = cookie;
rh->rs->rc->reader_callbacks = g_slist_remove(rh->rs->rc->reader_callbacks,
&rh->rs->r_callback);
event_release(rh->ev_timeout);
rh->ev_timeout = NULL;
(*rh->fn.connect)(rh->arg, &rh->sech, S_TIMEOUT);
}
void
sec_close_connection_none(
void *h G_GNUC_UNUSED,
char *hostname G_GNUC_UNUSED)
{
return;
}
/*
* Transmit a packet.
*/
ssize_t
stream_sendpkt(
void * cookie,
pkt_t * pkt)
{
char *buf;
struct sec_handle *rh = cookie;
size_t len;
char *s;
assert(rh != NULL);
assert(pkt != NULL);
auth_debug(1, _("sec: stream_sendpkt: enter\n"));
if (rh->rc->prefix_packet)
s = rh->rc->prefix_packet(rh, pkt);
else
s = g_strdup("");
len = strlen(pkt->body) + strlen(s) + 2;
buf = g_malloc(len);
buf[0] = (char)pkt->type;
strncpy(&buf[1], s, len - 1);
strncpy(&buf[1 + strlen(s)], pkt->body, (len - strlen(s) - 1));
amfree(s);
auth_debug(1,
_("sec: stream_sendpkt: %s (%d) pkt_t (len %zu) contains:\n\n\"%s\"\n\n"),
pkt_type2str(pkt->type), pkt->type, strlen(pkt->body), pkt->body);
if (security_stream_write(&rh->rs->secstr, buf, len) < 0) {
security_seterror(&rh->sech, "%s", security_stream_geterror(&rh->rs->secstr));
amfree(buf);
return (-1);
}
amfree(buf);
return (0);
}
/*
* Set up to receive a packet asyncronously, and call back when
* it has been read.
*/
void
stream_recvpkt(
void * cookie,
void (*fn)(void *, pkt_t *, security_status_t),
void * arg,
int timeout)
{
struct sec_handle *rh = cookie;
assert(rh != NULL);
auth_debug(1, _("sec: recvpkt registered for %s\n"), 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_create((event_id_t)timeout, EV_TIME,
stream_recvpkt_timeout, rh);
event_activate(rh->ev_timeout);
}
rh->fn.recvpkt = fn;
rh->arg = arg;
security_stream_read(&rh->rs->secstr, recvpkt_callback, rh);
}
/*
* This is called when a handle times out before receiving a packet.
*/
void
stream_recvpkt_timeout(
void * cookie)
{
struct sec_handle *rh = cookie;
assert(rh != NULL);
auth_debug(1, _("sec: recvpkt timeout for %s\n"), rh->hostname);
stream_recvpkt_cancel(rh);
(*rh->fn.recvpkt)(rh->arg, NULL, S_TIMEOUT);
}
/*
* Remove a async receive request from the queue
*/
void
stream_recvpkt_cancel(
void * cookie)
{
struct sec_handle *rh = cookie;
auth_debug(1, _("sec: cancelling recvpkt for %s\n"), rh->hostname);
assert(rh != NULL);
security_stream_read_cancel(&rh->rs->secstr);
if (rh->ev_timeout != NULL) {
event_release(rh->ev_timeout);
rh->ev_timeout = NULL;
}
}
/*
* Write a chunk of data to a stream. Blocks until completion.
*/
GMutex *stream_write_mutex = NULL;
int
tcpm_stream_write(
void * s,
const void *buf,
size_t size)
{
struct sec_stream *rs = s;
assert(rs != NULL);
assert(rs->rc != NULL);
if (!stream_write_mutex) {
stream_write_mutex = g_mutex_new();
}
g_mutex_lock(stream_write_mutex);
auth_debug(6, _("sec: stream_write: writing %zu bytes to %s:%d %d\n"),
size, rs->rc->hostname, rs->handle,
rs->rc->write);
if (tcpm_send_token(rs->rc, rs->handle, &rs->rc->errmsg,
buf, size)) {
security_stream_seterror(&rs->secstr, "%s", rs->rc->errmsg);
g_mutex_unlock(stream_write_mutex);
return (-1);
}
g_mutex_unlock(stream_write_mutex);
return (0);
}
/*
* Write a chunk of data to a stream.
*/
int
tcpm_stream_write_async(
void * s,
void * buf,
size_t size,
void (*fn)(void *, ssize_t, void *, ssize_t),
void * arg)
{
struct sec_stream *rs = s;
int stack_size;
assert(rs != NULL);
assert(rs->rc != NULL);
auth_debug(6, _("sec: stream_write_async: writing %zu bytes to %s:%d %d\n"),
size, rs->rc->hostname, rs->handle,
rs->rc->write);
stack_size = tcpm_send_token_async(rs, buf, size, fn, arg);
return (stack_size);
}
/*
* Submit a request to read some data. Calls back with the given
* function and arg when completed.
*/
void
tcpm_stream_read(
void * s,
void (*fn)(void *, void *, ssize_t),
void * arg)
{
struct sec_stream *rs = s;
assert(rs != NULL);
/*
* Only one read request can be active per stream.
*/
rs->r_callback.handle = rs->handle;
rs->r_callback.s = rs;
rs->r_callback.callback = stream_read_callback;
if (!rs->ev_read_callback) {
rs->ev_read_callback = TRUE;
rs->rc->reader_callbacks = g_slist_prepend(rs->rc->reader_callbacks,
&rs->r_callback);
sec_tcp_conn_read(rs->rc);
}
rs->fn = fn;
rs->arg = arg;
}
/* buffer for tcpm_stream_read_sync function */
static ssize_t sync_pktlen;
static void *sync_pkt;
/*
* Read a chunk of data from a stream. Blocks until completion.
*/
static void
for_event_release(
void * cookie)
{
struct sec_stream *rs = cookie;
event_release(rs->ev_read_sync);
}
ssize_t
tcpm_stream_read_sync(
void * s,
void ** buf)
{
struct sec_stream *rs = s;
assert(rs != NULL);
/*
* Only one read request can be active per stream.
*/
if (rs->ev_read_callback) {
return -1;
}
sync_pktlen = 0;
sync_pkt = NULL;
if (rs->closed_by_network) {
security_stream_seterror(&rs->secstr, "Failed to read from handle %d because server already closed it", rs->handle);
return -1;
}
rs->r_callback.handle = rs->handle;
rs->r_callback.s = rs;
rs->r_callback.callback = stream_read_sync_callback;
if (!rs->ev_read_callback) {
rs->ev_read_callback = TRUE;
rs->rc->reader_callbacks = g_slist_prepend(rs->rc->reader_callbacks,
&rs->r_callback);
sec_tcp_conn_read(rs->rc);
}
g_mutex_lock(security_mutex);
rs->event_id = newevent++;
rs->ev_read_sync = event_create(rs->event_id, EV_WAIT, for_event_release, rs);
event_activate(rs->ev_read_sync);
g_mutex_unlock(security_mutex);
event_wait(rs->ev_read_sync);
rs->ev_read_sync = NULL;
/* Can't use rs or rc, they can be freed */
*buf = sync_pkt;
return (sync_pktlen);
}
void
tcpm_stream_read_to_shm_ring(
void *s,
void (*fn)(void *, void *, ssize_t),
shm_ring_t *shm_ring,
void *arg)
{
struct sec_stream *rs = s;
assert(rs != NULL);
/*
* Only one read request can be active per stream.
*/
rs->r_callback.handle = rs->handle;
rs->r_callback.s = rs;
rs->r_callback.callback = stream_read_to_shm_ring_callback;
if (!rs->ev_read_callback) {
rs->ev_read_callback = TRUE;
rs->rc->reader_callbacks = g_slist_prepend(rs->rc->reader_callbacks,
&rs->r_callback);
sec_tcp_conn_read(rs->rc);
}
rs->fn = fn;
rs->arg = arg;
rs->shm_ring = shm_ring;
rs->ring_init = FALSE;
}
/*
* Cancel a previous stream read request. It's ok if we didn't have a read
* scheduled.
*/
void
tcpm_stream_read_cancel(
void * s)
{
struct sec_stream *rs = s;
assert(rs != NULL);
if (rs->ev_read_callback) {
rs->rc->reader_callbacks = g_slist_remove(rs->rc->reader_callbacks,
&rs->r_callback);
rs->ev_read_callback = FALSE;
sec_tcp_conn_read_cancel(rs->rc);
}
}
/*
* Pause all read requests on the connection.
*/
void
tcpm_stream_pause(
void * s)
{
struct sec_stream *rs = s;
struct tcp_conn *rc = rs->rc;
assert(rs != NULL);
if (rc->ev_read_refcnt == 0) {
return;
}
if (!rc->ev_read) {
return;
}
event_release(rc->ev_read);
rc->ev_read = NULL;
rc->paused = TRUE;
}
/*
* Resume all read requests on the connection.
*/
void
tcpm_stream_resume(
void * s)
{
struct sec_stream *rs = s;
struct tcp_conn *rc = rs->rc;
assert(rs != NULL);
if (!rc->paused) {
return;
}
rc->paused = FALSE;
if (rc->ev_read_refcnt == 0) {
return;
}
if (rc->ev_read) {
return;
}
rc->ev_read = event_create((event_id_t)rc->read, EV_READFD,
sec_tcp_conn_read_callback, rc);
event_activate(rc->ev_read);
}
/*
* Transmits a chunk of data over a rsh_handle, adding
* the necessary headers to allow the remote end to decode it.
*/
static void
tcpm_send_token_helper(
struct tcp_conn *rc,
int handle,
const void *buf,
size_t len,
struct iovec **iov,
int *nb_iov,
char **encbuf,
ssize_t *encsize)
{
guint32 *nethandle = g_malloc(sizeof(guint32));
guint32 *netlength = g_malloc(sizeof(guint32));
time_t logtime;
assert(sizeof(*netlength) == 4);
logtime = time(NULL);
if (logtime > rc->logstamp + 10) {
g_debug("tcpm_send_token: data is still flowing");
rc->logstamp = logtime;
}
auth_debug(6, "tcpm_send_token: write %zd bytes to handle %d\n",
len, handle);
/*
* 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((guint32)handle);
(*iov)[1].iov_base = (void *)nethandle;
(*iov)[1].iov_len = sizeof(*nethandle);
*encbuf = (char *)buf;
*encsize = len;
if(len == 0) {
(*iov)[2].iov_base = NULL;
(*iov)[2].iov_len = 0;
*nb_iov = 2;
}
else {
if (rc->driver->data_encrypt == NULL) {
(*iov)[2].iov_base = (void *)buf;
(*iov)[2].iov_len = len;
} else {
/* (the extra (void *) cast is to quiet type-punning warnings) */
rc->driver->data_encrypt(rc, (void *)buf, len, (void **)(void *)encbuf, encsize);
(*iov)[2].iov_base = (void *)*encbuf;
(*iov)[2].iov_len = *encsize;
*netlength = htonl(*encsize);
}
*nb_iov = 3;
}
if (debug_auth >= 3) {
crc_t crc;
crc32_init(&crc);
crc32_add((uint8_t *)buf, len, &crc);
g_debug("packet send CRC: %d %08x:%llu", handle, crc32_finish(&crc), (long long)crc.size);
}
}
ssize_t
tcpm_send_token(
struct tcp_conn *rc,
int handle,
char ** errmsg,
const void *buf,
size_t len)
{
struct iovec iov[3];
struct iovec iov_copy[3];
struct iovec *iovx = iov;
int nb_iov = 3;
char *encbuf;
ssize_t encsize;
int rval;
int save_errno;
tcpm_send_token_helper(rc, handle, buf, len, &iovx, &nb_iov, &encbuf, &encsize);
/* copy iov because data_write modify it */
memcpy(iov_copy, iov, 3*sizeof(struct iovec));
rval = rc->driver->data_write(rc, iov_copy, nb_iov);
save_errno = errno;
g_free(iov[0].iov_base);
g_free(iov[1].iov_base);
if (len != 0 && rc->driver->data_encrypt != NULL && buf != encbuf) {
amfree(encbuf);
}
if (rval < 0) {
if (errmsg) {
g_free(*errmsg);
*errmsg = g_strdup_printf(_("write error to: %s"),
strerror(save_errno));
}
return (-1);
}
return (0);
}
ssize_t
tcpm_send_token_async(
struct sec_stream *rs,
void * buf,
size_t len,
void (*fn)(void *, ssize_t, void *, ssize_t),
void * arg)
{
struct iovec iov[3];
struct iovec *iovx = iov;
int nb_iov = 3;
char *encbuf;
ssize_t encsize;
async_write_data *awd;
int handle = rs->handle;
tcpm_send_token_helper(rs->rc, handle, buf, len, &iovx, &nb_iov, &encbuf, &encsize);
awd = g_new0(struct async_write_data, 1);
memcpy(awd->iov, iov, 3*sizeof(struct iovec));
awd->nb_iov = nb_iov;
memcpy(awd->copy_iov, iov, 3*sizeof(struct iovec));
awd->copy_nb_iov = nb_iov;
awd->buf = encbuf;
awd->written = 0;
awd->fn = fn;
awd->arg = arg;
if (encbuf != buf)
amfree(buf);
rs->rc->async_write_data_list = g_list_append(rs->rc->async_write_data_list, awd);
rs->rc->async_write_data_size += 8 + len;
if(!rs->rc->ev_write) {
rs->rc->ev_write = event_create(
(event_id_t)(rs->rc->write),
EV_WRITEFD, tcpm_send_token_callback, rs);
event_activate(rs->rc->ev_write);
}
return (rs->rc->async_write_data_size);
}
static void
tcpm_send_token_callback(
void * cookie)
{
struct sec_stream *rs = cookie;
gboolean done = FALSE;
async_write_data *awd = NULL;
if (rs->rc->async_write_data_list) {
int rval;
int save_errno;
awd = (async_write_data *)rs->rc->async_write_data_list->data;
rval = rs->rc->driver->data_write_non_blocking(rs->rc, awd->copy_iov, awd->copy_nb_iov);
save_errno = errno;
if (rval < 0) {
security_stream_seterror(&rs->secstr, "write error to: %s", strerror(save_errno));
if (awd->fn) {
(*awd->fn)(awd->arg, rs->rc->async_write_data_size, NULL, -1);
}
return;
}
awd->written += rval;
rs->rc->async_write_data_size -= rval;
/* if completely written */
if (awd->copy_iov[0].iov_len == 0 &&
awd->copy_iov[1].iov_len == 0 &&
(awd->nb_iov < 2 || awd->copy_iov[2].iov_len == 0)) {
if (awd->fn) {
(*awd->fn)(awd->arg, rs->rc->async_write_data_size, awd->buf, awd->written);
}
g_free(awd->iov[0].iov_base);
g_free(awd->iov[1].iov_base);
rs->rc->async_write_data_list = g_list_remove(rs->rc->async_write_data_list,
awd);
done = TRUE;
}
}
/* unschedule us */
if (!rs->rc->async_write_data_list) {
event_release(rs->rc->ev_write);
rs->rc->ev_write = NULL;
}
if (done && !awd->buf) { /* closing */
if (rs->handle < 10000 || rs->closed_by_network == 1) {
security_stream_read_cancel(&rs->secstr);
rs->closed_by_network = 1;
sec_tcp_conn_put(rs->rc);
}
rs->closed_by_me = 1;
if (rs->closed_by_network) {
amfree(((security_stream_t *)rs)->error);
}
}
if (done) {
g_free(awd);
}
return;
}
/*
* return -2 for incomplete packet
* return -1 on error
* return 0 on EOF: *handle = H_EOF && *size = 0 if socket closed
* return 0 on EOF: *handle = handle && *size = 0 if stream closed
* return size : *handle = handle && *size = size for data read
*/
ssize_t
tcpm_recv_token(
struct tcp_conn *rc,
int * handle,
char ** errmsg,
char ** buf,
ssize_t * size)
{
ssize_t rval;
struct sec_stream *rs = NULL;
GSList *reader_callbacks;
assert(sizeof(rc->netint) == 8);
if (rc->size_header_read < (ssize_t)sizeof(rc->netint)) {
rval = rc->driver->data_read(rc,
((char *)&rc->netint) + rc->size_header_read,
sizeof(rc->netint) - rc->size_header_read, 0);
if (rval == -1) {
if (errmsg) {
g_free(*errmsg);
*errmsg = g_strdup_printf(_("Xrecv error: %s"),
strerror(errno));
}
auth_debug(1, _("tcpm_recv_token: A return(-1)\n"));
return(-1);
} else if (rval == 0) {
*size = 0;
*handle = H_EOF;
g_free(*errmsg);
*errmsg = g_strdup("SOCKET_EOF");
auth_debug(1, "tcpm_recv_token: A return(0)\n");
return(0);
} else if (rval < (ssize_t)sizeof(rc->netint) - rc->size_header_read) {
rc->size_header_read += rval;
return(-2);
}
rc->size_header_read += rval;
amfree(rc->buffer);
*size = (ssize_t)ntohl(rc->netint[0]);
*handle = (int)ntohl(rc->netint[1]);
rc->buffer = NULL;
rc->size_buffer_read = 0;
/* amanda protocol packet can be above NETWORK_BLOCK_BYTES */
if (*size > 128*NETWORK_BLOCK_BYTES || *size < 0) {
if (isprint((int)(*size ) & 0xFF) &&
isprint((int)(*size >> 8 ) & 0xFF) &&
isprint((int)(*size >> 16) & 0xFF) &&
isprint((int)(*size >> 24) & 0xFF) &&
isprint((*handle ) & 0xFF) &&
isprint((*handle >> 8 ) & 0xFF) &&
isprint((*handle >> 16) & 0xFF) &&
isprint((*handle >> 24) & 0xFF)) {
char s[201];
char *s1;
int i;
s[0] = ((int)(*size) >> 24) & 0xFF;
s[1] = ((int)(*size) >> 16) & 0xFF;
s[2] = ((int)(*size) >> 8) & 0xFF;
s[3] = ((int)(*size) ) & 0xFF;
s[4] = (*handle >> 24) & 0xFF;
s[5] = (*handle >> 16) & 0xFF;
s[6] = (*handle >> 8 ) & 0xFF;
s[7] = (*handle ) & 0xFF;
i = 8; s[i] = ' ';
while(i<100 && isprint((int)s[i]) && s[i] != '\n') {
rval = rc->driver->data_read(rc, &s[i], 1, 0);
switch (rval) {
case -1: s[i] = '\0'; break;
case 0: s[i] = '\0'; break;
default:
dbprintf(_("read: %c\n"), s[i]); i++; s[i]=' ';
break;
}
}
s[i] = '\0';
s1 = quote_string(s);
g_free(*errmsg);
*errmsg = g_strdup_printf(_("tcpm_recv_token: invalid size: %s"),
s1);
dbprintf(_("tcpm_recv_token: invalid size %s\n"), s1);
amfree(s1);
} else {
g_free(*errmsg);
*errmsg = g_strdup("tcpm_recv_token: invalid size");
dbprintf("tcpm_recv_token: invalid size %zd\n", *size);
}
*size = -1;
return -1;
}
if (*size == 0) {
auth_debug(1, "tcpm_recv_token: read EOF from %d\n", *handle);
g_free(*errmsg);
*errmsg = g_strdup("EOF");
rc->size_header_read = 0;
rs = NULL;
for (reader_callbacks = rc->reader_callbacks; reader_callbacks != NULL;
reader_callbacks = reader_callbacks->next) {
reader_callback *r_callback = (reader_callback *)reader_callbacks->data;
if (r_callback->s->handle == rc->handle) {
rs = r_callback->s;
break;
}
}
if (rs && rs->shm_ring) {
rs->shm_ring->mc->eof_flag = TRUE;
sem_post(rs->shm_ring->sem_read);
sem_post(rs->shm_ring->sem_read);
}
return 0;
}
}
*size = (ssize_t)ntohl(rc->netint[0]);
*handle = (int)ntohl(rc->netint[1]);
if (!rs) {
for (reader_callbacks = rc->reader_callbacks; reader_callbacks != NULL;
reader_callbacks = reader_callbacks->next) {
reader_callback *r_callback = (reader_callback *)reader_callbacks->data;
if (r_callback->s->handle == rc->handle) {
rs = r_callback->s;
break;
}
}
}
if (!rs || !rs->shm_ring) {
if (!rc->buffer)
rc->buffer = g_malloc((size_t)*size);
rval = rc->driver->data_read(rc, rc->buffer + rc->size_buffer_read,
(size_t)*size - rc->size_buffer_read, 0);
} else {
size_t to_read;
uint64_t write_offset;
uint64_t readx;
uint64_t written;
uint64_t shm_ring_size;
void *decbuf;
ssize_t decsize;
char *buf = NULL;
size_t size_read = 0;
if (!rs->ring_init) {
shm_ring_producer_set_size(rs->shm_ring, NETWORK_BLOCK_BYTES*8, NETWORK_BLOCK_BYTES);
rs->ring_init = TRUE;
}
to_read = (size_t)*size - rc->size_buffer_read;
write_offset = rs->shm_ring->mc->write_offset;
written = rs->shm_ring->mc->written;
shm_ring_size = rs->shm_ring->mc->ring_size;
if (rc->driver->data_decrypt) {
// read to a buffer
if (!rc->buffer)
rc->buffer = g_malloc((size_t)*size);
rval = rc->driver->data_read(rc, rc->buffer + rc->size_buffer_read,
(size_t)*size - rc->size_buffer_read, 0);
if (rval < 0) {
g_free(*errmsg);
*errmsg = g_strdup_printf("recv error: data_read failed");
auth_debug(1, _(" recv error: D return(-1)\n"));
return -1;
}
if (rval < (ssize_t)*size - rc->size_buffer_read) {
rc->size_buffer_read += rval;
return (-2);
}
// decrypt to another buffer
buf = rc->buffer;
rc->buffer = NULL;
rc->driver->data_decrypt(rc, buf, *size, &decbuf, &decsize);
if (buf != (char *)decbuf) {
amfree(buf);
buf = (char *)decbuf;
}
*size = decsize;
to_read = *size;
} else {
to_read = *size - rc->size_buffer_read;
}
rval = 1;
while (to_read > 0 && rval > 0) {
size_t avail;
size_t usable;
/* wait for enough space in the ring to read the packet */
while (!rs->shm_ring->mc->cancelled) {
readx = rs->shm_ring->mc->readx;
avail = shm_ring_size - (written - readx);
if (avail >= to_read)
break;
if (avail >= shm_ring_size/4)
break;
if (shm_ring_sem_wait(rs->shm_ring, rs->shm_ring->sem_write) != 0) {
g_free(*errmsg);
*errmsg = g_strdup_printf("recv error: sem_wait(sem_write) failed");
return -1;
}
}
if (rs->shm_ring->mc->cancelled) {
g_free(*errmsg);
*errmsg = g_strdup_printf("recv error: shm_ring is cancelled");
auth_debug(1, _("tcpm_recv_token: D return(-1)\n"));
return -1;
}
usable = to_read;
if (usable > avail) {
usable = avail;
}
if (rc->driver->data_decrypt) {
// copy from the decrypted buffer
if (write_offset + usable <= shm_ring_size) {
memcpy(rs->shm_ring->data + write_offset , buf, usable);
} else {
memcpy(rs->shm_ring->data + write_offset, buf, shm_ring_size - write_offset);
memcpy(rs->shm_ring->data, buf + shm_ring_size - write_offset, usable -(shm_ring_size - write_offset));
}
rval = usable;
} else {
if (usable > shm_ring_size - write_offset)
usable = shm_ring_size - write_offset;
rval = rc->driver->data_read(rc, rs->shm_ring->data + write_offset,
usable, 0);
}
if (rval > 0) {
size_read += rval;
if (rs->shm_ring->mc->written == 0 && rs->shm_ring->mc->need_sem_ready) {
sem_post(rs->shm_ring->sem_ready);
if (shm_ring_sem_wait(rs->shm_ring, rs->shm_ring->sem_start) != 0) {
g_free(*errmsg);
*errmsg = g_strdup_printf("recv error: sem_wait(sem_start) failed");
amfree(buf);
return -1;
}
}
write_offset += rval;
if (write_offset >= shm_ring_size) {
write_offset -= shm_ring_size;
}
rs->shm_ring->mc->write_offset = write_offset;
rs->shm_ring->mc->written += rval;
rs->shm_ring->data_avail += rval;
if (rs->shm_ring->data_avail >= rs->shm_ring->mc->consumer_block_size) {
sem_post(rs->shm_ring->sem_read);
rs->shm_ring->data_avail -= rs->shm_ring->mc->consumer_block_size;
}
rc->size_buffer_read += rval;
to_read -= rval;
if (rc->size_buffer_read < (ssize_t)*size && to_read == 0) {
amfree(buf);
return -2;
}
}
}
if (rval < 0) {
g_free(*errmsg);
*errmsg = g_strdup_printf(_("Yrecv error: %s"), strerror(errno));
g_debug("tcpm_recv_token: cancelling shm-ring because rval < 0");
rs->shm_ring->mc->cancelled = TRUE;
rs->shm_ring->mc->eof_flag = TRUE;
sem_post(rs->shm_ring->sem_read);
sem_post(rs->shm_ring->sem_read);
auth_debug(1, _("tcpm_recv_token: C return(-1)\n"));
amfree(buf);
return (-1);
} else if (rval == 0) {
g_free(*errmsg);
*errmsg = g_strdup("SOCKET_EOF");
*size = 0;
*handle = H_EOF;
rs->shm_ring->mc->eof_flag = TRUE;
sem_post(rs->shm_ring->sem_read);
sem_post(rs->shm_ring->sem_read);
auth_debug(1, "tcpm_recv_token: C return(0)\n");
amfree(buf);
return (0);
} else {
*size = size_read;
rc->size_header_read = 0;
rc->size_buffer_read = 0;
amfree(buf);
return (*size);
}
}
if (rval == -1) {
g_free(*errmsg);
*errmsg = g_strdup_printf(_("Zrecv error: %s"), strerror(errno));
auth_debug(1, _("tcpm_recv_token: B return(-1)\n"));
return (-1);
} else if (rval == 0) {
*size = 0;
*handle = H_EOF;
g_free(*errmsg);
*errmsg = g_strdup("SOCKET_EOF");
auth_debug(1, "tcpm_recv_token: B return(0)\n");
return (0);
} else if (rval < (ssize_t)*size - rc->size_buffer_read) {
rc->size_buffer_read += rval;
return (-2);
}
rc->size_buffer_read += rval;
amfree(*buf);
*buf = rc->buffer;
rc->size_header_read = 0;
rc->size_buffer_read = 0;
rc->buffer = NULL;
auth_debug(6, _("tcpm_recv_token: read %zd bytes from %d\n"), *size, *handle);
if (*size > 0 && rc->driver->data_decrypt != NULL) {
void *decbuf;
ssize_t decsize;
rc->driver->data_decrypt(rc, *buf, *size, &decbuf, &decsize);
if (*buf != (char *)decbuf) {
amfree(*buf);
*buf = (char *)decbuf;
}
*size = decsize;
}
if (debug_auth >= 3) {
crc_t crc;
crc32_init(&crc);
crc32_add((uint8_t *)*buf, *size, &crc);
g_debug("packet receive CRC: %d %08x:%llu", *handle, crc32_finish(&crc), (long long)crc.size);
}
return((*size));
}
void
tcpm_close_connection(
void *h,
char *hostname)
{
struct sec_handle *rh = h;
auth_debug(1, _("tcpm_close_connection: closing connection to %s : %s\n"), rh->hostname, hostname);
if (rh && rh->rc && rh->rc->read >= 0) {
rh->rc->toclose = 1;
sec_tcp_conn_put(rh->rc);
}
}
/*
* Accept an incoming connection on a stream_server socket
* Nothing needed for tcpma.
*/
int
tcpma_stream_accept(
void * s)
{
(void)s; /* Quiet unused parameter warning */
return (0);
}
/*
* Return a connected stream. For sec, this means setup a stream
* with the supplied handle.
*/
void *
tcpma_stream_client(
void * h,
int id)
{
struct sec_handle *rh = h;
struct sec_stream *rs;
assert(rh != NULL);
if (id <= 0) {
security_seterror(&rh->sech,
_("%d: invalid security stream id"), id);
return (NULL);
}
rs = g_new0(struct sec_stream, 1);
security_streaminit(&rs->secstr, rh->sech.driver);
rs->handle = id;
rs->ev_read_callback = FALSE;
rs->closed_by_me = 0;
rs->closed_by_network = 0;
if (rh->rc) {
rs->rc = rh->rc;
rh->rc->refcnt++;
}
else {
rs->rc = sec_tcp_conn_get(rh->dle_hostname, rh->hostname, 0);
rs->rc->driver = rh->sech.driver;
rh->rc = rs->rc;
}
auth_debug(1, _("sec: stream_client: connected to stream %d\n"), id);
return (rs);
}
/*
* Create the server end of a stream. For sec, this means setup a stream
* object and allocate a new handle for it.
*/
void *
tcpma_stream_server(
void * h)
{
struct sec_handle *rh = h;
struct sec_stream *rs;
assert(rh != NULL);
rs = g_new0(struct sec_stream, 1);
security_streaminit(&rs->secstr, rh->sech.driver);
rs->closed_by_me = 0;
rs->closed_by_network = 0;
if (rh->rc) {
rs->rc = rh->rc;
rs->rc->refcnt++;
}
else {
rs->rc = sec_tcp_conn_get(rh->dle_hostname, rh->hostname, 0);
rs->rc->driver = rh->sech.driver;
rh->rc = rs->rc;
}
/*
* Stream should already be setup!
*/
if (rs->rc->read < 0) {
sec_tcp_conn_put(rs->rc);
amfree(rs->secstr.error);
security_seterror(&rh->sech, _("lost connection to %s"), rh->hostname);
return (NULL);
}
assert(g_str_equal(rh->hostname, rs->rc->hostname));
/*
* so as not to conflict with the amanda server's handle numbers,
* we start at 500000 and work down
*/
g_mutex_lock(security_mutex);
rs->handle = 500000 - newhandle++;
g_mutex_unlock(security_mutex);
rs->ev_read_callback = FALSE;
auth_debug(1, _("sec: stream_server: created stream %d\n"), rs->handle);
return (rs);
}
/*
* Close and unallocate resources for a stream.
*/
void
tcpma_stream_close(
void * s)
{
struct sec_stream *rs = s;
char buf = 0;
assert(rs != NULL);
auth_debug(1, _("sec: tcpma_stream_close: closing stream %d\n"), rs->handle);
if (rs->rc->write != -1)
tcpm_stream_write(rs, &buf, 0);
if (rs->handle < 10000 || rs->closed_by_network == 1) {
security_stream_read_cancel(&rs->secstr);
rs->closed_by_network = 1;
sec_tcp_conn_put(rs->rc);
}
rs->closed_by_me = 1;
if (rs->closed_by_network) {
amfree(((security_stream_t *)rs)->error);
}
}
/*
* Close and unallocate resources for a stream.
*/
void
tcpma_stream_close_async(
void * s,
void (*fn)(void *, ssize_t, void *, ssize_t),
void * arg)
{
struct sec_stream *rs = s;
//char buf = 0;
assert(rs != NULL);
auth_debug(1, _("sec: tcpma_stream_close_async: closing stream %d\n"), rs->handle);
if (rs->rc->write != -1)
tcpm_stream_write_async(rs, NULL, 0, fn, arg);
}
/*
* Create the server end of a stream. For bsdudp, this means setup a tcp
* socket for receiving a connection.
*/
void *
tcp1_stream_server(
void * h)
{
struct sec_stream *rs = NULL;
struct sec_handle *rh = h;
assert(rh != NULL);
rs = g_new0(struct sec_stream, 1);
security_streaminit(&rs->secstr, rh->sech.driver);
rs->closed_by_me = 0;
rs->closed_by_network = 0;
if (rh->rc) {
rs->rc = rh->rc;
g_mutex_lock(security_mutex);
rs->handle = 500000 - newhandle++;
g_mutex_unlock(security_mutex);
rs->rc->refcnt++;
rs->socket = 0; /* the socket is already opened */
}
else {
rh->rc = sec_tcp_conn_get(rh->dle_hostname, rh->hostname, 1);
rh->rc->driver = rh->sech.driver;
rs->rc = rh->rc;
rs->socket = stream_server(SU_GET_FAMILY(&rh->udp->peer), &rs->port,
STREAM_BUFSIZE, STREAM_BUFSIZE, 0);
if (rs->socket < 0) {
security_seterror(&rh->sech,
_("can't create server stream: %s"), strerror(errno));
amfree(rs->secstr.error);
return (NULL);
}
rh->rc->read = rs->socket;
rh->rc->write = rs->socket;
rs->handle = (int)rs->port;
}
rs->fd = -1;
rs->ev_read_callback = FALSE;
return (rs);
}
/*
* Accepts a new connection on unconnected streams. Assumes it is ok to
* block on accept()
*/
int
tcp1_stream_accept(
void * s)
{
struct sec_stream *bs = s;
assert(bs != NULL);
assert(bs->socket != -1);
assert(bs->fd < 0);
if (bs->socket > 0) {
bs->fd = stream_accept(bs->socket, 30, STREAM_BUFSIZE, STREAM_BUFSIZE);
if (bs->fd < 0) {
security_stream_seterror(&bs->secstr,
_("can't accept new stream connection: %s"),
strerror(errno));
return (-1);
}
bs->rc->read = bs->fd;
bs->rc->write = bs->fd;
}
return (0);
}
/*
* Return a connected stream
*/
void *
tcp1_stream_client(
void * h,
int id)
{
struct sec_stream *rs = NULL;
struct sec_handle *rh = h;
char *msg = NULL;
assert(rh != NULL);
rs = g_new0(struct sec_stream, 1);
security_streaminit(&rs->secstr, rh->sech.driver);
rs->handle = id;
rs->ev_read_callback = FALSE;
rs->closed_by_me = 0;
rs->closed_by_network = 0;
if (rh->rc) {
rs->rc = rh->rc;
rh->rc->refcnt++;
}
else {
rh->rc = sec_tcp_conn_get(rh->dle_hostname, rh->hostname, 1);
rh->rc->driver = rh->sech.driver;
rs->rc = rh->rc;
rh->rc->read = stream_client(NULL, rh->hostname, (in_port_t)id,
STREAM_BUFSIZE, STREAM_BUFSIZE, &rs->port, 0, &msg);
if (msg) {
security_seterror(&rh->sech,
"can't connect stream to %s port %d: %s",
rh->hostname, id, msg);
g_free(msg);
return NULL;
}
if (rh->rc->read < 0) {
security_seterror(&rh->sech,
_("can't connect stream to %s port %d: %s"),
rh->hostname, id, strerror(errno));
amfree(rs->secstr.error);
return (NULL);
}
rh->rc->write = rh->rc->read;
}
rs->socket = -1; /* we're a client */
rh->rs = rs;
return (rs);
}
int
tcp_stream_write(
void * s,
const void *buf,
size_t size)
{
struct sec_stream *rs = s;
time_t logtime;
assert(rs != NULL);
logtime = time(NULL);
if (rs->rc && logtime > rs->rc->logstamp + 10) {
g_debug("tcp_stream_write: data is still flowing");
rs->rc->logstamp = logtime;
}
if (full_write(rs->fd, buf, size) < size) {
security_stream_seterror(&rs->secstr,
_("write error on stream %d: %s"), rs->port, strerror(errno));
return (-1);
}
return (0);
}
/*
* Write a chunk of data to a stream. Blocks until completion.
*/
int
tcp_stream_write_async(
void * s,
void * buf,
size_t size,
void (*fn)(void *, ssize_t, void *, ssize_t),
void * arg)
{
int r = tcp_stream_write(s, buf, size);
(*fn)(arg, 0, buf, r);
return 0;
}
char *
bsd_prefix_packet(
void * h,
pkt_t * pkt)
{
struct sec_handle *rh = h;
struct passwd *pwd;
char *buf;
if (pkt->type != P_REQ)
return g_strdup("");
if ((pwd = getpwuid(geteuid())) == NULL) {
security_seterror(&rh->sech,
_("can't get login name for my uid %ld"),
(long)geteuid());
return g_strdup("");
}
buf = g_malloc(16+strlen(pwd->pw_name));
strncpy(buf, "SECURITY USER ", (16 + strlen(pwd->pw_name)));
strncpy(&buf[14], pwd->pw_name, (16 + strlen(pwd->pw_name) - 14));
buf[14 + strlen(pwd->pw_name)] = '\n';
buf[15 + strlen(pwd->pw_name)] = '\0';
return (buf);
}
/*
* Check the security of a received packet. Returns negative on security
* violation, or returns 0 if ok. Removes the security info from the pkt_t.
*/
int
bsd_recv_security_ok(
struct sec_handle * rh,
pkt_t * pkt,
int need_priv_port)
{
char *tok, *security, *body, *result;
char *service = NULL, *serviceX, *serviceY;
char *security_line;
char *s;
size_t len;
in_port_t port;
/*
* Now, find the SECURITY line in the body, and parse it out
* into an argv.
*/
if (strncmp_const(pkt->body, "SECURITY ") == 0) {
security = pkt->body;
len = 0;
while(*security != '\n' && len < pkt->size) {
security++;
len++;
}
if(*security == '\n') {
body = security+1;
*security = '\0';
security_line = g_strdup(pkt->body);
security = pkt->body + strlen("SECURITY ");
} else {
body = pkt->body;
security_line = NULL;
security = NULL;
}
} else {
body = pkt->body;
security_line = NULL;
security = NULL;
}
/*
* Now, find the SERVICE line in the body, and parse it out
* into an argv.
*/
s = body;
if (strncmp_const_skip_no_var(s, "SERVICE ", s) == 0) {
serviceX = g_strdup(s);
serviceY = strtok(serviceX, "\n");
if (serviceY)
service = g_strdup(serviceY);
amfree(serviceX);
}
/*
* We need to do different things depending on which type of packet
* this is.
*/
switch (pkt->type) {
case P_REQ:
/*
* Request packets must come from a reserved port
*/
port = SU_GET_PORT(&rh->peer);
if (need_priv_port && port >= IPPORT_RESERVED) {
security_seterror(&rh->sech,
_("host %s: port %u not secure"), rh->hostname,
(unsigned int)port);
amfree(service);
amfree(security_line);
return (-1);
}
if (!service) {
security_seterror(&rh->sech,
_("packet as no SERVICE line"));
amfree(security_line);
return (-1);
}
/*
* Request packets contain a remote username. We need to check
* that we allow it in.
*
* They will look like:
* SECURITY USER [username]
*/
/* there must be some security info */
if (security == NULL) {
security_seterror(&rh->sech,
_("no bsd SECURITY for P_REQ"));
amfree(service);
amfree(security_line);
return (-1);
}
/* second word must be USER */
if ((tok = strtok(security, " ")) == NULL) {
security_seterror(&rh->sech,
_("SECURITY line: %s"), security_line);
amfree(service);
amfree(security_line);
return (-1); /* default errmsg */
}
if (!g_str_equal(tok, "USER")) {
security_seterror(&rh->sech,
_("REQ SECURITY line parse error, expecting USER, got %s"), tok);
amfree(service);
amfree(security_line);
return (-1);
}
/* the third word is the username */
if ((tok = strtok(NULL, "")) == NULL) {
security_seterror(&rh->sech,
_("SECURITY line: %s"), security_line);
amfree(service);
amfree(security_line);
return (-1); /* default errmsg */
}
if ((result = check_user(rh, tok, service)) != NULL) {
security_seterror(&rh->sech, "%s", result);
amfree(service);
amfree(result);
amfree(security_line);
return (-1);
}
/* we're good to go */
break;
default:
break;
}
amfree(service);
amfree(security_line);
/*
* If there is security info at the front of the packet, we need to
* shift the rest of the data up and nuke it.
*/
if (body != pkt->body)
memmove(pkt->body, body, strlen(body) + 1);
return (0);
}
/*
* Transmit a packet. Add security information first.
*/
ssize_t
udpbsd_sendpkt(
void * cookie,
pkt_t * pkt)
{
struct sec_handle *rh = cookie;
struct passwd *pwd;
assert(rh != NULL);
assert(pkt != NULL);
auth_debug(1, _("udpbsd_sendpkt: enter\n"));
/*
* Initialize this datagram, and add the header
*/
dgram_zero(&rh->udp->dgram);
dgram_cat(&rh->udp->dgram, "%s", pkthdr2str(rh, pkt));
/*
* Add the security info. This depends on which kind of packet we're
* sending.
*/
switch (pkt->type) {
case P_REQ:
/*
* Requests get sent with our username in the body
*/
if ((pwd = getpwuid(geteuid())) == NULL) {
security_seterror(&rh->sech,
_("can't get login name for my uid %ld"), (long)getuid());
return (-1);
}
dgram_cat(&rh->udp->dgram, _("SECURITY USER %s\n"), pwd->pw_name);
break;
default:
break;
}
/*
* Add the body, and send it
*/
dgram_cat(&rh->udp->dgram, "%s", pkt->body);
auth_debug(1,
_("sec: udpbsd_sendpkt: %s (%d) pkt_t (len %zu) contains:\n\n\"%s\"\n\n"),
pkt_type2str(pkt->type), pkt->type, strlen(pkt->body), pkt->body);
if (dgram_send_addr(&rh->peer, &rh->udp->dgram) != 0) {
security_seterror(&rh->sech,
_("send %s to %s failed: %s"), pkt_type2str(pkt->type),
rh->hostname, strerror(errno));
return (-1);
}
return (0);
}
void
udp_close(
void * cookie)
{
struct sec_handle *rh = cookie;
if (rh->proto_handle == NULL) {
return;
}
auth_debug(1, _("udp: close handle '%s'\n"), rh->proto_handle);
udp_recvpkt_cancel(rh);
if (rh->next) {
rh->next->prev = rh->prev;
}
else {
rh->udp->bh_last = rh->prev;
}
if (rh->prev) {
rh->prev->next = rh->next;
}
else {
rh->udp->bh_first = rh->next;
}
amfree(rh->proto_handle);
amfree(rh->hostname);
amfree(rh);
}
/*
* Set up to receive a packet asynchronously, and call back when it has
* been read.
*/
void
udp_recvpkt(
void * cookie,
void (*fn)(void *, pkt_t *, security_status_t),
void * arg,
int timeout)
{
struct sec_handle *rh = cookie;
auth_debug(1, _("udp_recvpkt(cookie=%p, fn=%p, arg=%p, timeout=%u)\n"),
cookie, fn, arg, timeout);
assert(rh != NULL);
assert(fn != NULL);
/*
* Subsequent recvpkt calls override previous ones
*/
if (rh->ev_read == NULL) {
udp_addref(rh->udp, &udp_netfd_read_callback);
rh->ev_read = event_create(rh->event_id, EV_WAIT,
udp_recvpkt_callback, rh);
event_activate(rh->ev_read);
}
if (rh->ev_timeout != NULL)
event_release(rh->ev_timeout);
if (timeout < 0) {
rh->ev_timeout = NULL;
} else {
rh->ev_timeout = event_create((event_id_t)timeout, EV_TIME,
udp_recvpkt_timeout, rh);
event_activate(rh->ev_timeout);
}
rh->fn.recvpkt = fn;
rh->arg = arg;
}
/*
* Remove a async receive request on this handle from the queue.
* If it is the last one to be removed, then remove the event
* handler for our network fd
*/
void
udp_recvpkt_cancel(
void * cookie)
{
struct sec_handle *rh = cookie;
assert(rh != NULL);
if (rh->ev_read != NULL) {
udp_delref(rh->udp);
event_release(rh->ev_read);
rh->ev_read = NULL;
}
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.
*/
void
udp_recvpkt_callback(
void * cookie)
{
struct sec_handle *rh = cookie;
void (*fn)(void *, pkt_t *, security_status_t);
void *arg;
auth_debug(1, _("udp: receive handle '%s' netfd '%s'\n"),
rh->proto_handle, rh->udp->handle);
assert(rh != NULL);
/* if it doesn't correspond to this handle, something is wrong */
assert(g_str_equal(rh->proto_handle, rh->udp->handle));
/* if it didn't come from the same host/port, forget it */
if (cmp_sockaddr(&rh->peer, &rh->udp->peer, 0) != 0) {
amfree(rh->udp->handle);
dbprintf(_("not from same host\n"));
dump_sockaddr(&rh->peer);
dump_sockaddr(&rh->udp->peer);
return;
}
/*
* We need to cancel the recvpkt request before calling the callback
* because the callback may reschedule us.
*/
fn = rh->fn.recvpkt;
arg = rh->arg;
udp_recvpkt_cancel(rh);
/*
* Check the security of the packet. If it is bad, then pass NULL
* to the packet handling function instead of a packet.
*/
if (rh->udp->recv_security_ok &&
rh->udp->recv_security_ok(rh, &rh->udp->pkt, rh->udp->need_priv_port) < 0) {
(*fn)(arg, NULL, S_ERROR);
} else {
(*fn)(arg, &rh->udp->pkt, S_OK);
}
}
/*
* This is called when a handle times out before receiving a packet.
*/
void
udp_recvpkt_timeout(
void * cookie)
{
struct sec_handle *rh = cookie;
void (*fn)(void *, pkt_t *, security_status_t);
void *arg;
assert(rh != NULL);
assert(rh->ev_timeout != NULL);
fn = rh->fn.recvpkt;
arg = rh->arg;
udp_recvpkt_cancel(rh);
(*fn)(arg, NULL, S_TIMEOUT);
}
/*
* Given a hostname and a port, setup a udp_handle
*/
int
udp_inithandle(
udp_handle_t * udp,
struct sec_handle * rh,
char * hostname,
sockaddr_union *addr,
in_port_t port,
char * handle,
int sequence)
{
/*
* Save the hostname and port info
*/
auth_debug(1, _("udp_inithandle port %u handle %s sequence %d\n"),
(unsigned int)ntohs(port), handle, sequence);
assert(addr != NULL);
rh->hostname = g_strdup(hostname);
copy_sockaddr(&rh->peer, addr);
SU_SET_PORT(&rh->peer, port);
rh->prev = udp->bh_last;
if (udp->bh_last) {
rh->prev->next = rh;
}
if (!udp->bh_first) {
udp->bh_first = rh;
}
rh->next = NULL;
udp->bh_last = rh;
rh->sequence = sequence;
g_mutex_lock(security_mutex);
rh->event_id = newevent++;
g_mutex_unlock(security_mutex);
amfree(rh->proto_handle);
rh->proto_handle = g_strdup(handle);
rh->fn.connect = NULL;
rh->arg = NULL;
rh->ev_read = NULL;
rh->ev_timeout = NULL;
auth_debug(1, _("udp: adding handle '%s'\n"), rh->proto_handle);
return(0);
}
/*
* Callback for received packets. This is the function bsd_recvpkt
* registers with the event handler. It is called when the event handler
* realizes that data is waiting to be read on the network socket.
*/
void
udp_netfd_read_callback(
void * cookie)
{
struct udp_handle *udp = cookie;
struct sec_handle *rh;
int a;
char hostname[NI_MAXHOST];
in_port_t port;
char *errmsg = NULL;
int result;
auth_debug(1, _("udp_netfd_read_callback(cookie=%p)\n"), cookie);
assert(udp != NULL);
#ifndef TEST /* { */
/*
* Receive the packet.
*/
dgram_zero(&udp->dgram);
if (dgram_recv(&udp->dgram, 0, &udp->peer) < 0)
return;
#endif /* !TEST */ /* } */
/*
* Parse the packet.
*/
if (str2pkthdr(udp) < 0)
return;
/*
* If there are events waiting on this handle, we're done
*/
rh = udp->bh_first;
while(rh != NULL && (!g_str_equal(rh->proto_handle, udp->handle) ||
rh->sequence != udp->sequence ||
cmp_sockaddr(&rh->peer, &udp->peer, 0) != 0)) {
rh = rh->next;
}
if (rh && event_wakeup(rh->event_id) > 0)
return;
/*
* If we didn't find a handle, then check for a new incoming packet.
* If no accept handler was setup, then just return.
*/
if (udp->accept_fn == NULL) {
g_debug(_("Receive packet from unknown source"));
dump_sockaddr(&udp->peer);
//dump_sockaddr(&rh->peer);
return;
}
rh = g_new0(struct sec_handle, 1);
rh->proto_handle=NULL;
rh->udp = udp;
rh->rc = NULL;
security_handleinit(&rh->sech, udp->driver);
result = getnameinfo((struct sockaddr *)&udp->peer, SS_LEN(&udp->peer),
hostname, sizeof(hostname), NULL, 0, 0);
if (result != 0) {
dbprintf("getnameinfo failed: %s\n",
gai_strerror(result));
security_seterror(&rh->sech, "getnameinfo failed: %s",
gai_strerror(result));
return;
}
if (check_name_give_sockaddr(hostname,
(struct sockaddr *)&udp->peer, &errmsg) < 0) {
security_seterror(&rh->sech, "%s",errmsg);
amfree(errmsg);
amfree(rh);
return;
}
port = SU_GET_PORT(&udp->peer);
a = udp_inithandle(udp, rh,
hostname,
&udp->peer,
port,
udp->handle,
udp->sequence);
if (a < 0) {
auth_debug(1, _("bsd: closeX handle '%s'\n"), rh->proto_handle);
amfree(rh);
return;
}
/*
* Check the security of the packet. If it is bad, then pass NULL
* to the accept function instead of a packet.
*/
if (rh->udp->recv_security_ok(rh, &udp->pkt, rh->udp->need_priv_port) < 0)
(*udp->accept_fn)(&rh->sech, NULL);
else
(*udp->accept_fn)(&rh->sech, &udp->pkt);
}
/*
* 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.
*/
struct tcp_conn *
sec_tcp_conn_get(
const char *dle_hostname,
const char *hostname,
int want_new)
{
GSList *iter;
struct tcp_conn *rc = NULL;
auth_debug(1, _("sec_tcp_conn_get: %s %s\n"), dle_hostname, hostname);
g_mutex_lock(security_mutex);
if (want_new == 0) {
for (iter = connq; iter != NULL; iter = iter->next) {
rc = (struct tcp_conn *)iter->data;
if (!rc->errmsg &&
strcasecmp(hostname, rc->hostname) == 0 &&
((!dle_hostname && !rc->dle_hostname) ||
(dle_hostname && rc->dle_hostname && strcasecmp(dle_hostname, rc->dle_hostname) == 0))) {
break;
}
}
if (iter != NULL) {
rc->refcnt++;
auth_debug(1,
_("sec_tcp_conn_get: exists, refcnt to %s is now %d\n"),
rc->hostname, rc->refcnt);
g_mutex_unlock(security_mutex);
return (rc);
}
}
g_mutex_unlock(security_mutex);
auth_debug(1, _("sec_tcp_conn_get: creating new handle\n"));
/*
* We can't be creating a new handle if we are the client
*/
rc = g_new0(struct tcp_conn, 1);
rc->read = rc->write = -1;
rc->driver = NULL;
rc->pid = -1;
rc->ev_read = NULL;
rc->toclose = 0;
rc->donotclose = 0;
strncpy(rc->hostname, hostname, sizeof(rc->hostname) - 1);
rc->hostname[sizeof(rc->hostname) - 1] = '\0';
if (dle_hostname) {
rc->dle_hostname = g_strdup(dle_hostname);
}
rc->errmsg = NULL;
rc->refcnt = 1;
rc->handle = -1;
rc->pkt = NULL;
rc->accept_fn = NULL;
rc->recv_security_ok = NULL;
rc->prefix_packet = NULL;
rc->auth = 0;
rc->conf_fn = NULL;
rc->datap = NULL;
g_mutex_lock(security_mutex);
rc->event_id = newevent++;
connq = g_slist_append(connq, rc);
g_mutex_unlock(security_mutex);
return (rc);
}
/*
* Delete a reference to a connection, and close it if it is the last
* reference.
*/
static void
sec_tcp_conn_put(
struct tcp_conn *rc)
{
amwait_t status;
assert(rc->refcnt > 0);
--rc->refcnt;
auth_debug(1, _("sec_tcp_conn_put: decrementing refcnt for %s to %d\n"),
rc->hostname, rc->refcnt);
if (rc->refcnt > 0) {
return;
}
auth_debug(1, _("sec_tcp_conn_put: closing connection to %s\n"), rc->hostname);
if (rc->read != -1)
aclose(rc->read);
if (rc->write != -1)
aclose(rc->write);
if (rc->pid != -1 && !rc->child_watch) {
int pid;
int count = 50;
pid = waitpid(rc->pid, &status, WNOHANG);
while (pid == 0 && count > 0) {
struct timespec tdelay;
tdelay.tv_sec = 0;
tdelay.tv_nsec = 50000000;
nanosleep(&tdelay, NULL);
pid = waitpid(rc->pid, &status, WNOHANG);
count--;
}
if (pid == 0) {
g_debug("sending SIGTERM to pid: %ld", (long)rc->pid);
if (kill(rc->pid, SIGTERM) == -1 && errno != ESRCH) {
g_debug("Can't kill pid %ld: %s", (long)rc->pid, strerror(errno));
}
pid = waitpid(rc->pid, &status, WNOHANG);
count = 50;
while (pid == 0 && count > 0) {
struct timespec tdelay;
tdelay.tv_sec = 0;
tdelay.tv_nsec = 50000000;
nanosleep(&tdelay, NULL);
pid = waitpid(rc->pid, &status, WNOHANG);
count--;
}
}
rc->pid = -1;
}
if (rc->ev_read != NULL)
event_release(rc->ev_read);
if (rc->errmsg != NULL)
amfree(rc->errmsg);
g_mutex_lock(security_mutex);
connq = g_slist_remove(connq, rc);
g_mutex_unlock(security_mutex);
amfree(rc->pkt);
if(!rc->donotclose) {
/* amfree(rc) */
/* a memory leak occurs, but freeing it lead to memory
* corruption because it can still be used.
* We need to find a good place to free 'rc'.
*/
}
}
/*
* Turn on read events for a conn. Or, increase a ev_read_refcnt if we are
* already receiving read events.
*/
void
sec_tcp_conn_read(
struct tcp_conn * rc)
{
assert (rc != NULL);
if (rc->ev_read != NULL) {
rc->ev_read_refcnt++;
auth_debug(1,
_("sec: conn_read: incremented ev_read_refcnt to %d for %s\n"),
rc->ev_read_refcnt, rc->hostname);
return;
}
auth_debug(1, _("sec: conn_read registering event handler for %s\n"),
rc->hostname);
rc->ev_read = event_create((event_id_t)rc->read, EV_READFD,
sec_tcp_conn_read_callback, rc);
event_activate(rc->ev_read);
rc->ev_read_refcnt = 1;
}
static void
sec_tcp_conn_read_cancel(
struct tcp_conn * rc)
{
--rc->ev_read_refcnt;
auth_debug(1,
_("sec: conn_read_cancel: decremented ev_read_refcnt to %d for %s\n"),
rc->ev_read_refcnt, rc->hostname);
if (rc->ev_read_refcnt > 0) {
return;
}
auth_debug(1,
_("sec: conn_read_cancel: releasing event handler for %s\n"),
rc->hostname);
event_release(rc->ev_read);
rc->ev_read = NULL;
}
/*
* This is called when a handle is woken up because data read off of the
* net is for it.
*/
static void
recvpkt_callback(
void * cookie,
void * buf,
ssize_t bufsize)
{
pkt_t pkt;
struct sec_handle *rh = cookie;
assert(rh != NULL);
auth_debug(1, _("sec: recvpkt_callback: %zd\n"), bufsize);
/*
* We need to cancel the recvpkt request before calling
* the callback because the callback may reschedule us.
*/
stream_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, "%s", security_stream_geterror(&rh->rs->secstr));
(*rh->fn.recvpkt)(rh->arg, NULL, S_ERROR);
return;
default:
break;
}
parse_pkt(&pkt, buf, (size_t)bufsize);
auth_debug(1,
_("sec: received %s packet (%d) from %s, contains:\n\n\"%s\"\n\n"),
pkt_type2str(pkt.type), pkt.type,
rh->hostname, pkt.body);
if (rh->rc->recv_security_ok && (rh->rc->recv_security_ok)(rh, &pkt, rh->rc->need_priv_port) < 0)
(*rh->fn.recvpkt)(rh->arg, NULL, S_ERROR);
else
(*rh->fn.recvpkt)(rh->arg, &pkt, S_OK);
amfree(pkt.body);
}
/*
* Callback for tcpm_stream_read_sync
*/
static void
stream_read_sync_callback(
void * s)
{
struct sec_stream *rs = s;
assert(rs != NULL);
auth_debug(6, _("sec: stream_read_sync_callback: handle %d\n"), 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) {
auth_debug(6, _("stream_read_sync_callback: stream_read_sync_callback: it was for us\n"));
rs->rc->handle = H_TAKEN;
} else if (rs->rc->handle != H_EOF) {
auth_debug(6, _("stream_read_sync_callback: stream_read_sync_callback: not for us\n"));
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.
*/
tcpm_stream_read_cancel(rs);
sync_pktlen = rs->rc->pktlen;
sync_pkt = malloc(sync_pktlen);
memcpy(sync_pkt, rs->rc->pkt, sync_pktlen);
if (rs->rc->pktlen <= 0) {
auth_debug(6, _("sec: stream_read_sync_callback: %s\n"), rs->rc->errmsg);
security_stream_seterror(&rs->secstr, "%s", rs->rc->errmsg);
if(rs->closed_by_me == 1 && rs->closed_by_network == 0) {
sec_tcp_conn_put(rs->rc);
}
rs->closed_by_network = 1;
return;
}
auth_debug(6,
_("sec: stream_read_sync_callback: read %zd bytes from %s:%d\n"),
rs->rc->pktlen, rs->rc->hostname, rs->handle);
event_wakeup(rs->event_id);
}
/*
* Callback for tcpm_stream_read
*/
static void
stream_read_callback(
void * arg)
{
struct sec_stream *rs = arg;
time_t logtime;
assert(rs != NULL);
assert(rs->rc != NULL);
logtime = time(NULL);
if (logtime > rs->rc->logstamp + 10) {
g_debug("stream_read_callback: data is still flowing");
rs->rc->logstamp = logtime;
}
auth_debug(6, _("sec: stream_read_callback: handle %d\n"), 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) {
auth_debug(6, _("sec: stream_read_callback: it was for us\n"));
rs->rc->handle = H_TAKEN;
} else if (rs->rc->handle != H_EOF) {
auth_debug(6, _("sec: stream_read_callback: not for us\n"));
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.
*/
if (rs->rc->pktlen <= 0) {
auth_debug(5, _("Xsec: stream_read_callback: %s\n"), rs->rc->errmsg);
tcpm_stream_read_cancel(rs);
security_stream_seterror(&rs->secstr, "%s", rs->rc->errmsg);
if(rs->closed_by_me == 1 && rs->closed_by_network == 0) {
sec_tcp_conn_put(rs->rc);
}
rs->closed_by_network = 1;
(*rs->fn)(rs->arg, NULL, rs->rc->pktlen);
return;
}
auth_debug(6, _("sec: stream_read_callback: read %zd bytes from %s:%d\n"),
rs->rc->pktlen, rs->rc->hostname, rs->handle);
(*rs->fn)(rs->arg, rs->rc->pkt, rs->rc->pktlen);
auth_debug(6, _("sec: after callback stream_read_callback\n"));
}
/*
* Callback for tcpm_stream_read_to_shm_ring
*/
static void
stream_read_to_shm_ring_callback(
void * arg)
{
struct sec_stream *rs = arg;
time_t logtime;
assert(rs != NULL);
assert(rs->rc != NULL);
logtime = time(NULL);
if (logtime > rs->rc->logstamp + 10) {
g_debug("stream_read_to_shm_ring_callback: data is still flowing");
rs->rc->logstamp = logtime;
}
auth_debug(6, _("sec: stream_read_to_shm_ring_callback: handle %d\n"), 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) {
auth_debug(6, _("sec: stream_read_to_shm_ring_callback: it was for us\n"));
rs->rc->handle = H_TAKEN;
} else if (rs->rc->handle != H_EOF) {
auth_debug(6, _("sec: stream_read_to_shm_ring_callback: not for us\n"));
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.
*/
if (rs->rc->pktlen <= 0) {
auth_debug(5, _("sec: stream_read_to_shm_ring_callback: %s\n"), rs->rc->errmsg);
tcpm_stream_read_cancel(rs);
security_stream_seterror(&rs->secstr, "%s", rs->rc->errmsg);
if(rs->closed_by_me == 1 && rs->closed_by_network == 0) {
sec_tcp_conn_put(rs->rc);
}
rs->closed_by_network = 1;
(*rs->fn)(rs->arg, NULL, rs->rc->pktlen);
return;
}
auth_debug(6, _("sec: stream_read_to_shm_ring_callback: read %zd bytes from %s:%d\n"),
rs->rc->pktlen, rs->rc->hostname, rs->handle);
(*rs->fn)(rs->arg, rs->rc->pkt, rs->rc->pktlen);
auth_debug(6, _("sec: after callback stream_read_to_shm_ring_callback\n"));
}
/*
* 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
sec_tcp_conn_read_callback(
void * cookie)
{
struct tcp_conn * rc = cookie;
struct sec_handle * rh;
pkt_t pkt;
ssize_t rval;
GSList *reader_callbacks;
assert(cookie != NULL);
auth_debug(6, _("sec: conn_read_callback %d %d\n"), (int)rc->event_id, rc->read);
/* Read the data off the wire. If we get errors, shut down. */
rval = tcpm_recv_token(rc, &rc->handle, &rc->errmsg, &rc->pkt,
&rc->pktlen);
auth_debug(6, _("sec: conn_read_callback: tcpm_recv_token returned %zd\n"),
rval);
if (rval == -2) {
return;
}
if (rval < 0 || rc->handle == H_EOF) {
rc->pktlen = rval;
rc->handle = H_EOF;
for (reader_callbacks = rc->reader_callbacks; reader_callbacks != NULL;
reader_callbacks = reader_callbacks->next) {
reader_callback *r_callback = (reader_callback *)reader_callbacks->data;
r_callback->callback(r_callback->s);
}
/* Must close all events */
/* delete our 'accept' reference */
if (rc->accept_fn != NULL) {
(*rc->accept_fn)(NULL, NULL);
if(rc->refcnt != 1) {
dbprintf(_("STRANGE, rc->refcnt should be 1, it is %d\n"),
rc->refcnt);
}
rc->accept_fn = NULL;
while (rc->refcnt > 0) {
sec_tcp_conn_put(rc);
}
}
return;
}
if(rval == 0) {
rc->pktlen = 0;
for (reader_callbacks = rc->reader_callbacks; reader_callbacks != NULL;
reader_callbacks = reader_callbacks->next) {
reader_callback *r_callback = (reader_callback *)reader_callbacks->data;
if (r_callback->s->handle == rc->handle) {
r_callback->callback(r_callback->s);
break;
}
}
if (rc->handle != H_TAKEN) {
g_debug("ignoring close stream %d", rc->handle);
}
return;
}
/* If there are events waiting on this handle, we're done */
rc->donotclose = 1;
for (reader_callbacks = rc->reader_callbacks; reader_callbacks != NULL;
reader_callbacks = reader_callbacks->next) {
reader_callback *r_callback = (reader_callback *)reader_callbacks->data;
if (r_callback->s->handle == rc->handle) {
r_callback->callback(r_callback->s);
break;
}
}
rc->donotclose = 0;
if (rc->handle == H_TAKEN) {
if(rc->refcnt == 0) {
// do not free, it stillcan be used
//amfree(rc);
}
return;
}
if (rc->pktlen == 0) {
if(rc->refcnt == 0) {
// do not free, it stillcan be used
//amfree(rc);
}
return;
}
assert(rc->refcnt > 0);
/* If there is no accept fn registered, then drop the packet */
if (rc->accept_fn == NULL) {
g_warning(
_("sec: conn_read_callback: %zd bytes for handle %d went unclaimed!"),
rc->pktlen, rc->handle);
if (rc->pktlen<100) {
char *badbuf = g_malloc(rc->pktlen+1);
memcpy(badbuf, rc->pkt, rc->pktlen);
badbuf[rc->pktlen] = '\0';
g_debug("packet: %s", badbuf);
g_free(badbuf);
}
return;
}
rh = g_new0(struct sec_handle, 1);
security_handleinit(&rh->sech, rc->driver);
rh->hostname = g_strdup(rc->hostname);
rh->ev_timeout = NULL;
rh->rc = rc;
rh->peer = rc->peer;
rh->rs = tcpma_stream_client(rh, rc->handle);
auth_debug(1, _("sec: new connection %d\n"),rc->handle);
pkt.body = NULL;
parse_pkt(&pkt, rc->pkt, (size_t)rc->pktlen);
auth_debug(1, _("sec: calling accept_fn\n"));
if (rh->rc->recv_security_ok && (rh->rc->recv_security_ok)(rh, &pkt, rh->rc->need_priv_port) < 0)
(*rc->accept_fn)(&rh->sech, NULL);
else
(*rc->accept_fn)(&rh->sech, &pkt);
amfree(pkt.body);
}
void
parse_pkt(
pkt_t * pkt,
const void *buf,
size_t bufsize)
{
const unsigned char *bufp = buf;
auth_debug(1, _("sec: parse_pkt: parsing buffer of %zu bytes\n"), bufsize);
pkt->type = (pktype_t)*bufp++;
bufsize--;
pkt->packet_size = bufsize+1;
pkt->body = g_malloc(pkt->packet_size);
if (bufsize == 0) {
pkt->body[0] = '\0';
} else {
memcpy(pkt->body, bufp, bufsize);
pkt->body[pkt->packet_size - 1] = '\0';
}
pkt->size = strlen(pkt->body);
auth_debug(1, _("sec: parse_pkt: %s (%d): \"%s\"\n"), pkt_type2str(pkt->type),
pkt->type, pkt->body);
}
/*
* Convert a packet header into a string
*/
const char *
pkthdr2str(
const struct sec_handle * rh,
const pkt_t * pkt)
{
static char retbuf[256];
assert(rh != NULL);
assert(pkt != NULL);
g_snprintf(retbuf, sizeof(retbuf), _("Amanda %d.%d %s HANDLE %s SEQ %d\n"),
VERSION_MAJOR, VERSION_MINOR, pkt_type2str(pkt->type),
rh->proto_handle, rh->sequence);
auth_debug(1, _("bsd: pkthdr2str handle '%s'\n"), rh->proto_handle);
/* check for truncation. If only we had asprintf()... */
assert(retbuf[strlen(retbuf) - 1] == '\n');
return (retbuf);
}
/*
* Parses out the header line in 'str' into the pkt and handle
* Returns negative on parse error.
*/
int
str2pkthdr(
udp_handle_t * udp)
{
char *str;
const char *tok;
pkt_t *pkt;
pkt = &udp->pkt;
assert(udp->dgram.cur != NULL);
str = g_strdup(udp->dgram.cur);
/* "Amanda %d.%d <ACK,NAK,...> HANDLE %s SEQ %d\n" */
/* Read in "Amanda" */
if ((tok = strtok(str, " ")) == NULL || !g_str_equal(tok, "Amanda"))
goto parse_error;
/* nothing is done with the major/minor numbers currently */
if ((tok = strtok(NULL, " ")) == NULL || strchr(tok, '.') == NULL)
goto parse_error;
/* Read in the packet type */
if ((tok = strtok(NULL, " ")) == NULL)
goto parse_error;
amfree(pkt->body);
pkt_init_empty(pkt, pkt_str2type(tok));
if (pkt->type == (pktype_t)-1)
goto parse_error;
/* Read in "HANDLE" */
if ((tok = strtok(NULL, " ")) == NULL || !g_str_equal(tok, "HANDLE"))
goto parse_error;
/* parse the handle */
if ((tok = strtok(NULL, " ")) == NULL)
goto parse_error;
amfree(udp->handle);
udp->handle = g_strdup(tok);
/* Read in "SEQ" */
if ((tok = strtok(NULL, " ")) == NULL || !g_str_equal(tok, "SEQ"))
goto parse_error;
/* parse the sequence number */
if ((tok = strtok(NULL, "\n")) == NULL)
goto parse_error;
udp->sequence = atoi(tok);
/* Save the body, if any */
if ((tok = strtok(NULL, "")) != NULL)
pkt_cat(pkt, "%s", tok);
amfree(str);
return (0);
parse_error:
#if 0 /* XXX we have no way of passing this back up */
security_seterror(&rh->sech,
"parse error in packet header : '%s'", origstr);
#endif
amfree(str);
return (-1);
}
char *
check_user(
struct sec_handle * rh,
const char * remoteuser,
const char * service)
{
struct passwd *pwd;
char *r;
char *result = NULL;
char *localuser = NULL;
/* lookup our local user name */
if ((pwd = getpwnam(CLIENT_LOGIN)) == NULL) {
return g_strdup_printf(_("getpwnam(%s) failed."), CLIENT_LOGIN);
}
/*
* Make a copy of the user name in case getpw* is called by
* any of the lower level routines.
*/
localuser = g_strdup(pwd->pw_name);
#ifndef USE_AMANDAHOSTS
r = check_user_ruserok(rh->hostname, pwd, remoteuser);
#else
r = check_user_amandahosts(rh->hostname, &rh->peer, pwd, remoteuser, service);
#endif
if (r != NULL) {
result = g_strdup_printf(
_("user %s from %s is not allowed to execute the service %s: %s"),
remoteuser, rh->hostname, service, r);
amfree(r);
}
amfree(localuser);
return result;
}
/*
* See if a remote user is allowed in. This version uses ruserok()
* and friends.
*
* Returns NULL on success, or error message on error.
*/
char *
check_user_ruserok(
const char * host,
struct passwd * pwd,
const char * remoteuser)
{
int saved_stderr;
int fd[2];
FILE *fError;
amwait_t exitcode;
pid_t ruserok_pid;
pid_t pid;
char *es;
char *result;
int ok;
uid_t myuid = getuid();
int devnull;
/*
* note that some versions of ruserok (eg SunOS 3.2) look in
* "./.rhosts" rather than "~CLIENT_LOGIN/.rhosts", so we have to
* chdir ourselves. Sigh.
*
* And, believe it or not, some ruserok()'s try an initgroup just
* for the hell of it. Since we probably aren't root at this point
* it'll fail, and initgroup "helpfully" will blatt "Setgroups: Not owner"
* into our stderr output even though the initgroup failure is not a
* problem and is expected. Thanks a lot. Not.
*/
if (pipe(fd) != 0) {
return g_strconcat(_("pipe() fails: "), strerror(errno), NULL);
}
if ((ruserok_pid = fork()) < 0) {
return g_strconcat(_("fork() fails: "), strerror(errno), NULL);
} else if (ruserok_pid == 0) {
int ec;
close(fd[0]);
fError = fdopen(fd[1], "w");
if (!fError) {
error(_("Can't fdopen: %s"), strerror(errno));
/*NOTREACHED*/
}
/* pamper braindead ruserok's */
if (chdir(pwd->pw_dir) != 0) {
g_fprintf(fError, _("chdir(%s) failed: %s"),
pwd->pw_dir, strerror(errno));
fclose(fError);
exit(1);
}
if (debug_auth >= 9) {
char *dir = g_strdup(pwd->pw_dir);
auth_debug(9, _("bsd: calling ruserok(%s, %d, %s, %s)\n"), host,
((myuid == 0) ? 1 : 0), remoteuser, pwd->pw_name);
if (myuid == 0) {
auth_debug(9, _("bsd: because you are running as root, "));
auth_debug(9, _("/etc/hosts.equiv will not be used\n"));
} else {
show_stat_info("/etc/hosts.equiv", NULL);
}
show_stat_info(dir, "/.rhosts");
amfree(dir);
}
saved_stderr = dup(2);
if (saved_stderr < 0) {
g_debug("Can't dup 2: %s", strerror(errno));
ec = 1;
exit(ec);
}
close(2);
if ((devnull = open("/dev/null", O_RDWR)) == -1) {
g_debug(_("Could not open /dev/null: %s"), strerror(errno));
ec = 1;
} else {
int devnull2 = -1;
if (devnull != 2) {
devnull2 = dup2(devnull, 2);
}
ok = ruserok(host, myuid == 0, remoteuser, CLIENT_LOGIN);
if (ok < 0) {
ec = 1;
} else {
ec = 0;
}
close(devnull);
if (devnull2 != -1)
close(devnull2);
}
(void)dup2(saved_stderr,2);
close(saved_stderr);
exit(ec);
}
close(fd[1]);
fError = fdopen(fd[0], "r");
if (!fError) {
error(_("Can't fdopen: %s"), strerror(errno));
/*NOTREACHED*/
}
result = NULL;
while ((es = pgets(fError)) != NULL) {
if (*es == 0) {
amfree(es);
continue;
}
if (result == NULL) {
result = g_strdup("");
} else {
strappend(result, ": ");
}
strappend(result, es);
amfree(es);
}
fclose(fError);
pid = wait(&exitcode);
while (pid != ruserok_pid) {
if ((pid == (pid_t) -1) && (errno != EINTR)) {
amfree(result);
return g_strdup_printf(_("ruserok wait failed: %s"), strerror(errno));
}
pid = wait(&exitcode);
}
if (!WIFEXITED(exitcode) || WEXITSTATUS(exitcode) != 0) {
amfree(result);
result = str_exit_status("ruserok child", exitcode);
} else {
amfree(result);
}
return result;
}
/*
* Check to see if a user is allowed in. This version uses .amandahosts
* Returns an error message on failure, or NULL on success.
*/
char *
check_user_amandahosts(
const char * host,
sockaddr_union *addr,
struct passwd * pwd,
const char * remoteuser,
const char * service)
{
char *line = NULL;
char *filehost;
const char *fileuser;
char *ptmp = NULL;
char *result = NULL;
FILE *fp = NULL;
int found;
struct stat sbuf;
int hostmatch;
int usermatch;
char *aservice = NULL;
#ifdef WORKING_IPV6
char ipstr[INET6_ADDRSTRLEN];
#else
char ipstr[INET_ADDRSTRLEN];
#endif
auth_debug(1, _("check_user_amandahosts(host=%s, pwd=%p, "
"remoteuser=%s, service=%s)\n"),
host, pwd, remoteuser, service);
ptmp = g_strconcat(pwd->pw_dir, "/.amandahosts", NULL);
if (debug_auth >= 9) {
show_stat_info(ptmp, "");;
}
if ((fp = fopen(ptmp, "r")) == NULL) {
result = g_strdup_printf(_("cannot open %s: %s"), ptmp, strerror(errno));
amfree(ptmp);
return result;
}
/*
* Make sure the file is owned by the Amanda user and does not
* have any group/other access allowed.
*/
if (fstat(fileno(fp), &sbuf) != 0) {
result = g_strdup_printf(_("cannot fstat %s: %s"), ptmp, strerror(errno));
goto common_exit;
}
if (sbuf.st_uid != pwd->pw_uid) {
result = g_strdup_printf(_("%s: owned by id %ld, should be %ld"),
ptmp, (long)sbuf.st_uid, (long)pwd->pw_uid);
goto common_exit;
}
if ((sbuf.st_mode & 077) != 0) {
result = g_strdup_printf(_("%s: incorrect permissions; file must be accessible only by its owner (chmod 600 %s)"), ptmp, ptmp);
goto common_exit;
}
/*
* Now, scan the file for the host/user/service.
*/
found = 0;
while ((line = pgets(fp)) != NULL) {
if (*line == 0) {
amfree(line);
continue;
}
auth_debug(9, _("bsd: processing line: <%s>\n"), line);
/* get the host out of the file */
if ((filehost = strtok(line, " \t")) == NULL) {
amfree(line);
continue;
}
/* get the username. If no user specified, then use the local user */
if ((fileuser = strtok(NULL, " \t")) == NULL) {
fileuser = pwd->pw_name;
}
hostmatch = (strcasecmp(filehost, host) == 0);
/* ok if addr=127.0.0.1 and
* either localhost or localhost.domain is in .amandahost */
if (!hostmatch &&
(strcasecmp(filehost, "localhost")== 0 ||
strcasecmp(filehost, "localhost.localdomain")== 0)) {
#ifdef WORKING_IPV6
if (SU_GET_FAMILY(addr) == (sa_family_t)AF_INET6)
inet_ntop(AF_INET6, &addr->sin6.sin6_addr,
ipstr, sizeof(ipstr));
else
#endif
inet_ntop(AF_INET, &addr->sin.sin_addr,
ipstr, sizeof(ipstr));
if (g_str_equal(ipstr, "127.0.0.1") ||
g_str_equal(ipstr, "::1"))
hostmatch = 1;
}
usermatch = (strcasecmp(fileuser, remoteuser) == 0);
auth_debug(9, _("bsd: comparing \"%s\" with\n"), filehost);
auth_debug(9, _("bsd: \"%s\" (%s)\n"), host,
hostmatch ? _("match") : _("no match"));
auth_debug(9, _("bsd: and \"%s\" with\n"), fileuser);
auth_debug(9, _("bsd: \"%s\" (%s)\n"), remoteuser,
usermatch ? _("match") : _("no match"));
/* compare */
if (!hostmatch || !usermatch) {
amfree(line);
continue;
}
if (!service) {
/* success */
amfree(line);
found = 1;
break;
}
/* get the services. If no service specified, then use
* noop/selfcheck/sendsize/sendbackup
*/
aservice = strtok(NULL, " \t,");
if (!aservice) {
if (g_str_equal(service, "noop") ||
g_str_equal(service, "selfcheck") ||
g_str_equal(service, "sendsize") ||
g_str_equal(service, "sendbackup")) {
/* success */
found = 1;
amfree(line);
break;
}
else {
amfree(line);
continue;
}
}
do {
if (g_str_equal(aservice, service)) {
found = 1;
break;
}
if (g_str_equal(aservice, "amdump") &&
(g_str_equal(service, "noop") ||
g_str_equal(service, "selfcheck") ||
g_str_equal(service, "sendsize") ||
g_str_equal(service, "sendbackup"))) {
found = 1;
break;
}
} while((aservice = strtok(NULL, " \t,")) != NULL);
if (aservice && g_str_equal(aservice, service)) {
/* success */
found = 1;
amfree(line);
break;
}
amfree(line);
}
if (! found) {
if (g_str_equal(service, "amindexd") ||
g_str_equal(service, "amidxtaped")) {
result = g_strdup_printf(_("Please add the line \"%s %s amindexd amidxtaped\" to %s on the server"), host, remoteuser, ptmp);
} else if (g_str_equal(service, "amdump") ||
g_str_equal(service, "noop") ||
g_str_equal(service, "selfcheck") ||
g_str_equal(service, "sendsize") ||
g_str_equal(service, "sendbackup")) {
result = g_strdup_printf(_("Please add the line \"%s %s amdump\" to %s on the client"), host, remoteuser, ptmp);
} else {
result = g_strdup_printf(_("%s: invalid service %s"), ptmp, service);
}
}
common_exit:
afclose(fp);
amfree(ptmp);
return result;
}
/* return 1 on success, 0 on failure */
int
check_security(
sockaddr_union *addr,
char * str,
unsigned long cksum,
char ** errstr,
char *service G_GNUC_UNUSED)
{
char * remotehost = NULL, *remoteuser = NULL;
char * bad_bsd = NULL;
struct passwd * pwptr;
uid_t myuid;
char * s;
char * fp;
int ch;
char hostname[NI_MAXHOST];
in_port_t port;
int result;
(void)cksum; /* Quiet unused parameter warning */
auth_debug(1,
_("check_security(addr=%p, str='%s', cksum=%lu, errstr=%p\n"),
addr, str, cksum, errstr);
dump_sockaddr(addr);
*errstr = NULL;
/* what host is making the request? */
if ((result = getnameinfo((struct sockaddr *)addr, SS_LEN(addr),
hostname, NI_MAXHOST, NULL, 0, 0)) != 0) {
dbprintf(_("getnameinfo failed: %s\n"),
gai_strerror(result));
*errstr = g_strjoin(NULL, "[", "addr ", str_sockaddr(addr), ": ",
"getnameinfo failed: ", gai_strerror(result),
"]", NULL);
return 0;
}
remotehost = g_strdup(hostname);
if( check_name_give_sockaddr(hostname,
(struct sockaddr *)addr, errstr) < 0) {
amfree(remotehost);
return 0;
}
/* next, make sure the remote port is a "reserved" one */
port = SU_GET_PORT(addr);
if (port >= IPPORT_RESERVED) {
*errstr = g_strdup_printf(_("[host %s: port %u not secure]"),
remotehost, (unsigned int)port);
amfree(remotehost);
return 0;
}
/* extract the remote user name from the message */
s = str;
ch = *s++;
bad_bsd = g_strdup_printf(_("[host %s: bad bsd security line]"), remotehost);
if (strncmp_const_skip(s - 1, "USER ", s, ch) != 0) {
*errstr = bad_bsd;
bad_bsd = NULL;
amfree(remotehost);
return 0;
}
skip_whitespace(s, ch);
if (ch == '\0') {
*errstr = bad_bsd;
bad_bsd = NULL;
amfree(remotehost);
return 0;
}
fp = s - 1;
skip_non_whitespace(s, ch);
s[-1] = '\0';
remoteuser = g_strdup(fp);
s[-1] = (char)ch;
amfree(bad_bsd);
/* lookup our local user name */
myuid = getuid();
if ((pwptr = getpwuid(myuid)) == NULL)
error(_("error [getpwuid(%d) fails]"), (int)myuid);
auth_debug(1, _("bsd security: remote host %s user %s local user %s\n"),
remotehost, remoteuser, pwptr->pw_name);
#ifndef USE_AMANDAHOSTS
s = check_user_ruserok(remotehost, pwptr, remoteuser);
#else
s = check_user_amandahosts(remotehost, addr, pwptr, remoteuser, service);
#endif
if (s != NULL) {
*errstr = g_strdup_printf(_("[access as %s not allowed from %s@%s: %s]"),
pwptr->pw_name, remoteuser, remotehost, s);
}
amfree(s);
amfree(remotehost);
amfree(remoteuser);
return *errstr == NULL;
}
/*
* Like read(), but waits until the entire buffer has been filled.
*/
ssize_t
net_read(
int fd,
void * vbuf,
size_t origsize,
int timeout)
{
char *buf = vbuf; /* ptr arith */
ssize_t nread;
size_t size = origsize;
auth_debug(1, _("net_read: begin %zu\n"), origsize);
while (size > 0) {
auth_debug(1, _("net_read: while %p %zu\n"), buf, size);
nread = net_read_fillbuf(fd, timeout, buf, size);
if (nread < 0) {
auth_debug(1, _("db: net_read: end return(-1)\n"));
return (-1);
}
if (nread == 0) {
auth_debug(1, _("net_read: end return(0)\n"));
return (0);
}
buf += nread;
size -= nread;
}
auth_debug(1, _("net_read: end %zu\n"), origsize);
return ((ssize_t)origsize);
}
/*
* net_read likes to do a lot of little reads. Buffer it.
*/
ssize_t
net_read_fillbuf(
int fd,
int timeout,
void * buf,
size_t size)
{
SELECT_ARG_TYPE readfds;
struct timeval tv;
ssize_t nread;
auth_debug(1, _("net_read_fillbuf: begin\n"));
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
tv.tv_sec = timeout;
tv.tv_usec = 0;
switch (select(fd + 1, &readfds, NULL, NULL, &tv)) {
case 0:
auth_debug(1, "net_read_fillbuf: case 0: timeout");
errno = ETIMEDOUT;
/* FALLTHROUGH */
case -1:
auth_debug(1, _("net_read_fillbuf: case -1\n"));
return (-1);
case 1:
auth_debug(1, _("net_read_fillbuf: case 1\n"));
assert(FD_ISSET(fd, &readfds));
break;
default:
auth_debug(1, _("net_read_fillbuf: case default\n"));
assert(0);
break;
}
nread = read(fd, buf, size);
auth_debug(1, _("net_read_fillbuf: end %zd\n"), nread);
if (nread < 0) {
g_debug("err: %p %s", buf, strerror(errno));
return (-1);
}
return (nread);
}
/*
* Display stat() information about a file.
*/
void
show_stat_info(
char * a,
char * b)
{
char *name = g_strjoin(NULL, a, b, NULL);
struct stat sbuf;
struct passwd *pwptr G_GNUC_UNUSED;
struct passwd pw G_GNUC_UNUSED;
char *owner;
struct group *grptr G_GNUC_UNUSED;
struct group gr G_GNUC_UNUSED;
char *group;
int buflen G_GNUC_UNUSED;
char *buf G_GNUC_UNUSED;
if (stat(name, &sbuf) != 0) {
auth_debug(1, _("bsd: cannot stat %s: %s\n"), name, strerror(errno));
amfree(name);
return;
}
#ifdef _SC_GETPW_R_SIZE_MAX
buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
if (buflen == -1)
buflen = 1024;
#else
buflen = 1024;
#endif
buf = malloc(buflen);
#ifdef HAVE_GETPWUID_R
if (getpwuid_r(sbuf.st_uid, &pw, buf, buflen, &pwptr) == 0 &&
pwptr != NULL) {
owner = g_strdup(pwptr->pw_name);
} else
#endif
{
owner = g_malloc(NUM_STR_SIZE + 1);
g_snprintf(owner, NUM_STR_SIZE, "%ld", (long)sbuf.st_uid);
}
#ifdef HAVE_GETGRGID_R
if (getgrgid_r(sbuf.st_gid, &gr, buf, buflen, &grptr) == 0 &&
grptr != NULL) {
group = g_strdup(grptr->gr_name);
} else
#endif
{
group = g_malloc(NUM_STR_SIZE + 1);
g_snprintf(group, NUM_STR_SIZE, "%ld", (long)sbuf.st_gid);
}
auth_debug(1, _("bsd: processing file: %s\n"), name);
auth_debug(1, _("bsd: owner=%s group=%s mode=%03o\n"),
owner, group,
(int) (sbuf.st_mode & 0777));
amfree(name);
amfree(owner);
amfree(group);
amfree(buf);
}
int
check_name_give_sockaddr(
const char *hostname,
struct sockaddr *addr,
char **errstr)
{
int result;
struct addrinfo *res = NULL, *res1;
char *canonname;
result = resolve_hostname(hostname, 0, &res, &canonname);
if (result != 0) {
dbprintf(_("check_name_give_sockaddr: resolve_hostname('%s'): %s\n"), hostname, gai_strerror(result));
g_free(*errstr);
*errstr = g_strdup_printf(_("check_name_give_sockaddr: resolve_hostname('%s'): %s"),
hostname, gai_strerror(result));
goto error;
}
if (canonname == NULL) {
dbprintf(_("resolve_hostname('%s') did not return a canonical name\n"), hostname);
g_free(*errstr);
*errstr = g_strdup_printf(_("check_name_give_sockaddr: resolve_hostname('%s') did not return a canonical name"),
hostname);
goto error;
}
if (strncasecmp(hostname, canonname, strlen(hostname)) != 0) {
dbprintf(_("%s doesn't resolve to itself, it resolves to %s\n"),
hostname, canonname);
g_free(*errstr);
*errstr = g_strdup_printf(_("%s doesn't resolve to itself, it resolves to %s"),
hostname, canonname);
goto error;
}
for(res1=res; res1 != NULL; res1 = res1->ai_next) {
if (cmp_sockaddr((sockaddr_union *)res1->ai_addr, (sockaddr_union *)addr, 1) == 0) {
freeaddrinfo(res);
amfree(canonname);
return 0;
}
}
g_debug("%s doesn't resolve to %s",
hostname, str_sockaddr((sockaddr_union *)addr));
g_free(*errstr);
*errstr = g_strdup_printf("%s doesn't resolve to %s",
hostname, str_sockaddr((sockaddr_union *)addr));
error:
if (res) freeaddrinfo(res);
amfree(canonname);
return -1;
}
in_port_t
find_port_for_service(
char *service,
char *proto)
{
in_port_t port;
char *s;
int all_numeric = 1;
for (s=service; *s != '\0'; s++) {
if (!isdigit((int)*s)) {
all_numeric = 0;
}
}
if (all_numeric == 1) {
port = atoi(service);
} else {
struct servent *result;
#ifdef HAVE_FUNC_GETSERVBYNAME_R_6
struct servent sp;
char buf[2048];
int r;
r = getservbyname_r(service, proto, &sp, buf, 2048, &result);
assert(r != ERANGE);
if (r != 0) {
port = 0;
} else {
port = (in_port_t)(ntohs((in_port_t)sp.s_port));
}
#elif defined HAVE_FUNC_GETSERVBYNAME_R_5
struct servent sp;
char buf[2048];
result = getservbyname_r(service, proto, &sp, buf, 2048);
if (result == 0) {
assert(errno != ERANGE);
port = 0;
} else {
port = (in_port_t)(ntohs((in_port_t)sp.s_port));
}
#elif defined HAVE_FUNC_GETSERVBYNAME_R_4
struct servent sp;
struct servent_data servent_data;
int r;
memset(&servent_data, 0, sizeof(struct servent_data));
r = getservbyname_r(service, proto, &sp, &servent_data);
result = &sp;
if (r != 0) {
port = 0;
} else {
port = (in_port_t)(ntohs((in_port_t)result->s_port));
}
#else
if ((result = getservbyname(service, proto)) == NULL) {
port = 0;
} else {
port = (in_port_t)(ntohs((in_port_t)result->s_port));
}
#endif
}
return port;
}
char *
sec_get_authenticated_peer_name_hostname(
security_handle_t *hdl)
{
char *hostname = ((struct sec_handle *)hdl)->hostname;
if (!hostname)
hostname = "";
return g_strdup(hostname);
}
ssize_t
generic_data_write(
void *cookie,
struct iovec *iov,
int iovcnt)
{
struct tcp_conn *rc = cookie;
return full_writev(rc->write, iov, iovcnt);
}
ssize_t
generic_data_write_non_blocking(
void *cookie,
struct iovec *iov,
int iovcnt)
{
struct tcp_conn *rc = cookie;
ssize_t r;
ssize_t n;
ssize_t delta;
int flags = fcntl(rc->write, F_GETFL, 0);
flags = fcntl(rc->write, F_SETFL, flags|O_NONBLOCK);
while(iovcnt>0 && iov->iov_len == 0) {
iov++;
iovcnt--;
}
r = writev(rc->write, iov, iovcnt);
n = r;
for (; n > 0; iovcnt--, iov++) {
delta = ((size_t)n < (size_t)iov->iov_len) ? n : (ssize_t)iov->iov_len;
n -= delta;
iov->iov_len -= delta;
iov->iov_base = (char *)iov->iov_base + delta;
if (iov->iov_len > 0)
break;
}
return r;
}
ssize_t
generic_data_read(
void *cookie,
void *vbuf,
size_t sizebuf,
int timeout G_GNUC_UNUSED)
{
struct tcp_conn *rc = cookie;
return read(rc->read, (char *)vbuf, sizebuf);
}