/* 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef OPENSSL_SUPPORT # include #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 * ****************************************************************************/