/*
elmo - ELectronic Mail Operator
Copyright (C) 2002, 2003, 2004 rzyjontko
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; version 2.
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
----------------------------------------------------------------------
These are all primitives I need to use when connecting as client.
*/
/****************************************************************************
* IMPLEMENTATION HEADERS
****************************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <setjmp.h>
#include <signal.h>
#include <regex.h>
#ifdef OPENSSL_SUPPORT
# include <openssl/ssl.h>
#endif
#include "xmalloc.h"
#include "error.h"
#include "networking.h"
#include "gettext.h"
#include "rstring.h"
#include "cmd.h"
#include "debug.h"
#include "clock.h"
#include "str.h"
/****************************************************************************
* IMPLEMENTATION PRIVATE DEFINITIONS / ENUMERATIONS / SIMPLE TYPEDEFS
****************************************************************************/
#define BUFFER_SIZE (17 * 1024)
#define SEND_CHUNK_SIZE 1000
#define MIN(a,b) (((a)<(b))?(a):(b))
/****************************************************************************
* IMPLEMENTATION PRIVATE CLASS PROTOTYPES / EXTERNAL CLASS REFERENCES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE STRUCTURES / UTILITY CLASSES
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION REQUIRED EXTERNAL REFERENCES (AVOID)
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE DATA
****************************************************************************/
/* There is one problem with non-multi-threaded programs. You cannot
discard the connection while it's in progress because gethostbyname,
and connect block until the end of execution.
To work this around I setup a special SIGINT signal handler, and make
a nonlocal transfer of control with longjmp function from it.
The code should look like:
if (setjmp (discard_connection))
return 1;
prev_handler = signal (SIGINT, connection_break);
ret = do_the_blocking_action ();
signal (SIGINT, prev_handler);
The signal handler must restore previous handler in the same manner. */
/* The prev_handler is used to restore a previous handler (which terminates
the program execution). */
static void (*prev_handler)(int) = NULL;
/* The discard_connection is a longjmp target established before
gethostbyname, and connect. */
static sigjmp_buf discard_connection;
#ifdef OPENSSL_SUPPORT
/* OpenSSL context. */
static SSL_CTX *ssl_ctx = NULL;
#endif
struct nlist *Net::nlist = NULL;
/****************************************************************************
* INTERFACE DATA
****************************************************************************/
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTION PROTOTYPES
****************************************************************************/
static void read_handler (int fd);
static void write_handler (int fd);
/****************************************************************************
* IMPLEMENTATION PRIVATE FUNCTIONS
****************************************************************************/
static void
connection_break (int signum)
{
signal (SIGINT, prev_handler);
siglongjmp (discard_connection, 1);
}
bool
Net::resolve_host (void)
{
struct hostent *ht;
bool ret;
status = NET_RESOLVING;
ht = gethostbyname (server_name);
if (! ht){
error_ (0, _("Couldn't resolve name %s: %s"),
server_name, hstrerror (h_errno));
ret = false;
}
else {
server_addr = * (struct in_addr *) ht->h_addr_list[0];
ret = true;
}
if (! ret)
status = NET_DOWN;
return ret;
}
bool
Net::make_connection (bool secure)
{
struct sockaddr_in st;
socklen_t st_len = sizeof (struct sockaddr_in);
int ret;
if (status != NET_RESOLVING)
return false;
status = NET_CONNECTING;
sock = socket (PF_INET, SOCK_STREAM, 0);
if (sock == -1){
error_ (errno, _("Couldn't connect to %s (%s)"),
server_name, inet_ntoa (server_addr));
return false;
}
st.sin_family = AF_INET;
st.sin_addr = server_addr;
st.sin_port = server_port;
ret = connect (sock, (struct sockaddr *) & st, sizeof (st));
if (ret == -1){
error_ (errno, "%s", inet_ntoa (server_addr));
return false;
}
if (getsockname (sock, (struct sockaddr *) & st, & st_len) == 0){
local_addr = st.sin_addr;
local_port = st.sin_port;
}
if (secure){
#ifdef OPENSSL_SUPPORT
ssl = SSL_new (ssl_ctx);
SSL_set_fd (ssl, sock);
ret = SSL_connect (ssl);
#else
error_ (0, "%s", _("Secure connection requested, but "
"no ssl support."));
return false;
#endif
}
status = NET_READY;
return true;
}
void
Net::shutdown_connection (void)
{
#ifdef OPENSSL_SUPPORT
if (ssl)
SSL_shutdown (ssl);
else
#endif
shutdown (sock, SHUT_RDWR);
}
bool
Net::check_read_write_ret (int ret, const char *s)
{
if (ret < 0){
error_ (errno, s);
close ();
return false;
}
if (ret == 0){
error_ (errno, _("Server has closed the connection."));
close ();
return false;
}
return true;
}
void
Net::data_reader (void)
{
int ret;
regmatch_t matches[1];
if (read_buffer == NULL){
read_size = BUFFER_SIZE;
read_fill = 0;
read_buffer = (char *) xmalloc (BUFFER_SIZE);
}
if (read_fill + 1 >= read_size){
read_size *= 2;
read_buffer = (char *) xrealloc (read_buffer, read_size);
}
#ifdef OPENSSL_SUPPORT
if (ssl){
ret = SSL_read (ssl, read_buffer + read_fill,
read_size - read_fill - 1);
}
else
#endif
ret = recv (sock, read_buffer + read_fill,
read_size - read_fill - 1, 0);
if (! check_read_write_ret (ret, _("Error occured during receiving data.")))
return;
if (progress != -1)
progress_advance (progress, ret);
total_recv += ret;
read_fill += ret;
read_buffer[read_fill] = '\0';
ret = regexec (& terminator, read_buffer, 1, matches, 0);
if (ret && ret != REG_NOMATCH){
error_regex (ret, & terminator, NULL);
close ();
return;
}
if (ret == 0){
int len = read_fill;
read_fill = 0;
status = NET_READY;
is_compiled = 0;
regfree (& terminator);
if (progress != -1)
progress_close (progress);
progress = -1;
cmd_del_readfd_handler (sock);
/* It is absolutely crucial, that this call is the last
thing to do in this function. It is because recv_fun
may (implicitly) modify some important data structures.
It may remove some readfd handlers (including this one),
or add a new one. It may even destroy the enet object. */
recv_fun (read_buffer, len);
}
}
void
Net::data_sender (void)
{
int ret;
int size;
size = MIN (SEND_CHUNK_SIZE, send_size - send_sent);
#ifdef OPENSSL_SUPPORT
if (ssl)
ret = SSL_write (ssl, send_buffer + send_sent, size);
else
#endif
ret = send (sock, send_buffer + send_sent, size, 0);
if (! check_read_write_ret (ret, _("Error occured during sending data.")))
return;
if (progress_desc && progress == -1){
progress = progress_setup (send_size, "%s",
progress_desc->str);
}
if (progress != -1)
progress_advance (progress, ret);
total_sent += ret;
send_sent += ret;
if (send_sent == send_size){
send_buffer = NULL;
send_size = 0;
send_sent = 0;
status = NET_READY;
cmd_del_writefd_handler (sock);
/* It is absolutely crucial, that this call is the last
thing to do in this function. It is because send_fun
may (implicitly) modify some important data structures.
It may remove some readfd handlers (including this one),
or add a new one. It may even destroy the enet object. */
send_fun ();
}
}
void
Net::send_fun (void)
{
status = NET_READING_DATA;
if (progress != -1)
progress_close (progress);
if (bytes_expected != 0)
progress = progress_setup (bytes_expected, "%s",
progress_desc->str);
cmd_add_readfd_handler (sock, read_handler);
}
static void
read_handler (int fd)
{
Net *net = Net::find_by_sock (fd);
if (net){
net->data_reader ();
return;
}
error_ (0, "%s", _("No descriptor found: please submit a bug"));
cmd_del_readfd_handler (fd);
}
static void
write_handler (int fd)
{
Net *net = Net::find_by_sock (fd);
if (net){
net->data_sender ();
return;
}
error_ (0, "%s", _("No descriptor found: please submit a bug"));
cmd_del_readfd_handler (fd);
}
/****************************************************************************
* INTERFACE FUNCTIONS
****************************************************************************/
void
net_init (void)
{
signal (SIGPIPE, SIG_IGN);
#ifdef OPENSSL_SUPPORT
SSL_load_error_strings ();
SSL_library_init ();
ssl_ctx = SSL_CTX_new (SSLv23_method ());
#endif
}
void
net_free_resources (void)
{
#ifdef OPENSSL_SUPPORT
if (ssl_ctx)
SSL_CTX_free (ssl_ctx);
#endif
}
/****************************************************************************
* INTERFACE CLASS BODIES
****************************************************************************/
Net::Net (void)
{
sock = -1;
ssl = NULL;
progress = -1;
progress_desc = NULL;
bytes_expected = 0;
bytes_received = 0;
total_sent = 0;
total_recv = 0;
time_start = time (NULL);
is_compiled = false;
read_size = 0;
read_fill = 0;
read_buffer = NULL;
send_size = 0;
send_sent = 0;
send_buffer = NULL;
status = NET_DOWN;
server_name = NULL;
server_port = 0;
local_port = 0;
memset (& local_addr, '\0', sizeof (local_addr));
memset (& server_addr, '\0', sizeof (server_addr));
Net::add (this);
}
Net::~Net (void)
{
close (false);
Net::remove (this);
}
bool
Net::open (const char *hostname, unsigned short port, bool secure)
{
int p;
server_name = xstrdup (hostname);
server_port = htons (port);
if (sigsetjmp (discard_connection, 1)){
if (p != -1)
progress_close (p);
error_ (0, _("Connection to %s has been terminated."),
server_name);
return false;
}
p = progress_setup (1, _("resolving host %s..."), hostname);
errno = 0;
prev_handler = signal (SIGINT, connection_break);
if (! resolve_host ()){
if (p != -1)
progress_close (p);
signal (SIGINT, prev_handler);
return false;
}
progress_change_desc (p, _("connecting to %s..."),
inet_ntoa (server_addr));
if (! make_connection (secure)){
if (p != -1)
progress_close (p);
signal (SIGINT, prev_handler);
return false;
}
signal (SIGINT, prev_handler);
progress_close (p);
return true;
}
void
Net::close (bool callback)
{
shutdown_connection ();
if (server_name)
xfree (server_name);
server_name = NULL;
if (is_compiled)
regfree (& terminator);
is_compiled = false;
if (read_buffer)
xfree (read_buffer);
read_buffer = NULL;
if (progress != -1)
progress_close (progress);
progress = -1;
if (progress_desc)
str_destroy (progress_desc);
progress_desc = NULL;
#ifdef OPENSSL_SUPPORT
if (ssl)
SSL_free (ssl);
#endif
ssl = NULL;
if (sock != -1){
cmd_del_readfd_handler (sock);
cmd_del_writefd_handler (sock);
}
sock = -1;
bytes_expected = 0;
bytes_received = 0;
read_fill = 0;
send_sent = 0;
status = NET_DOWN;
if (callback)
on_shutdown ();
}
bool
Net::is_connected (void)
{
return status >= NET_READY;
}
void
Net::net_recv (const char *re)
{
int ret;
if (status != NET_READY)
return;
ret = regcomp (& terminator, re, REG_ICASE | REG_EXTENDED);
if (ret){
error_regex (ret, & terminator, re);
regfree (& terminator);
is_compiled = 0;
return;
}
is_compiled = 1;
status = NET_READING_DATA;
cmd_add_readfd_handler (sock, read_handler);
}
void
Net::net_send (char *buf, int size)
{
if (status != NET_READY)
return;
send_size = size;
send_buffer = buf;
status = NET_WRITING_DATA;
cmd_add_writefd_handler (sock, write_handler);
}
void
Net::expect (int size, char *desc, ...)
{
va_list ap;
if (status != NET_READY)
return;
if (progress_desc)
str_clear (progress_desc);
else
progress_desc = str_create ();
bytes_expected = size;
bytes_received = 0;
va_start (ap, desc);
str_vsprintf (progress_desc, desc, ap);
va_end (ap);
}
void
Net::send_progress (char *desc, ...)
{
va_list ap;
if (status != NET_READY)
return;
if (progress_desc)
str_clear (progress_desc);
else
progress_desc = str_create ();
va_start (ap, desc);
str_vsprintf (progress_desc, desc, ap);
va_end (ap);
}
void
Net::combo (char *buf, int size, const char *re)
{
int ret;
if (status != NET_READY)
return;
if (is_compiled){
regfree (& terminator);
is_compiled = 0;
}
ret = regcomp (& terminator, re,
REG_ICASE | REG_EXTENDED | REG_NEWLINE);
if (ret){
error_regex (ret, & terminator, re);
regfree (& terminator);
is_compiled = 0;
return;
}
is_compiled = 1;
net_send (buf, size);
}
void
Net::add (Net *net)
{
struct nlist *entry = (struct nlist*) xmalloc (sizeof (struct nlist));
entry->next = Net::nlist;
entry->net = net;
nlist = entry;
}
void
Net::remove (Net *net)
{
struct nlist *prev = Net::nlist;
struct nlist *seek = Net::nlist;
if (seek == NULL)
return;
while (seek->net != net){
prev = seek;
seek = seek->next;
}
prev->next = seek->next;
xfree (seek);
}
Net *
Net::find_by_sock (int fd)
{
struct nlist *l;
for (l = nlist; l; l = l->next)
if (l->net->sock == fd)
return l->net;
return NULL;
}
/****************************************************************************
*
* END MODULE networking.cc
*
****************************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1