427 lines
7.2 KiB
C
427 lines
7.2 KiB
C
/*
|
|
* Copyright (C) 2013 Martin Willi
|
|
* Copyright (C) 2013 revosec AG
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*/
|
|
|
|
#include <library.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
typedef struct private_stream_t private_stream_t;
|
|
|
|
/**
|
|
* Private data of an stream_t object.
|
|
*/
|
|
struct private_stream_t {
|
|
|
|
/**
|
|
* Public stream_t interface.
|
|
*/
|
|
stream_t public;
|
|
|
|
/**
|
|
* Underlying socket
|
|
*/
|
|
int fd;
|
|
|
|
/**
|
|
* Callback if data is ready to read
|
|
*/
|
|
stream_cb_t read_cb;
|
|
|
|
/**
|
|
* Data for read-ready callback
|
|
*/
|
|
void *read_data;
|
|
|
|
/**
|
|
* Callback if write is non-blocking
|
|
*/
|
|
stream_cb_t write_cb;
|
|
|
|
/**
|
|
* Data for write-ready callback
|
|
*/
|
|
void *write_data;
|
|
};
|
|
|
|
METHOD(stream_t, read_, ssize_t,
|
|
private_stream_t *this, void *buf, size_t len, bool block)
|
|
{
|
|
while (TRUE)
|
|
{
|
|
ssize_t ret;
|
|
|
|
if (block)
|
|
{
|
|
ret = read(this->fd, buf, len);
|
|
}
|
|
else
|
|
{
|
|
ret = recv(this->fd, buf, len, MSG_DONTWAIT);
|
|
if (ret == -1 && errno == EAGAIN)
|
|
{
|
|
/* unify EGAIN and EWOULDBLOCK */
|
|
errno = EWOULDBLOCK;
|
|
}
|
|
}
|
|
if (ret == -1 && errno == EINTR)
|
|
{ /* interrupted, try again */
|
|
continue;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
METHOD(stream_t, read_all, bool,
|
|
private_stream_t *this, void *buf, size_t len)
|
|
{
|
|
ssize_t ret;
|
|
|
|
while (len)
|
|
{
|
|
ret = read_(this, buf, len, TRUE);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (ret == 0)
|
|
{
|
|
errno = ECONNRESET;
|
|
return FALSE;
|
|
}
|
|
len -= ret;
|
|
buf += ret;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
METHOD(stream_t, write_, ssize_t,
|
|
private_stream_t *this, void *buf, size_t len, bool block)
|
|
{
|
|
ssize_t ret;
|
|
|
|
while (TRUE)
|
|
{
|
|
if (block)
|
|
{
|
|
ret = write(this->fd, buf, len);
|
|
}
|
|
else
|
|
{
|
|
ret = send(this->fd, buf, len, MSG_DONTWAIT);
|
|
if (ret == -1 && errno == EAGAIN)
|
|
{
|
|
/* unify EGAIN and EWOULDBLOCK */
|
|
errno = EWOULDBLOCK;
|
|
}
|
|
}
|
|
if (ret == -1 && errno == EINTR)
|
|
{ /* interrupted, try again */
|
|
continue;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
METHOD(stream_t, write_all, bool,
|
|
private_stream_t *this, void *buf, size_t len)
|
|
{
|
|
ssize_t ret;
|
|
|
|
while (len)
|
|
{
|
|
ret = write_(this, buf, len, TRUE);
|
|
if (ret < 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (ret == 0)
|
|
{
|
|
errno = ECONNRESET;
|
|
return FALSE;
|
|
}
|
|
len -= ret;
|
|
buf += ret;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Remove a registered watcher
|
|
*/
|
|
static void remove_watcher(private_stream_t *this)
|
|
{
|
|
if (this->read_cb || this->write_cb)
|
|
{
|
|
lib->watcher->remove(lib->watcher, this->fd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Watcher callback
|
|
*/
|
|
static bool watch(private_stream_t *this, int fd, watcher_event_t event)
|
|
{
|
|
bool keep = FALSE;
|
|
stream_cb_t cb;
|
|
|
|
switch (event)
|
|
{
|
|
case WATCHER_READ:
|
|
cb = this->read_cb;
|
|
this->read_cb = NULL;
|
|
keep = cb(this->read_data, &this->public);
|
|
if (keep)
|
|
{
|
|
this->read_cb = cb;
|
|
}
|
|
break;
|
|
case WATCHER_WRITE:
|
|
cb = this->write_cb;
|
|
this->write_cb = NULL;
|
|
keep = cb(this->write_data, &this->public);
|
|
if (keep)
|
|
{
|
|
this->write_cb = cb;
|
|
}
|
|
break;
|
|
case WATCHER_EXCEPT:
|
|
break;
|
|
}
|
|
return keep;
|
|
}
|
|
|
|
/**
|
|
* Register watcher for stream callbacks
|
|
*/
|
|
static void add_watcher(private_stream_t *this)
|
|
{
|
|
watcher_event_t events = 0;
|
|
|
|
if (this->read_cb)
|
|
{
|
|
events |= WATCHER_READ;
|
|
}
|
|
if (this->write_cb)
|
|
{
|
|
events |= WATCHER_WRITE;
|
|
}
|
|
if (events)
|
|
{
|
|
lib->watcher->add(lib->watcher, this->fd, events,
|
|
(watcher_cb_t)watch, this);
|
|
}
|
|
}
|
|
|
|
METHOD(stream_t, on_read, void,
|
|
private_stream_t *this, stream_cb_t cb, void *data)
|
|
{
|
|
remove_watcher(this);
|
|
|
|
this->read_cb = cb;
|
|
this->read_data = data;
|
|
|
|
add_watcher(this);
|
|
}
|
|
|
|
METHOD(stream_t, on_write, void,
|
|
private_stream_t *this, stream_cb_t cb, void *data)
|
|
{
|
|
remove_watcher(this);
|
|
|
|
this->write_cb = cb;
|
|
this->write_data = data;
|
|
|
|
add_watcher(this);
|
|
}
|
|
|
|
METHOD(stream_t, get_file, FILE*,
|
|
private_stream_t *this)
|
|
{
|
|
FILE *file;
|
|
int fd;
|
|
|
|
/* fclose() closes the FD passed to fdopen(), so dup() it */
|
|
fd = dup(this->fd);
|
|
if (fd == -1)
|
|
{
|
|
return NULL;
|
|
}
|
|
file = fdopen(fd, "w+");
|
|
if (!file)
|
|
{
|
|
close(fd);
|
|
}
|
|
return file;
|
|
}
|
|
|
|
METHOD(stream_t, destroy, void,
|
|
private_stream_t *this)
|
|
{
|
|
remove_watcher(this);
|
|
close(this->fd);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
stream_t *stream_create_from_fd(int fd)
|
|
{
|
|
private_stream_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.read = _read_,
|
|
.read_all = _read_all,
|
|
.on_read = _on_read,
|
|
.write = _write_,
|
|
.write_all = _write_all,
|
|
.on_write = _on_write,
|
|
.get_file = _get_file,
|
|
.destroy = _destroy,
|
|
},
|
|
.fd = fd,
|
|
);
|
|
|
|
return &this->public;
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
int stream_parse_uri_unix(char *uri, struct sockaddr_un *addr)
|
|
{
|
|
if (!strpfx(uri, "unix://"))
|
|
{
|
|
return -1;
|
|
}
|
|
uri += strlen("unix://");
|
|
|
|
memset(addr, 0, sizeof(*addr));
|
|
addr->sun_family = AF_UNIX;
|
|
strncpy(addr->sun_path, uri, sizeof(addr->sun_path));
|
|
addr->sun_path[sizeof(addr->sun_path)-1] = '\0';
|
|
|
|
return offsetof(struct sockaddr_un, sun_path) + strlen(addr->sun_path);
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
stream_t *stream_create_unix(char *uri)
|
|
{
|
|
struct sockaddr_un addr;
|
|
int len, fd;
|
|
|
|
len = stream_parse_uri_unix(uri, &addr);
|
|
if (len == -1)
|
|
{
|
|
DBG1(DBG_NET, "invalid stream URI: '%s'", uri);
|
|
return NULL;
|
|
}
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0)
|
|
{
|
|
DBG1(DBG_NET, "opening socket '%s' failed: %s", uri, strerror(errno));
|
|
return NULL;
|
|
}
|
|
if (connect(fd, (struct sockaddr*)&addr, len) < 0)
|
|
{
|
|
DBG1(DBG_NET, "connecting to '%s' failed: %s", uri, strerror(errno));
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
return stream_create_from_fd(fd);
|
|
}
|
|
|
|
/**
|
|
* See header.
|
|
*/
|
|
int stream_parse_uri_tcp(char *uri, struct sockaddr *addr)
|
|
{
|
|
char *pos, buf[128];
|
|
host_t *host;
|
|
u_long port;
|
|
int len;
|
|
|
|
if (!strpfx(uri, "tcp://"))
|
|
{
|
|
return -1;
|
|
}
|
|
uri += strlen("tcp://");
|
|
pos = strrchr(uri, ':');
|
|
if (!pos)
|
|
{
|
|
return -1;
|
|
}
|
|
if (*uri == '[' && pos > uri && *(pos - 1) == ']')
|
|
{
|
|
/* IPv6 URI */
|
|
snprintf(buf, sizeof(buf), "%.*s", (int)(pos - uri - 2), uri + 1);
|
|
}
|
|
else
|
|
{
|
|
snprintf(buf, sizeof(buf), "%.*s", (int)(pos - uri), uri);
|
|
}
|
|
port = strtoul(pos + 1, &pos, 10);
|
|
if (port == ULONG_MAX || *pos || port > 65535)
|
|
{
|
|
return -1;
|
|
}
|
|
host = host_create_from_dns(buf, AF_UNSPEC, port);
|
|
if (!host)
|
|
{
|
|
return -1;
|
|
}
|
|
len = *host->get_sockaddr_len(host);
|
|
memcpy(addr, host->get_sockaddr(host), len);
|
|
host->destroy(host);
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* See header
|
|
*/
|
|
stream_t *stream_create_tcp(char *uri)
|
|
{
|
|
union {
|
|
struct sockaddr_in in;
|
|
struct sockaddr_in6 in6;
|
|
struct sockaddr sa;
|
|
} addr;
|
|
int fd, len;
|
|
|
|
len = stream_parse_uri_tcp(uri, &addr.sa);
|
|
if (len == -1)
|
|
{
|
|
DBG1(DBG_NET, "invalid stream URI: '%s'", uri);
|
|
return NULL;
|
|
}
|
|
fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
|
|
if (fd < 0)
|
|
{
|
|
DBG1(DBG_NET, "opening socket '%s' failed: %s", uri, strerror(errno));
|
|
return NULL;
|
|
}
|
|
if (connect(fd, &addr.sa, len))
|
|
{
|
|
DBG1(DBG_NET, "connecting to '%s' failed: %s", uri, strerror(errno));
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
return stream_create_from_fd(fd);
|
|
}
|