diff options
Diffstat (limited to 'libmailutils/tcp.c')
-rw-r--r-- | libmailutils/tcp.c | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/libmailutils/tcp.c b/libmailutils/tcp.c new file mode 100644 index 000000000..ba15ae852 --- /dev/null +++ b/libmailutils/tcp.c @@ -0,0 +1,356 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999, 2000, 2004, 2006, 2007, 2010 Free Software + Foundation, Inc. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 3 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301 USA */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <sys/types.h> + +#include <netinet/in.h> + +#include <arpa/inet.h> + +#include <mailutils/errno.h> +#include <mailutils/stream.h> +#include <mailutils/mutil.h> + +#include <mailutils/sys/stream.h> + +#define TCP_STATE_INIT 1 +#define TCP_STATE_RESOLVE 2 +#define TCP_STATE_RESOLVING 3 +#define TCP_STATE_CONNECTING 4 +#define TCP_STATE_CONNECTED 5 + +struct _tcp_instance +{ + struct _mu_stream stream; + int fd; + char *host; + int port; + int state; + unsigned long address; + unsigned long source_addr; +}; + +/* On solaris inet_addr() return -1. */ +#ifndef INADDR_NONE +# define INADDR_NONE (unsigned long)-1 +#endif + +static int +_tcp_close (mu_stream_t stream) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + int err = 0; + + if (tcp->fd != -1) + { + if (close (tcp->fd) != 0) + { + err = errno; + } + } + tcp->fd = -1; + tcp->state = TCP_STATE_INIT; + return err; +} + +static int +resolve_hostname (const char *host, unsigned long *ip) +{ + unsigned long address = inet_addr (host); + if (address == INADDR_NONE) + { + struct hostent *phe = gethostbyname (host); + if (!phe) + return MU_ERR_GETHOSTBYNAME; + address = *(((unsigned long **) phe->h_addr_list)[0]); + } + *ip = address; + return 0; +} + +static int +_tcp_open (mu_stream_t stream) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + int flgs, ret; + socklen_t namelen; + struct sockaddr_in peer_addr; + struct sockaddr_in soc_addr; + int flags; + + mu_stream_get_flags (stream, &flags); + + switch (tcp->state) + { + case TCP_STATE_INIT: + if (tcp->fd == -1) + { + if ((tcp->fd = socket (PF_INET, SOCK_STREAM, 0)) == -1) + return errno; + } + if (flags & MU_STREAM_NONBLOCK) + { + flgs = fcntl (tcp->fd, F_GETFL); + flgs |= O_NONBLOCK; + fcntl (tcp->fd, F_SETFL, flgs); + mu_stream_set_flags (stream, MU_STREAM_NONBLOCK); + } + if (tcp->source_addr != INADDR_ANY) + { + struct sockaddr_in s; + s.sin_family = AF_INET; + s.sin_addr.s_addr = tcp->source_addr; + s.sin_port = 0; + if (bind (tcp->fd, (struct sockaddr*) &s, sizeof(s)) < 0) + { + int e = errno; + close (tcp->fd); + tcp->fd = -1; + return e; + } + } + + tcp->state = TCP_STATE_RESOLVING; + + case TCP_STATE_RESOLVING: + if (!(tcp->host != NULL && tcp->port > 0)) + { + _tcp_close (stream); + return EINVAL; + } + + if ((ret = resolve_hostname (tcp->host, &tcp->address))) + { + _tcp_close (stream); + return ret; + } + tcp->state = TCP_STATE_RESOLVE; + + case TCP_STATE_RESOLVE: + memset (&soc_addr, 0, sizeof (soc_addr)); + soc_addr.sin_family = AF_INET; + soc_addr.sin_port = htons (tcp->port); + soc_addr.sin_addr.s_addr = tcp->address; + + if ((connect (tcp->fd, + (struct sockaddr *) &soc_addr, sizeof (soc_addr))) == -1) + { + ret = errno; + if (ret == EINPROGRESS || ret == EAGAIN) + { + tcp->state = TCP_STATE_CONNECTING; + ret = EAGAIN; + } + else + _tcp_close (stream); + return ret; + } + tcp->state = TCP_STATE_CONNECTING; + + case TCP_STATE_CONNECTING: + namelen = sizeof (peer_addr); + if (getpeername (tcp->fd, + (struct sockaddr *) &peer_addr, &namelen) == 0) + tcp->state = TCP_STATE_CONNECTED; + else + { + ret = errno; + _tcp_close (stream); + return ret; + } + break; + } + return 0; +} + +static int +_tcp_ioctl (mu_stream_t stream, int code, void *ptr) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + mu_transport_t *ptrans; + + switch (code) + { + case MU_IOCTL_GET_TRANSPORT: + if (!ptr) + return EINVAL; + ptrans = ptr; + ptrans[0] = (mu_transport_t) tcp->fd; + ptrans[1] = NULL; + break; + + default: + return ENOSYS; + } + return 0; +} + +static int +_tcp_read (mu_stream_t stream, char *buf, size_t size, size_t *pret) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + ssize_t bytes; + + if ((bytes = recv (tcp->fd, buf, size, 0)) == -1) + return errno; + *pret = bytes; + return 0; +} + +static int +_tcp_write (mu_stream_t stream, const char *buf, size_t size, size_t *pret) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + ssize_t bytes; + + if ((bytes = send (tcp->fd, buf, size, 0)) == -1) + return errno; + *pret = bytes; + return 0; +} + +static void +_tcp_done (mu_stream_t stream) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + + if (tcp->host) + free (tcp->host); + if (tcp->fd != -1) + close (tcp->fd); +} + +int +_tcp_wait (mu_stream_t stream, int *pflags, struct timeval *tvp) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + if (tcp->fd == -1) + return EINVAL; + return mu_fd_wait (tcp->fd, pflags, tvp); +} + +int +_tcp_shutdown (mu_stream_t stream, int how) +{ + struct _tcp_instance *tcp = (struct _tcp_instance *)stream; + int flag; + if (tcp->fd == -1) + return EINVAL; + + switch (how) + { + case MU_STREAM_READ: + flag = SHUT_RD; + break; + + case MU_STREAM_WRITE: + flag = SHUT_WR; + } + + if (shutdown (tcp->fd, flag)) + return errno; + return 0; +} + +static struct _tcp_instance * +_create_tcp_stream (int flags) +{ + struct _tcp_instance *tcp = + (struct _tcp_instance *)_mu_stream_create (sizeof (*tcp), flags); + + if (tcp) + { + tcp->stream.open = _tcp_open; + tcp->stream.close = _tcp_close; + tcp->stream.read = _tcp_read; + tcp->stream.write = _tcp_write; + tcp->stream.ctl = _tcp_ioctl; + tcp->stream.done = _tcp_done; + tcp->stream.wait = _tcp_wait; + tcp->stream.shutdown = _tcp_shutdown; + tcp->fd = -1; + tcp->state = TCP_STATE_INIT; + } + return tcp; +} + +int +mu_tcp_stream_create_with_source_ip (mu_stream_t *stream, + const char *host, int port, + unsigned long source_ip, + int flags) +{ + struct _tcp_instance *tcp; + + if (host == NULL) + return MU_ERR_TCP_NO_HOST; + + if (port < 1) + return MU_ERR_TCP_NO_PORT; + + tcp = _create_tcp_stream (flags | MU_STREAM_RDWR); + if (!tcp) + return ENOMEM; + tcp->host = strdup (host); + if (!tcp->host) + { + free (tcp); + return ENOMEM; + } + tcp->port = port; + tcp->state = TCP_STATE_INIT; + tcp->source_addr = source_ip; + *stream = (mu_stream_t) tcp; + return 0; +} + +int +mu_tcp_stream_create_with_source_host (mu_stream_t *stream, + const char *host, int port, + const char *source_host, + int flags) +{ + unsigned long source_addr; + int ret = resolve_hostname (source_host, &source_addr); + if (ret == 0) + ret = mu_tcp_stream_create_with_source_ip (stream, host, port, + source_addr, flags); + return ret; +} + +int +mu_tcp_stream_create (mu_stream_t *stream, const char *host, int port, + int flags) +{ + return mu_tcp_stream_create_with_source_ip (stream, host, port, + INADDR_ANY, flags); +} |