diff options
author | Michal Kubecek <mkubecek@suse.cz> | 2015-04-13 09:21:39 +0200 |
---|---|---|
committer | Michal Kubecek <mkubecek@suse.cz> | 2015-04-13 09:21:39 +0200 |
commit | e2bc6f4153813cc570ae814c8ddb74628009b488 (patch) | |
tree | a40b171be1d859c2232ccc94f758010f9ae54d3c /src/sockets | |
download | twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.gz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.lz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.tar.xz twinkle-e2bc6f4153813cc570ae814c8ddb74628009b488.zip |
initial checkin
Check in contents of upstream 1.4.2 tarball, exclude generated files.
Diffstat (limited to 'src/sockets')
-rw-r--r-- | src/sockets/Makefile.am | 30 | ||||
-rw-r--r-- | src/sockets/connection.cpp | 294 | ||||
-rw-r--r-- | src/sockets/connection.h | 232 | ||||
-rw-r--r-- | src/sockets/connection_table.cpp | 411 | ||||
-rw-r--r-- | src/sockets/connection_table.h | 170 | ||||
-rw-r--r-- | src/sockets/dnssrv.cpp | 176 | ||||
-rw-r--r-- | src/sockets/dnssrv.h | 36 | ||||
-rw-r--r-- | src/sockets/interfaces.cpp | 114 | ||||
-rw-r--r-- | src/sockets/interfaces.h | 56 | ||||
-rw-r--r-- | src/sockets/socket.cpp | 444 | ||||
-rw-r--r-- | src/sockets/socket.h | 222 | ||||
-rw-r--r-- | src/sockets/url.cpp | 911 | ||||
-rw-r--r-- | src/sockets/url.h | 279 |
13 files changed, 3375 insertions, 0 deletions
diff --git a/src/sockets/Makefile.am b/src/sockets/Makefile.am new file mode 100644 index 0000000..fdcfce4 --- /dev/null +++ b/src/sockets/Makefile.am @@ -0,0 +1,30 @@ +AM_CPPFLAGS = \ + -Wall \ + -I$(top_srcdir)/src\ + $(XML2_CFLAGS) + +noinst_LIBRARIES = libsocket.a + +#noinst_PROGRAMS = srvinfo urlinfo +#srvinfo_SOURCES = srvinfo.cpp +#srvinfo_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/sockets/libsocket.a\ +# -lresolv +#urlinfo_SOURCES = urlinfo.cpp +#urlinfo_LDADD = $(top_builddir)/src/util.o\ +# $(top_builddir)/src/sockets/libsocket.a\ +# -lresolv + +libsocket_a_SOURCES =\ + connection.cpp\ + connection_table.cpp\ + dnssrv.cpp\ + interfaces.cpp\ + socket.cpp\ + url.cpp\ + connection.h\ + connection_table.h\ + dnssrv.h\ + interfaces.h\ + socket.h\ + url.h diff --git a/src/sockets/connection.cpp b/src/sockets/connection.cpp new file mode 100644 index 0000000..6d5486a --- /dev/null +++ b/src/sockets/connection.cpp @@ -0,0 +1,294 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#include "connection.h" + +#include <algorithm> +#include <iostream> + +#include "connection_table.h" +#include "log.h" +#include "sys_settings.h" +#include "util.h" +#include "audits/memman.h" +#include "parser/parse_ctrl.h" + +extern t_connection_table *connection_table; + +using namespace std; + +t_connection::t_connection(t_socket *socket) : + socket_(socket), + sip_msg_(NULL), + pos_send_buf_(0), + idle_time_(0), + can_reuse_(true) +{} + +t_connection::~t_connection() { + MEMMAN_DELETE(socket_); + delete socket_; +} + +t_socket *t_connection::get_socket(void) { + return socket_; +} + +t_connection::size_type t_connection::data_size(void) const { + return read_buf_.size(); +} + +void t_connection::read(bool &connection_closed) { + connection_closed = false; + char buf[READ_BLOCK_SIZE]; + + ssize_t nread = socket_->recv(buf, READ_BLOCK_SIZE); + + if (nread > 0) { + read_buf_.append(buf, buf + nread); + } else { + connection_closed = true; + } + + idle_time_ = 0; +} + +void t_connection::write(void) { + if (send_buf_.empty()) return; + + ssize_t nwrite = send_buf_.size() - pos_send_buf_; + if ((ssize_t)WRITE_BLOCK_SIZE < nwrite) nwrite = WRITE_BLOCK_SIZE; + ssize_t nwritten = socket_->send(send_buf_.c_str() + pos_send_buf_, nwrite); + pos_send_buf_ += nwritten; + + if (pos_send_buf_ >= send_buf_.size()) { + // All data written + send_buf_.clear(); + pos_send_buf_ = 0; + } +} + +ssize_t t_connection::send(const char *data, int data_size) { + ssize_t bytes_sent = socket_->send(data, data_size); + idle_time_ = 0; + + return bytes_sent; +} + +void t_connection::async_send(const char *data, int data_size) { + send_buf_ += string(data, data_size); + connection_table->restart_write_select(); +} + +t_sip_message *t_connection::get_sip_msg(string &raw_headers, string &raw_body, bool &error, + bool &msg_too_large) +{ + string log_msg; + + raw_headers.clear(); + raw_body.clear(); + error = false; + msg_too_large = false; + + if (!sip_msg_) { + // RFC 3261 7.5 + // Ignore CRLF preceding the start-line of a SIP message + while (read_buf_.size() >= 2 && read_buf_.substr(0, 2) == string(CRLF)) { + remove_data(2); + } + + // A complete list of headers has not been read yet, try + // to find the boundary between headers and body. + string seperator = string(CRLF) + string(CRLF); + string::size_type pos_body = read_buf_.find(seperator); + + if (pos_body == string::npos) { + // Still no complete list of headers. + if (read_buf_.size() > sys_config->get_sip_max_tcp_size()) { + log_file->write_report("Message too large", + "t_connection::get_sip_msg", LOG_SIP, LOG_WARNING); + error = true; + } + return NULL; + } + + pos_body += seperator.size(); + + // Parse SIP headers + raw_sip_headers_ = read_buf_.substr(0, pos_body); + list<string> parse_errors; + try { + sip_msg_ = t_parser::parse(raw_sip_headers_, parse_errors); + } + catch (int) { + // Discard malformed SIP messages. + log_msg = "Invalid SIP message.\n"; + log_msg += "Fatal parse error in headers.\n\n"; + log_msg += to_printable(raw_sip_headers_); + log_file->write_report(log_msg, "t_connection::get_sip_msg", LOG_SIP, LOG_DEBUG); + + error = true; + return NULL; + } + + // Log non-fatal parse errors. + if (!parse_errors.empty()) { + log_msg = "Parse errors:\n"; + log_msg += "\n"; + for (list<string>::iterator i = parse_errors.begin(); + i != parse_errors.end(); i++) + { + log_msg += *i; + log_msg += "\n"; + } + log_msg += "\n"; + log_file->write_report(log_msg, "t_connection::get_sip_msg", LOG_SIP, LOG_DEBUG); + } + + get_remote_address(sip_msg_->src_ip_port.ipaddr, sip_msg_->src_ip_port.port); + sip_msg_->src_ip_port.transport = "tcp"; + + // Remove the processed headers from the read buffer. + remove_data(pos_body); + } + + // RFC 3261 18.4 + // The Content-Length header field MUST be used with stream oriented transports. + if (!sip_msg_->hdr_content_length.is_populated()) { + // The transaction layer will send an error response. + log_file->write_report("Content-Length header is missing.", + "t_connection::get_sip_msg", LOG_SIP, LOG_WARNING); + } else { + if (read_buf_.size() < sip_msg_->hdr_content_length.length) { + // No full body read yet. + if (read_buf_.size() + raw_sip_headers_.size() <= + sys_config->get_sip_max_tcp_size()) + { + return NULL; + } else { + log_file->write_report("Message too large", + "t_connection::get_sip_msg", LOG_SIP, LOG_WARNING); + + msg_too_large = true; + } + } else { + if (sip_msg_->hdr_content_length.length > 0) { + raw_body = read_buf_.substr(0, sip_msg_->hdr_content_length.length); + remove_data(sip_msg_->hdr_content_length.length); + } + } + } + + // Return data to caller. Clear internally cached data. + t_sip_message *msg = sip_msg_; + sip_msg_ = NULL; + raw_headers = raw_sip_headers_; + raw_sip_headers_.clear(); + + return msg; +} + +string t_connection::get_data(size_t nbytes) const { + size_t nread = min(nbytes, read_buf_.size()); + + return read_buf_.substr(0, nread); +} + +void t_connection::remove_data(size_t nbytes) { + if (nbytes == 0) return; + + if (nbytes >= read_buf_.size()) { + read_buf_.clear(); + } else { + read_buf_.erase(0, nbytes); + } +} + +void t_connection::get_remote_address(unsigned long &remote_addr, unsigned short &remote_port) { + remote_addr = 0; + remote_port = 0; + + try { + t_socket_tcp *tcp_socket = dynamic_cast<t_socket_tcp *>(socket_); + if (tcp_socket) { + tcp_socket->get_remote_address(remote_addr, remote_port); + } else { + log_file->write_report("Socket is not connection oriented.", + "t_connection::get_sip_msg", + LOG_NORMAL, LOG_WARNING); + } + } + catch (int err) { + string errmsg = get_error_str(err); + string log_msg = "Cannot get remote address: "; + log_msg += errmsg; + log_file->write_report(log_msg, "t_connection::get_sip_msg", + LOG_NORMAL, LOG_WARNING); + } +} + +unsigned long t_connection::increment_idle_time(unsigned long interval) { + idle_time_ += interval; + return idle_time_; +} + +unsigned long t_connection::get_idle_time(void) const { + return idle_time_; +} + +bool t_connection::has_data_to_send(void) const { + return !send_buf_.empty(); +} + +void t_connection::set_reuse(bool reuse) { + can_reuse_ = reuse; +} + +bool t_connection::may_reuse(void) const { + return can_reuse_; +} + +void t_connection::add_registered_uri(const t_url &uri) { + // Add the URI if it is not in the set. + if (find(registered_uri_set_.begin(), registered_uri_set_.end(), uri) == registered_uri_set_.end()) + { + registered_uri_set_.push_back(uri); + } +} + +void t_connection::remove_registered_uri(const t_url &uri) { + registered_uri_set_.remove(uri); +} + +void t_connection::update_registered_uri_set(const t_request *req) { + assert(req->method == REGISTER); + + if (req->is_registration_request()) { + add_registered_uri(req->hdr_to.uri); + } else if (req->is_de_registration_request()) { + remove_registered_uri(req->hdr_to.uri); + } +} + +const list<t_url> &t_connection::get_registered_uri_set(void) const { + return registered_uri_set_; +} + +bool t_connection::has_registered_uri(void) const { + return !registered_uri_set_.empty(); +} diff --git a/src/sockets/connection.h b/src/sockets/connection.h new file mode 100644 index 0000000..b907206 --- /dev/null +++ b/src/sockets/connection.h @@ -0,0 +1,232 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +/** + * @file + * Network connection + */ + +#ifndef _H_CONNECTION +#define _H_CONNECTION + +#include <list> +#include <string> + +#include "socket.h" +#include "parser/request.h" +#include "parser/sip_message.h" + +using namespace std; + +/** Abstract class for a network connection. */ +class t_connection { +private: + static const unsigned int READ_BLOCK_SIZE = 1448; + static const unsigned int WRITE_BLOCK_SIZE = 1448; + + /** Buffer with data already read from the network. */ + string read_buf_; + + /** Socket for connection. */ + t_socket *socket_; + + /** SIP message parsed from available data (headers only) */ + t_sip_message *sip_msg_; + + /** Raw SIP headers that have been parsed already */ + string raw_sip_headers_; + + /** Data to be sent on the connection. */ + string send_buf_; + + /** Position in send buffer for next send action. */ + string::size_type pos_send_buf_; + + /** + * Time (ms) that the connection is idle . + * This time is reset to zero by read and send actions. + */ + unsigned long idle_time_; + + /** + * Flag to indicate that a connection can be reused. + * By default a connection is reusable. + */ + bool can_reuse_; + + /** + * A set of user URI's (AoR) that are registered via this connection. + * If persistent connections are used for NAT traversal, then these are + * the URI's that are impacted when the connection breaks. + * A URI is only added to this set, if a persistent connection is required + * for this user. + * @note The set is implemented as a list as t_url has not less-than operator. + */ + list<t_url> registered_uri_set_; + +public: + typedef string::size_type size_type; + + t_connection(t_socket *socket); + + /** + * Destuctor. + * @note The socket will be closed and destroyed. + */ + virtual ~t_connection(); + + /** + * Get a pointer to the socket. + * @return The socket. + */ + t_socket *get_socket(void); + + /** + * Get the amount of data in the read buffer. + * @return Number of bytes in read buffer. + */ + size_type data_size(void) const; + + /** + * Read a block data from connection in to read buffer. + * @param connection_closed [out] Indicates if the connection was closed. + * @throw int errno as set by recv. + */ + void read(bool &connection_closed); + + /** + * Send a block of data from the send buffer on a connection. + * @throw int errno. + */ + void write(void); + + /** + * Send data on a connection. + * @param data [in] Data to send + * @param data_size [in] Size of data in bytes + * @return Number of bytes sent. + * @throw int errno. + */ + ssize_t send(const char *data, int data_size); + + /** + * Append data to the send buffer for asynchronous sending. + * @param data [in] Data to send + * @param data_size [in] Size of data in bytes + */ + void async_send(const char *data, int data_size); + + /** + * Get a SIP message from the connection. + * @param raw_headers [out] Raw headers of SIP message + * @param raw_body [out] Raw body of SIP message + * @param error [out] Indicates if an error occurred (invalid SIP message) + * @param msg_too_large [out] Indicates that the message is cutoff because it was too large + * @return The SIP message if a message was received. + * @return NULL, if no full SIP message has been received yet or an error occurred. + * @post If error == true, then NULL is returned + * @post If msg_too_large == true, then a message is returned (partial though) + */ + t_sip_message *get_sip_msg(string &raw_headers, string &raw_body, bool &error, + bool &msg_too_large); + + /** + * Get read data from read buffer. + * @param nbytes [in] Maximum number of bytes to get. + * @return Data from the read buffer up to nbytes. + * @note The data is still in the buffer after this operation. + */ + string get_data(size_t nbytes = 0) const; + + /** + * Remove data from read buffer. + * @param nbytes [in] Number of bytes to remove. + */ + void remove_data(size_t nbytes); + + /** + * Get the remote address of a connection. + * @param remote_addr [out] Source IPv4 address of the connection. + * @param remote_port [out] Source port of the connection. + */ + void get_remote_address(unsigned long &remote_addr, unsigned short &remote_port); + + /** + * Add an interval to the idle time. + * @param interval [in] Interval in ms. + * @return The new idle time. + */ + unsigned long increment_idle_time(unsigned long interval); + + /** + * Get idle time. + * @return Idle time in ms. + */ + unsigned long get_idle_time(void) const; + + /** + * Check if there is data in the send buffer. + * @return true if there is data, otherwise false. + */ + bool has_data_to_send(void) const; + + /** Set re-use characteristic. */ + void set_reuse(bool reuse); + + /** + * Check if this connection may be reused to send data. + * @return true if the connection may be reused, otherwise false. + */ + bool may_reuse(void) const; + + /** + * Add a URI to the set of registered URI's. + * @param uri [in] The URI to add. + */ + void add_registered_uri(const t_url &uri); + + /** + * Remove a URI from the set of registered URI's. + * @param uri [in] The URI to remove. + */ + void remove_registered_uri(const t_url &uri); + + /** + * Update the set of registered URI based on a REGISTER request. + * If the REGISTER is a registration, then add the To-header URI. + * If the REGISTER is a de-registration, then remove the To-header URI. + * If the REGISTER is a query, then do nothing. + * @param req [in] A REGISTER request. + * @pre req must be a REGISTER request. + */ + void update_registered_uri_set(const t_request *req); + + /** + * Get the set of registered URI's. + * @return The set of registered URI's. + */ + const list<t_url> &get_registered_uri_set(void) const; + + /** + * Check if at least one registered URI is associated with this connection. + * @return True if a URI is associated, false otherwise. + */ + bool has_registered_uri(void) const; +}; + +#endif diff --git a/src/sockets/connection_table.cpp b/src/sockets/connection_table.cpp new file mode 100644 index 0000000..4211eb5 --- /dev/null +++ b/src/sockets/connection_table.cpp @@ -0,0 +1,411 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#include "connection_table.h" + +#include <algorithm> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/time.h> +#include <cerrno> +#include <iostream> +#include <cstdlib> +#include <fcntl.h> + +#include "log.h" +#include "protocol.h" +#include "util.h" +#include "audits/memman.h" + +using namespace std; + +extern t_connection_table *connection_table; + +void t_connection_table::create_pipe(int p[2]) { + if (pipe(p) == -1) { + string err = get_error_str(errno); + cerr << "FATAL: t_connection_table - Cannot create pipe.\n"; + cerr << err << endl; + exit(-1); + } + + if (fcntl(p[0], F_SETFL, O_NONBLOCK) == -1) { + string err = get_error_str(errno); + cerr << "FATAL: t_connection_table - fcntl fails on read side of pipe.\n"; + cerr << err << endl; + exit(-1); + } + + if (fcntl(p[1], F_SETFL, O_NONBLOCK) == -1) { + string err = get_error_str(errno); + cerr << "FATAL: t_connection_table - fcntl fails on write side of pipe.\n"; + cerr << err << endl; + exit(-1); + } +} + +void t_connection_table::signal_modification_read(void) { + t_mutex_guard guard(mtx_connections_); + + // Write a byte to the modified pipe, so a select can be retried. + char x = 'x'; + (void)write(fd_pipe_modified_read_[1], &x, 1); +} + +void t_connection_table::signal_modification_write(void) { + t_mutex_guard guard(mtx_connections_); + + // Write a byte to the modified pipe, so a select can be retried. + char x = 'x'; + (void)write(fd_pipe_modified_write_[1], &x, 1); +} + +void t_connection_table::signal_quit(void) { + t_mutex_guard guard(mtx_connections_); + + // Write a byte to the quit pipe, so a select can be halted. + char x = 'x'; + (void)write(fd_pipe_quit_read_[1], &x, 1); + (void)write(fd_pipe_quit_write_[1], &x, 1); + + terminated_ = true; +} + +t_recursive_mutex t_connection_table::mtx_connections_; + +t_connection_table::t_connection_table() : + terminated_(false) +{ + create_pipe(fd_pipe_modified_read_); + create_pipe(fd_pipe_modified_write_); + create_pipe(fd_pipe_quit_read_); + create_pipe(fd_pipe_quit_write_); +} + +t_connection_table::~t_connection_table() { + t_mutex_guard guard(mtx_connections_); + + for (list<t_connection *>::iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + MEMMAN_DELETE(*it); + delete *it; + } +} + +void t_connection_table::unlock(void) const { + mtx_connections_.unlock(); +} + +bool t_connection_table::empty(void) const { + t_mutex_guard guard(mtx_connections_); + return connections_.empty(); +} + +t_connection_table::size_type t_connection_table::size(void) const { + t_mutex_guard guard(mtx_connections_); + return connections_.size(); +} + +void t_connection_table::add_connection(t_connection *connection) { + t_mutex_guard guard(mtx_connections_); + connections_.push_back(connection); + signal_modification_read(); + signal_modification_write(); +} + +void t_connection_table::remove_connection(t_connection *connection) { + t_mutex_guard guard(mtx_connections_); + connections_.remove(connection); + signal_modification_read(); + signal_modification_write(); +} + +t_connection *t_connection_table::get_connection(unsigned long remote_addr, + unsigned short remote_port) +{ + mtx_connections_.lock(); + + t_connection *found_connection = NULL; + list<t_connection *> broken_connections; + + for (list<t_connection *>::iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + unsigned long addr; + unsigned short port; + + if ((*it)->may_reuse()) { + try { + t_socket *socket = (*it)->get_socket(); + t_socket_tcp *tcp_socket = dynamic_cast<t_socket_tcp *>(socket); + + if (tcp_socket) { + tcp_socket->get_remote_address(addr, port); + if (addr == remote_addr && port == remote_port) { + found_connection = *it; + break; + } + } + } catch (int err) { + // This should never happen. + cerr << "Cannot get remote address of socket." << endl; + + // Destroy and remove connection as it is probably broken. + broken_connections.push_back(*it); + } + } + } + + // Clear broken connections + for (list<t_connection *>::iterator it = broken_connections.begin(); + it != broken_connections.end(); ++it) + { + remove_connection(*it); + MEMMAN_DELETE(*it); + delete *it; + } + + if (!found_connection) mtx_connections_.unlock(); + return found_connection; +} + +list<t_connection *> t_connection_table::select_read(struct timeval *timeout) const { + fd_set read_fds; + int nfds = 0; + bool retry = true; + list<t_connection *> result; + + // Empty modification pipe + char pipe_buf; + while (read(fd_pipe_modified_read_[0], &pipe_buf, 1) > 0); + + while (retry) { + FD_ZERO(&read_fds); + + // Add modification pipe so select can be restarted when the + // connection table modifies. + FD_SET(fd_pipe_modified_read_[0], &read_fds); + nfds = fd_pipe_modified_read_[0]; + + // Add quit pipe so select can quit on demand. + FD_SET(fd_pipe_quit_read_[0], &read_fds); + nfds = max(nfds, fd_pipe_quit_read_[0]); + + mtx_connections_.lock(); + + for (list<t_connection *>::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + t_socket *socket = (*it)->get_socket(); + int fd = socket->get_descriptor(); + FD_SET(fd, &read_fds); + nfds = max(nfds, fd); + } + + mtx_connections_.unlock(); + + int ret = select(nfds + 1, &read_fds, NULL, NULL, timeout); + if (ret < 0) throw errno; + + if (FD_ISSET(fd_pipe_quit_read_[0], &read_fds)) { + // Quit was signalled, so stop immediately. + break; + } + + mtx_connections_.lock(); + + // Determine which sockets have become readable + for (list<t_connection *>::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + t_socket *socket = (*it)->get_socket(); + int fd = socket->get_descriptor(); + if (FD_ISSET(fd, &read_fds)) { + result.push_back(*it); + } + } + + if (!result.empty()) { + // Connections have become readable, so return to the caller. + retry = false; + } else { + mtx_connections_.unlock(); + + // No connections have become readable. Check signal descriptors + if (FD_ISSET(fd_pipe_modified_read_[0], &read_fds)) { + // The connection table is modified. Retry select. + read(fd_pipe_modified_read_[0], &pipe_buf, 1); + } else { + // This should never happen. + cerr << "ERROR: select_read returned without any file descriptor." << endl; + } + } + } + + return result; +} + +list<t_connection *> t_connection_table::select_write(struct timeval *timeout) const { + fd_set read_fds; + fd_set write_fds; + int nfds = 0; + bool retry = true; + list<t_connection *> result; + + // Empty modification pipe + char pipe_buf; + while (read(fd_pipe_modified_write_[0], &pipe_buf, 1) > 0); + + while (retry) { + FD_ZERO(&read_fds); + FD_ZERO(&write_fds); + + // Add modification pipe so select can be restarted when the + // connection table modifies. + FD_SET(fd_pipe_modified_write_[0], &read_fds); + nfds = fd_pipe_modified_write_[0]; + + // Add quit pipe so select can quit on demand. + FD_SET(fd_pipe_quit_write_[0], &read_fds); + nfds = max(nfds, fd_pipe_quit_write_[0]); + + mtx_connections_.lock(); + + for (list<t_connection *>::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + if ((*it)->has_data_to_send()) { + t_socket *socket = (*it)->get_socket(); + int fd = socket->get_descriptor(); + FD_SET(fd, &write_fds); + nfds = max(nfds, fd); + } + } + + mtx_connections_.unlock(); + + int ret = select(nfds + 1, &read_fds, &write_fds, NULL, timeout); + if (ret < 0) throw errno; + + if (FD_ISSET(fd_pipe_quit_write_[0], &read_fds)) { + // Quit was signalled, so stop immediately. + break; + } + + mtx_connections_.lock(); + + // Determine which sockets have become writable + for (list<t_connection *>::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + t_socket *socket = (*it)->get_socket(); + int fd = socket->get_descriptor(); + if (FD_ISSET(fd, &write_fds)) { + result.push_back(*it); + } + } + + if (!result.empty()) { + // Connections have become writable, so return to the caller. + retry = false; + } else { + mtx_connections_.unlock(); + + // No connections have become writable. Check signal descriptors + if (FD_ISSET(fd_pipe_modified_write_[0], &read_fds)) { + // The connection table is modified. Retry select. + read(fd_pipe_modified_write_[0], &pipe_buf, 1); + } else { + // This should never happen. + cerr << "ERROR: select_write returned without any file descriptor." << endl; + } + } + } + + return result; +} + +void t_connection_table::cancel_select(void) { + signal_quit(); +} + +void t_connection_table::restart_write_select(void) { + signal_modification_write(); +} + +void t_connection_table::close_idle_connections(unsigned long interval, bool &terminated) { + t_mutex_guard guard(mtx_connections_); + + terminated = terminated_; + + list<t_connection *> expired_connections; + + // Update idle times and find expired connections. + for (list<t_connection *>::iterator it = connections_.begin(); + it != connections_.end(); ++it) + { + unsigned long idle_time = (*it)->increment_idle_time(interval); + if (idle_time >= DUR_IDLE_CONNECTION || terminated) { + // If a registered URI is associated with the connection, then + // it is persistent and it should not be closed. + if (!(*it)->has_registered_uri()) { + expired_connections.push_back(*it); + } + } + } + + // Close expired connections. + for (list<t_connection *>::iterator it = expired_connections.begin(); + it != expired_connections.end(); ++it) + { + unsigned long ipaddr; + unsigned short port; + + (*it)->get_remote_address(ipaddr, port); + log_file->write_header("t_connection_table::close_idle_connections", LOG_NORMAL, LOG_DEBUG); + log_file->write_raw("Close connection to "); + log_file->write_raw(h_ip2str(ipaddr)); + log_file->write_raw(":"); + log_file->write_raw(int2str(port)); + log_file->write_endl(); + log_file->write_footer(); + + remove_connection(*it); + MEMMAN_DELETE(*it); + delete *it; + } +} + +void *connection_timeout_main(void *arg) { + bool terminated = false; + + while (!terminated) { + struct timespec sleep_timer; + + sleep_timer.tv_sec = 1; + sleep_timer.tv_nsec = 0; + nanosleep(&sleep_timer, NULL); + connection_table->close_idle_connections(1000, terminated); + } + + log_file->write_report("Connection timeout handler terminated.", + "::connection_timeout_main"); + + return NULL; +}; diff --git a/src/sockets/connection_table.h b/src/sockets/connection_table.h new file mode 100644 index 0000000..81a4894 --- /dev/null +++ b/src/sockets/connection_table.h @@ -0,0 +1,170 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +/** + * @file + * Connection table + */ + +#ifndef _H_CONNECTION_TABLE +#define _H_CONNECTION_TABLE + +#include <list> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> + +#include "connection.h" +#include "threads/mutex.h" + +using namespace std; + +/** Table of established connections. */ +class t_connection_table { +private: + /** Established connections */ + list<t_connection *> connections_; + + /** Mutex to protect concurrent access to the connections. */ + static t_recursive_mutex mtx_connections_; + + /** Indicates if the connection table is terminated. */ + bool terminated_; + + /** Pipe to signal modification of the list of readable connections. */ + int fd_pipe_modified_read_[2]; + + /** Pipe to signal modification of the list of connections with data to send. */ + int fd_pipe_modified_write_[2]; + + /** Pipe to signal the read select operation to quit. */ + int fd_pipe_quit_read_[2]; + + /** Pipe to signal the write select operation to quit. */ + int fd_pipe_quit_write_[2]; + + /** Create a pipe. */ + void create_pipe(int p[2]); + + /** Send a modification signal on the read modification pipe. */ + void signal_modification_read(void); + + /** Send a modification signal on the write modification pipe. */ + void signal_modification_write(void); + + /** Send a quit signal on the modification pipes. */ + void signal_quit(void); + +public: + typedef list<t_connection *>::size_type size_type; + + /** Constructor */ + t_connection_table(); + + /** + * Destructor. + * @note All connections in the table will be closed + * and destroyed. + */ + ~t_connection_table(); + + /** + * Unlock connection table. + * @note After some operations, the table stays lock to avoid race + * conditions. The caller should unlock the table explicitly + * when it has finished working on the connections. + */ + void unlock(void) const; + + /** + * Check if connection table is empty. + * @return true if empty, false if not empty. + */ + bool empty(void) const; + + /** + * Get number of connections in table. + * @return number of connections. + */ + size_type size(void) const; + + /** + * Add a connection to the table. + * @param connection [in] Connection to add. + */ + void add_connection(t_connection *connection); + + /** + * Remove a TCP connection from the table. + * @param connection [in] TCP connection to remove. + */ + void remove_connection(t_connection *connection); + + /** + * Get a connection to a particular destination. + * @param remote_addr [in] IP address of destination. + * @param remote_port [in] Port of destination. + * @return The connection to the destination. If there is no + * connection to the destination, then NULL is returned. + * @post If a connection is returned, the table is locked. The caller must + * unlock the tbale when it is finished with the connection. + * @note Only re-usable connections are considered. + */ + t_connection *get_connection(unsigned long remote_addr, unsigned short remote_port); + + /** + * Wait for connections to become readable. + * @param timeout [in] Maxmimum time to wait. If NULL, then wait indefinitely. + * @return List of sockets that are readable. + * @throw int Errno + * @post The transaction table is locked if a non-empty list of sockets is returned. + * @note The caller should unlock the table when processing of the sockets is finished. + */ + list<t_connection *> select_read(struct timeval *timeout) const; + + /** + * Wait for connections to become writeable. + * Only connections with data to send are waited for. + * @param timeout [in] Maxmimum time to wait. If NULL, then wait indefinitely. + * @return List of sockets that are writable. + * @throw int Errno + * @post The transaction table is locked if a non-empty list of sockets is returned. + * @note The caller should unlock the table when processing of the sockets is finished. + */ + list<t_connection *> select_write(struct timeval *timeout) const; + + /** Cancel all selects. */ + void cancel_select(void); + + /** Restart write select, so new connections with data are picked up. */ + void restart_write_select(void); + + /** + * Close all idle connections. + * Increment the idle time of all connections with interval. + * A persistent connection with associated registered URI's will not be closed. + * @param interval [in] Interval to add to idle time (ms). + * @param terminated [out] Indicates if the connection table has been terminated. + */ + void close_idle_connections(unsigned long interval, bool &terminated); +}; + +/** Main for thread handling connection timeouts */ +void *connection_timeout_main(void *arg); + +#endif diff --git a/src/sockets/dnssrv.cpp b/src/sockets/dnssrv.cpp new file mode 100644 index 0000000..38cd61d --- /dev/null +++ b/src/sockets/dnssrv.cpp @@ -0,0 +1,176 @@ +/* + This software is copyrighted (c) 2002 Rick van Rein, the Netherlands. + + This software has been modified by Michel de Boer. 2005 +*/ + +#include "dnssrv.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <resolv.h> +#include <errno.h> +#include <stdlib.h> +#include <netdb.h> +#include <sys/socket.h> +#include <string.h> +#include <signal.h> + + +/* Common offsets into an SRV RR */ +#define SRV_COST (RRFIXEDSZ+0) +#define SRV_WEIGHT (RRFIXEDSZ+2) +#define SRV_PORT (RRFIXEDSZ+4) +#define SRV_SERVER (RRFIXEDSZ+6) +#define SRV_FIXEDSZ (RRFIXEDSZ+6) + + +/* Data structures */ +typedef struct { + unsigned char buf [PACKETSZ]; + int len; +} iobuf; +typedef char name [MAXDNAME]; +#define MAXNUM_SRV PACKETSZ + +/* Local variable for SRV options */ +static unsigned long int srv_flags = 0L; + + +/* Setup the SRV options when initialising -- invocation optional */ +void insrv_init (unsigned long flags) { +#ifdef HAVE_RES_INIT + srv_flags = flags; + res_init (); +#endif +} + + +/* Test the given SRV options to see if all are set */ +int srv_testflag (unsigned long flags) { + return ((srv_flags & flags) == flags) ? 1 : 0; +} + + +/* Compare two SRV records by priority and by (scattered) weight */ +int srvcmp (const void *left, const void *right) { + int lcost = ntohs (((unsigned short **) left ) [0][5]); + int rcost = ntohs (((unsigned short **) right) [0][5]); + if (lcost == rcost) { + lcost = -ntohs (((unsigned short **) left ) [0][6]); + rcost = -ntohs (((unsigned short **) right) [0][6]); + } + if (lcost < rcost) { + return -1; + } else if (lcost > rcost) { + return +1; + } else { + return 0; + } +} + + +/* Setup a client socket for the named service over the given protocol under + * the given domain name. + */ +int insrv_lookup (const char *service, const char *proto, const char *domain, + list<t_dns_result> &result) +{ + // 1. convert service/proto to svcnm + // 2. construct SRV query for _service._proto.domain + + iobuf names; + name svcnm; + int ctr; + int rnd; + HEADER *nameshdr; + unsigned char *here, *srv[MAXNUM_SRV]; + int num_srv=0; + // Storage for fallback SRV list, constructed when DNS gives no SRV + //unsigned char fallbacksrv [2*(MAXCDNAME+SRV_FIXEDSZ+MAXCDNAME)]; + + // srv_flags &= ~SRV_GOT_MASK; + // srv_flags |= SRV_GOT_SRV; + + strcpy (svcnm, "_"); + strcat (svcnm, service); + strcat (svcnm, "._"); + strcat (svcnm, proto); + + // Note that SRV records are only defined for class IN + if (domain) { + names.len=res_querydomain (svcnm, domain, + C_IN, T_SRV, + names.buf, PACKETSZ); + } else { + names.len=res_query (svcnm, + C_IN, T_SRV, + names.buf, PACKETSZ); + } + if (names.len < 0) { + return -ENOENT; + } + nameshdr=(HEADER *) names.buf; + here=names.buf + HFIXEDSZ; + rnd=nameshdr->id; // Heck, gimme one reason why not! + + if ((names.len < HFIXEDSZ) || nameshdr->tc) { + return -EMSGSIZE; + } + switch (nameshdr->rcode) { + case 1: + return -EFAULT; + case 2: + return -EAGAIN; + case 3: + return -ENOENT; + case 4: + return -ENOSYS; + case 5: + return -EPERM; + default: + break; + } + if (ntohs (nameshdr->ancount) == 0) { + return -ENOENT; + } + if (ntohs (nameshdr->ancount) > MAXNUM_SRV) { + return -ERANGE; + } + for (ctr=ntohs (nameshdr->qdcount); ctr>0; ctr--) { + int strlen=dn_skipname (here, names.buf+names.len); + here += strlen + QFIXEDSZ; + } + for (ctr=ntohs (nameshdr->ancount); ctr>0; ctr--) { + int strlen=dn_skipname (here, names.buf+names.len); + here += strlen; + srv [num_srv++] = here; + here += SRV_FIXEDSZ; + here += dn_skipname (here, names.buf+names.len); + } + + // Overwrite weight with rnd-spread version to divide load over weights + for (ctr=0; ctr<num_srv; ctr++) { + *(unsigned short *) (srv [ctr]+SRV_WEIGHT) + = htons(rnd % (1+ns_get16 (srv [ctr]+SRV_WEIGHT))); + } + qsort (srv, num_srv, sizeof (*srv), srvcmp); + + result.clear(); + for (ctr=0; ctr<num_srv; ctr++) { + name srvname; + if (ns_name_ntop (srv [ctr]+SRV_SERVER, srvname, MAXDNAME) < 0) { + return -errno; + } + + t_dns_result r; + r.hostname = srvname; + r.port = ns_get16(srv [ctr]+SRV_PORT); + result.push_back(r); + } + + return 0; +} diff --git a/src/sockets/dnssrv.h b/src/sockets/dnssrv.h new file mode 100644 index 0000000..bf9ed53 --- /dev/null +++ b/src/sockets/dnssrv.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#ifndef _DNSSRV_H +#define _DNSSRV_H + +#include <list> +#include <string> + +using namespace std; + +typedef struct { + string hostname; + unsigned short port; +} t_dns_result; + +void insrv_init (unsigned long flags); +int insrv_lookup (const char *service, const char *proto, const char *domain, + list<t_dns_result> &result); + +#endif diff --git a/src/sockets/interfaces.cpp b/src/sockets/interfaces.cpp new file mode 100644 index 0000000..293c48d --- /dev/null +++ b/src/sockets/interfaces.cpp @@ -0,0 +1,114 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#include <cstring> + +#include "interfaces.h" +#include "url.h" + +using namespace std; + +t_interface::t_interface(string _name) : name(_name) {} + +string t_interface::get_ip_addr(void) const { + return inet_ntoa(address); +} + +string t_interface::get_ip_netmask(void) const { + return inet_ntoa(netmask); +} + +list <t_interface> *get_interfaces(bool include_loopback) { + struct ifaddrs *ifa, *ifaddrs; + struct sockaddr_in *sin; + t_interface *intf; + + list<t_interface> *result = new list<t_interface>; + + if (getifaddrs(&ifaddrs)) { + // No interfaces found + return result; + } + + for (ifa = ifaddrs; ifa ; ifa = ifa -> ifa_next) { + // Skip interface without address + // Skip interfaces marked DOWN and LOOPBACK. + if (ifa->ifa_addr == NULL || !(ifa->ifa_flags & IFF_UP) || + ((ifa->ifa_flags & IFF_LOOPBACK) && !include_loopback)) { + continue; + } + + // Add the interface to the list if it has an IP4 address + switch(ifa->ifa_addr->sa_family) { + case AF_INET: + intf = new t_interface(ifa->ifa_name); + sin = (struct sockaddr_in *)ifa->ifa_addr; + memcpy(&intf->address, &sin->sin_addr, + sizeof(struct in_addr)); + sin = (struct sockaddr_in *)ifa->ifa_netmask; + memcpy(&intf->netmask, &sin->sin_addr, + sizeof(struct in_addr)); + + result->push_back(*intf); + delete intf; + break; + } + } + + freeifaddrs(ifaddrs); + + return result; +} + +bool exists_interface(const string &hostname) { + struct hostent *h; + + h = gethostbyname(hostname.c_str()); + if (h == NULL) return false; + string ipaddr = inet_ntoa(*((struct in_addr *)h->h_addr)); + + list<t_interface> *l = get_interfaces(true); + + for (list<t_interface>::iterator i = l->begin(); i != l->end(); i++) { + if (i->get_ip_addr() == ipaddr) { + delete l; + return true; + } + } + + delete l; + return false; +} + + + +bool exists_interface_dev(const string &devname, string &ip_address) { + + list<t_interface> *l = get_interfaces(true); + + for (list<t_interface>::iterator i = l->begin(); i != l->end(); i++) { + if (i->name == devname) { + ip_address = i->get_ip_addr(); + delete l; + return true; + } + } + + delete l; + return false; +} diff --git a/src/sockets/interfaces.h b/src/sockets/interfaces.h new file mode 100644 index 0000000..c8cc633 --- /dev/null +++ b/src/sockets/interfaces.h @@ -0,0 +1,56 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#ifndef _H_INTERFACES +#define _H_INTERFACES + +#include <list> +#include <string> +#include <netdb.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <ifaddrs.h> + +using namespace std; + +class t_interface { +public: + string name; // interface name, eg. eth0 + struct in_addr address; // interface IP address + struct in_addr netmask; // interface netmask + + t_interface(string _name); + + // Get string representation of IP address + string get_ip_addr(void) const; + string get_ip_netmask(void) const; +}; + +// Return a list of all interfaces that are UP +// If include_loopback == true, then the loopback interface is returned as well. +list<t_interface> *get_interfaces(bool include_loopback = false); + +// Check if an interface with a certain IP address exists +bool exists_interface(const string &hostname); + +// Check if an interface exists and return its IP address +bool exists_interface_dev(const string &devname, string &ip_address); + +#endif diff --git a/src/sockets/socket.cpp b/src/sockets/socket.cpp new file mode 100644 index 0000000..87bdc27 --- /dev/null +++ b/src/sockets/socket.cpp @@ -0,0 +1,444 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#include <cstdio> +#include <cerrno> +#include <cstring> +#include <sys/un.h> +#include "twinkle_config.h" +#include "socket.h" +#include "audits/memman.h" + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#if HAVE_LINUX_TYPES_H +#include <linux/types.h> +#endif + +#if HAVE_LINUX_ERRQUEUE_H +#include <linux/errqueue.h> +#endif + +///////////////// +// t_icmp_msg +///////////////// + +t_icmp_msg::t_icmp_msg(short _type, short _code, unsigned long _icmp_src_ipaddr, + unsigned long _ipaddr, unsigned short _port) : + type(_type), code(_code), icmp_src_ipaddr(_icmp_src_ipaddr), + ipaddr(_ipaddr), port(_port) +{} + +///////////////// +// t_socket +///////////////// + +t_socket::~t_socket() { + close(sd); +} + +t_socket::t_socket() : sd(0) +{} + +t_socket::t_socket(int _sd) : sd(_sd) +{} + +int t_socket::get_descriptor(void) const { + return sd; +} + +int t_socket::getsockopt(int level, int optname, void *optval, socklen_t *optlen) { + return ::getsockopt(sd, level, optname, optval, optlen); +} + +int t_socket::setsockopt(int level, int optname, const void *optval, socklen_t optlen) { + return ::setsockopt(sd, level, optname, optval, optlen); +} + +///////////////// +// t_socket_udp +///////////////// + + +t_socket_udp::t_socket_udp() { + struct sockaddr_in addr; + int ret; + + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) throw errno; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(0); + ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) throw errno; +} + +t_socket_udp::t_socket_udp(unsigned short port) { + struct sockaddr_in addr; + int ret; + + sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) throw errno; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) throw errno; +} + +int t_socket_udp::connect(unsigned long dest_addr, unsigned short dest_port) { + struct sockaddr_in addr; + int ret; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(dest_addr); + addr.sin_port = htons(dest_port); + ret = ::connect(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) throw errno; + + return ret; +} + +int t_socket_udp::sendto(unsigned long dest_addr, unsigned short dest_port, + const char *data, int data_size) { + struct sockaddr_in addr; + int ret; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(dest_addr); + addr.sin_port = htons(dest_port); + ret = ::sendto(sd, data, data_size, 0, + (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) throw errno; + + return ret; +} + +ssize_t t_socket_udp::send(const void *data, int data_size) { + int ret = ::send(sd, data, data_size, 0); + if (ret < 0) throw errno; + + return ret; +} + +int t_socket_udp::recvfrom(unsigned long &src_addr, unsigned short &src_port, + char *buf, int buf_size) { + struct sockaddr_in addr; + int ret, len_addr; + + len_addr = sizeof(addr); + memset(buf, 0, buf_size); + ret = ::recvfrom(sd, buf, buf_size - 1, 0, + (struct sockaddr *)&addr, (socklen_t *)&len_addr); + if (ret < 0) throw errno; + + src_addr = ntohl(addr.sin_addr.s_addr); + src_port = ntohs(addr.sin_port); + + return ret; +} + +ssize_t t_socket_udp::recv(void *buf, int buf_size) { + int ret; + + memset(buf, 0, buf_size); + ret = ::recv(sd, buf, buf_size - 1, 0); + if (ret < 0) throw errno; + + return ret; +} + +bool t_socket_udp::select_read(unsigned long timeout) { + fd_set fds; + struct timeval t; + + FD_ZERO(&fds); + FD_SET(sd, &fds); + + t.tv_sec = timeout / 1000; + t.tv_usec = (timeout % 1000) * 1000; + + int ret = select(sd + 1, &fds, NULL, NULL, &t); + + if (ret < 0) throw errno; + if (ret == 0) return false; + return true; +} + +bool t_socket_udp::enable_icmp(void) { +#if HAVE_LINUX_ERRQUEUE_H + int enable = 1; + int ret = setsockopt(SOL_IP, IP_RECVERR, &enable, sizeof(int)); + if (ret < 0) return false; + return true; +#else + return false; +#endif +} + +bool t_socket_udp::get_icmp(t_icmp_msg &icmp) { +#if HAVE_LINUX_ERRQUEUE_H + int ret; + char buf[256]; + + // The destination address of the packet causing the ICMP + struct sockaddr dest_addr; + + struct msghdr msgh; + struct cmsghdr *cmsg; + + // Initialize message header to receive the ancillary data for + // an ICMP message. + memset(&msgh, 0, sizeof(struct msghdr)); + msgh.msg_control = buf; + msgh.msg_controllen = 256; + msgh.msg_name = &dest_addr; + msgh.msg_namelen = sizeof(struct sockaddr); + + // Get error from the socket error queue + ret = recvmsg(sd, &msgh, MSG_ERRQUEUE); + if (ret < 0) return false; + + // Find ICMP message in returned controll messages + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) + { + if (cmsg->cmsg_level == SOL_IP && + cmsg->cmsg_type == IP_RECVERR) + { + // ICMP message found + sock_extended_err *err = (sock_extended_err *)CMSG_DATA(cmsg); + icmp.type = err->ee_type; + icmp.code = err->ee_code; + + // Get IP address of host that has sent the ICMP error + sockaddr *sa_src_icmp = SO_EE_OFFENDER(err); + if (sa_src_icmp->sa_family == AF_INET) { + sockaddr_in *addr = (sockaddr_in *)sa_src_icmp; + icmp.icmp_src_ipaddr = ntohl(addr->sin_addr.s_addr); + } else { + // Non supported address type + icmp.icmp_src_ipaddr = 0; + } + + // Get destinnation address/port of packet causing the error. + if (dest_addr.sa_family == AF_INET) { + sockaddr_in *addr = (sockaddr_in *)&dest_addr; + icmp.ipaddr = ntohl(addr->sin_addr.s_addr); + icmp.port = ntohs(addr->sin_port); + return true; + } else { + // Non supported address type + continue; + } + } + } +#endif + return false; +} + +string h_ip2str(unsigned long ipaddr) { + char buf[16]; + unsigned long x = htonl(ipaddr); + unsigned char *ipbuf = (unsigned char *)&x; + + snprintf(buf, 16, "%u.%u.%u.%u", ipbuf[0], ipbuf[1], ipbuf[2], + ipbuf[3]); + + return string(buf); +} + +///////////////// +// t_socket_tcp +///////////////// + +t_socket_tcp::t_socket_tcp() { + sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) throw errno; +} + +t_socket_tcp::t_socket_tcp(unsigned short port) { + struct sockaddr_in addr; + int ret; + + sd = socket(AF_INET, SOCK_STREAM, 0); + if (sd < 0) throw errno; + + int enable = 1; + + // Allow server to connect to the socket immediately (disable TIME_WAIT) + (void)setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + + enable = 1; + + // Disable Nagle algorithm + (void)setsockopt(IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + ret = bind(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) throw errno; +} + +t_socket_tcp::t_socket_tcp(int _sd) : t_socket(_sd) +{} + +void t_socket_tcp::listen(int backlog) { + int ret = ::listen(sd, backlog); + if (ret < 0) throw errno; +} + +t_socket_tcp *t_socket_tcp::accept(unsigned long &src_addr, unsigned short &src_port) { + struct sockaddr_in addr; + socklen_t socklen = sizeof(addr); + int ret = ::accept(sd, (struct sockaddr *)&addr, &socklen); + if (ret < 0) throw errno; + + src_addr = ntohl(addr.sin_addr.s_addr); + src_port = ntohs(addr.sin_port); + + t_socket_tcp *sock = new t_socket_tcp(ret); + MEMMAN_NEW(sock); + return sock; +} + +void t_socket_tcp::connect(unsigned long dest_addr, unsigned short dest_port) { + struct sockaddr_in addr; + int ret; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(dest_addr); + addr.sin_port = htons(dest_port); + ret = ::connect(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) throw errno; +} + +ssize_t t_socket_tcp::send(const void *data, int data_size) { + ssize_t ret = ::send(sd, data, data_size, 0); + if (ret < 0) throw errno; + + return ret; +} + +ssize_t t_socket_tcp::recv(void *buf, int buf_size) { + ssize_t ret; + + ret = ::recv(sd, buf, buf_size, 0); + if (ret < 0) throw errno; + + return ret; +} + +void t_socket_tcp::get_remote_address(unsigned long &remote_addr, unsigned short &remote_port) { + struct sockaddr_in addr; + socklen_t namelen = sizeof(addr); + + int ret = getpeername(sd, (struct sockaddr *)&addr, &namelen); + if (ret < 0) throw errno; + if (addr.sin_family != AF_INET) throw EBADF; + + remote_addr = ntohl(addr.sin_addr.s_addr); + remote_port = ntohs(addr.sin_port); +}; + +///////////////// +// t_socket_local +///////////////// + +t_socket_local::t_socket_local() { + sd = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sd < 0) throw errno; +} + +t_socket_local::t_socket_local(int _sd) { + sd = _sd; +} + +void t_socket_local::bind(const string &name) { + int ret; + struct sockaddr_un sockname; + + // A name for a local socket can be at most 108 characters + // including NULL at end of string. + if (name.size() > 107) { + throw ENAMETOOLONG; + } + + sockname.sun_family = AF_LOCAL; + strcpy(sockname.sun_path, name.c_str()); + ret = ::bind(sd, (struct sockaddr *)&sockname, SUN_LEN(&sockname)); + if (ret < 0) throw errno; +} + +void t_socket_local::listen(int backlog) { + int ret; + ret = ::listen(sd, backlog); + if (ret < 0) throw errno; +} + +int t_socket_local::accept(void) { + int ret; + ret = ::accept(sd, NULL, 0); + if (ret < 0) throw errno; + return ret; +} + +void t_socket_local::connect(const string &name) { + int ret; + struct sockaddr_un sockname; + + // A name for a local socket can be at most 108 characters + // including NULL at end of string. + if (name.size() > 107) { + throw ENAMETOOLONG; + } + + sockname.sun_family = AF_LOCAL; + strcpy(sockname.sun_path, name.c_str()); + ret = ::connect(sd, (struct sockaddr *)&sockname, SUN_LEN(&sockname)); + if (ret < 0) throw errno; +} + +int t_socket_local::read(void *buf, int count) { + int ret; + + ret = ::read(sd, buf, count); + if (ret < 0) throw errno; + return ret; +} + +ssize_t t_socket_local::recv(void *buf, int buf_size) { + return read(buf, buf_size); +} + +int t_socket_local::write(const void *buf, int count) { + int ret; + + ret = ::write(sd, buf, count); + if (ret < 0) throw errno; + return ret; +} + +ssize_t t_socket_local::send(const void *buf, int count) { + return write(buf, count); +} diff --git a/src/sockets/socket.h b/src/sockets/socket.h new file mode 100644 index 0000000..0f0f183 --- /dev/null +++ b/src/sockets/socket.h @@ -0,0 +1,222 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +/** + * @file + * Socket operations + */ + +#ifndef _H_SOCKET +#define _H_SOCKET + +#include <string> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +using namespace std; + +// ports and addresses should be in host order + +/** ICMP message */ +class t_icmp_msg { +public: + short type; + short code; + + // ICMP source IP address + unsigned long icmp_src_ipaddr; + + // Destination IP address/port of packet causing the ICMP message. + unsigned long ipaddr; + unsigned short port; + + t_icmp_msg() {}; + t_icmp_msg(short _type, short _code, unsigned long _icmp_src_ipaddr, + unsigned long _ipaddr, unsigned short _port); +}; + +/** Abstract socket */ +class t_socket { +protected: + int sd; /**< Socket descriptor. */ + + /** + * Constructor. This constructor does not create a valid socket. + * The subclasses will create the real socket. + */ + t_socket(); + + /** + * Constructor. + * @param _sd Socket desciptor. + */ + t_socket(int _sd); + +public: + /** Destructor */ + virtual ~t_socket(); + + /** + * Get the socket descriptor. + * @return The socket descriptor. + */ + int get_descriptor(void) const; + + /** Get socket options */ + int getsockopt(int level, int optname, void *optval, socklen_t *optlen); + + /** Set socket options */ + int setsockopt(int level, int optname, const void *optval, socklen_t optlen); + + /** Receive data */ + virtual ssize_t recv(void *buf, int buf_size) = 0; + + /** Send data */ + virtual ssize_t send(const void *data, int data_size) = 0; +}; + +/** UDP socket */ +class t_socket_udp : public t_socket { +public: + // Create a socket and bind it to any port. + // Throws an int exception if it fails. The int thrown is the value + // of errno as set by 'socket' or 'bind'. + t_socket_udp(); + + // Create a socket and bind it to port. + // Throws an int exception if it fails. The int thrown is the value + // of errno as set by 'socket' or 'bind'. + t_socket_udp(unsigned short port); + + // Connect a socket + // Throws an int exception if it fails (errno as set by 'sendto') + int connect(unsigned long dest_addr, unsigned short dest_port); + + // Throws an int exception if it fails (errno as set by 'sendto') + int sendto(unsigned long dest_addr, unsigned short dest_port, + const char *data, int data_size); + virtual ssize_t send(const void *data, int data_size); + + // Throws an int exception if it fails (errno as set by 'recvfrom') + // On success the length of the data in buf is returned. After the + // data in buf there will be a 0. + int recvfrom(unsigned long &src_addr, unsigned short &src_port, + char *buf, int buf_size); + + virtual ssize_t recv(void *buf, int buf_size); + + // Do a select on the socket in read mode. timeout is in ms. + // Returns true if the socket becomes unblocked. Returns false + // on time out. Throws an int exception if select fails + // (errno as set by 'select') + bool select_read(unsigned long timeout); + + // Enable reception of ICMP errors on this socket. + // Returns false if ICMP reception cannot be enabled. + bool enable_icmp(void); + + // Get an ICMP message that was received on this socket. + // Returns false if no ICMP message can be retrieved. + bool get_icmp(t_icmp_msg &icmp); +}; + +/** TCP socket */ +class t_socket_tcp : public t_socket { +public: + /** + * Constructor. Create a socket. + * @throw int The errno value + */ + t_socket_tcp(); + + /** + * Constructor. Create a socket and bind it to a port. + * @throw int The errno value + */ + t_socket_tcp(unsigned short port); + + /** + * Constructor. Create a socket from an existing descriptor. + */ + t_socket_tcp(int _sd); + + /** + * Listen for a connection. + * @throw int Errno + */ + void listen(int backlog); + + + /** + * Accept a connection + * @param src_addr [out] Source IPv4 address of the connection. + * @param src_port [out] Source port of the connection. + * @return A socket for the new connection + * @throw int Errno + */ + t_socket_tcp *accept(unsigned long &src_addr, unsigned short &src_port); + + /** + * Connect to a destination + * @param dest_addr [in] Destination IPv4 address. + * @param dest_port [in] Destination port. + * @throw int Errno + */ + void connect(unsigned long dest_addr, unsigned short dest_port); + + /** Send data */ + virtual ssize_t send(const void *data, int data_size); + + + /** Receive data */ + virtual ssize_t recv(void *buf, int buf_size); + + /** + * Get the remote address of a connection. + * @param remote_addr [out] Source IPv4 address of the connection. + * @param remote_port [out] Source port of the connection. + * @throw int Errno + */ + void get_remote_address(unsigned long &remote_addr, unsigned short &remote_port); +}; + +/** Local socket */ +class t_socket_local : public t_socket { +public: + // Throws an int exception if it fails. The int thrown is the value + // of errno as set by 'socket' + t_socket_local(); + + t_socket_local(int _sd); + + void bind(const string &name); + void listen(int backlog); + int accept(void); + void connect(const string &name); + int read(void *buf, int count); + virtual ssize_t recv(void *buf, int buf_size); + int write(const void *buf, int count); + virtual ssize_t send(const void *buf, int count); +}; + +// Convert an IP address in host order to a string. +string h_ip2str(unsigned long ipaddr); + +#endif diff --git a/src/sockets/url.cpp b/src/sockets/url.cpp new file mode 100644 index 0000000..0b10dd0 --- /dev/null +++ b/src/sockets/url.cpp @@ -0,0 +1,911 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <netdb.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include "dnssrv.h" +#include "log.h" +#include "socket.h" +#include "url.h" +#include "user.h" +#include "util.h" + +using namespace std; + +unsigned short get_default_port(const string &protocol) { + if (protocol == "mailto") return 25; + if (protocol == "http") return 80; + if (protocol == "sip") return 5060; + if (protocol == "sips") return 5061; + if (protocol == "stun") return 3478; + + return 0; +} + +unsigned long gethostbyname(const string &name) { + struct hostent *h; + + h = gethostbyname(name.c_str()); + if (h == NULL) return 0; + return ntohl(*((unsigned long *)h->h_addr)); +} + +list<unsigned long> gethostbyname_all(const string &name) { + struct hostent *h; + list<unsigned long> l; + + h = gethostbyname(name.c_str()); + if (h == NULL) return l; + + char **ipaddr = h->h_addr_list; + while (*ipaddr) { + l.push_back(ntohl(*((unsigned long *)(*ipaddr)))); + ipaddr++; + } + + return l; +} + +string get_local_hostname(void) { + char name[256]; + int rc = gethostname(name, 256); + + if (rc < 0) { + return "localhost"; + } + + struct hostent *h = gethostbyname(name); + + if (h == NULL) { + return "localhost"; + } + + return h->h_name; +} + +unsigned long get_src_ip4_address_for_dst(unsigned long dst_ip4) { + string log_msg; + struct sockaddr_in addr; + int ret; + + // Create UDP socket + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + string err = get_error_str(errno); + log_msg = "Cannot create socket: "; + log_msg += err; + log_file->write_report(log_msg, "::get_src_ip4_address_for_dst", + LOG_NORMAL, LOG_CRITICAL); + return 0; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(dst_ip4); + addr.sin_port = htons(5060); + + // Connect to the destination. Note that no network traffic + // is sent out as this is a UDP socket. The routing engine + // will set the correct source address however. + ret = connect(sd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + string err = get_error_str(errno); + log_msg = "Cannot connect socket: "; + log_msg += err; + log_file->write_report(log_msg, "::get_src_ip4_address_for_dst", + LOG_NORMAL, LOG_CRITICAL); + + close(sd); + return 0; + } + + // Get source address of socket + memset(&addr, 0, sizeof(addr)); + socklen_t len_addr = sizeof(addr); + ret = getsockname(sd, (struct sockaddr *)&addr, &len_addr); + if (ret < 0) { + string err = get_error_str(errno); + log_msg = "Cannot get sockname: "; + log_msg += err; + log_file->write_report(log_msg, "::get_src_ip4_address_for_dst", + LOG_NORMAL, LOG_CRITICAL); + + close(sd); + return 0; + } + + close(sd); + return ntohl(addr.sin_addr.s_addr); +} + +string display_and_url2str(const string &display, const string &url) { + string s; + + if (!display.empty()) { + if (must_quote(display)) s += '"'; + s += display; + if (must_quote(display)) s += '"'; + s += " <"; + } + + s += url; + + if (!display.empty()) s += '>'; + + return s; +} + +// t_ip_port + +t_ip_port::t_ip_port(unsigned long _ipaddr, unsigned short _port) : + transport("udp"), ipaddr(_ipaddr), port(_port) {} + +t_ip_port::t_ip_port(const string &proto, unsigned long _ipaddr, unsigned short _port) : + transport(proto), ipaddr(_ipaddr), port(_port) {} + +void t_ip_port::clear(void) { + transport.clear(); + ipaddr = 0; + port = 0; +} + +bool t_ip_port::is_null(void) const { + return (ipaddr == 0 && port == 0); +} + +bool t_ip_port::operator==(const t_ip_port &other) const { + return (transport == other.transport && + ipaddr == other.ipaddr && + port == other.port); +} + +bool t_ip_port::operator!=(const t_ip_port &other) const { + return !operator==(other); +} + +string t_ip_port::tostring(void) const { + string s; + s = transport; + s += ":"; + s += h_ip2str(ipaddr); + s += ":"; + s += int2str(port); + + return s; +} + +// Private + +void t_url::construct_user_url(const string &s) { + string::size_type i; + string r; + + // Determine user/password (both are optional) + i = s.find('@'); + if (i != string::npos) { + if (i == 0 || i == s.size()-1) return; + string userpass = s.substr(0, i); + r = s.substr(i+1); + i = userpass.find(':'); + if (i != string::npos) { + if (i == 0 || i == userpass.size()-1) return; + user = unescape_hex(userpass.substr(0, i)); + password = unescape_hex(userpass.substr(i+1)); + + if (escape_passwd_value(password) != password) { + modified = true; + } + } else { + user = unescape_hex(userpass); + } + + // Set modified flag if user contains reserved symbols. + // This enforces escaping these symbols when the url gets + // encoded. + if (escape_user_value(user) != user) { + modified = true; + } + } else { + r = s; + } + + // Determine host/port + string hostport; + + i = r.find_first_of(";?"); + if (i != string::npos) { + hostport = r.substr(0, i); + if (!parse_params_headers(r.substr(i))) return; + } else { + hostport = r; + } + + if (hostport.empty()) return; + + if (hostport.at(0) == '[') { + // Host contains an IPv6 reference + i = hostport.find(']'); + if (i == string::npos) return; + // TODO: check format of an IPv6 address + host = hostport.substr(0, i+1); + if (i < hostport.size()-1) { + if (hostport.at(i+1) != ':') return; // wrong port separator + if (i+1 == hostport.size()-1) return; // port missing + unsigned long p = atol(hostport.substr(i+2).c_str()); + if (p > 65535) return; // illegal port value + port = (unsigned short)p; + } + } else { + // Host contains a host name or IPv4 address + i = hostport.find(':'); + if (i != string::npos) { + if (i == 0 || i == hostport.size()-1) return; + host = hostport.substr(0, i); + unsigned long p = atol(hostport.substr(i+1).c_str()); + if (p > 65535) return; // illegal port value + port = (unsigned short)p; + } else { + host = hostport; + } + } + + user_url = true; + valid = true; +} + + +void t_url::construct_machine_url(const string &s) { + string::size_type i; + + // Determine host + string hostport; + i = s.find_first_of("/?;"); + if ( i != string::npos) { + hostport = s.substr(0, i); + if (!parse_params_headers(s.substr(i))) return; + } else { + hostport = s; + } + + if (hostport.empty()) return; + + if (hostport.at(0) == '[') { + // Host contains an IPv6 reference + i = hostport.find(']'); + if (i == string::npos) return; + // TODO: check format of an IPv6 address + host = hostport.substr(0, i+1); + if (i < hostport.size()-1) { + if (hostport.at(i+1) != ':') return; // wrong port separator + if (i+1 == hostport.size()-1) return; // port missing + unsigned long p = atol(hostport.substr(i+2).c_str()); + if (p > 65535) return; // illegal port value + port = (unsigned short)p; + } + } else { + // Host contains a host name or IPv4 address + i = hostport.find(':'); + if (i != string::npos) { + if (i == 0 || i == hostport.size()-1) return; + host = hostport.substr(0, i); + unsigned long p = atol(hostport.substr(i+1).c_str()); + if (p > 65535) return; // illegal port value + port = (unsigned short)p; + } else { + host = hostport; + } + } + + user_url = false; + valid = true; +} + +bool t_url::parse_params_headers(const string &s) { + string param_str = ""; + + // Find start of headers + // Note: parameters will not contain / or ?-symbol + string::size_type header_start = s.find_first_of("/?"); + if (header_start != string::npos) { + headers = s.substr(header_start + 1); + + if (s[0] == ';') { + // The first symbol of the parameter list is ; + // Remove this. + param_str = s.substr(1, header_start - 1); + } + } else { + // There are no headers + // The first symbol of the parameter list is ; + // Remove this. + param_str = s.substr(1); + } + + if (param_str == "") return true; + + // Create a list of single parameters. Parameters are + // seperated by semi-colons. + // Note: parameters will not contain a semi-colon in the + // name or value. + vector<string> param_lst = split(param_str, ';'); + + // Parse the parameters + for (vector<string>::iterator i = param_lst.begin(); + i != param_lst.end(); i++) + { + string pname; + string pvalue; + + vector<string> param = split(*i, '='); + if (param.size() > 2) return false; + + pname = tolower(unescape_hex(trim(param.front()))); + if (param.size() == 2) { + pvalue = tolower(unescape_hex(trim(param.back()))); + } + + if (pname == "transport") { + transport = pvalue; + } else if (pname == "maddr") { + maddr = pvalue; + } else if (pname == "lr") { + lr = true; + } else if (pname == "user") { + user_param = pvalue; + } else if (pname == "method") { + method = pvalue; + } else if (pname == "ttl") { + ttl = atoi(pvalue.c_str()); + } else { + other_params += ';'; + other_params += *i; + } + } + + return true; +} + +// Public static + +string t_url::escape_user_value(const string &user_value) { + // RFC 3261 + // user = 1*( unreserved / escaped / user-unreserved ) + // user-unreserved = "&" / "=" / "+" / "$" / "," / ";" / "?" / "/" + // unreserved = alphanum / mark + // mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" + + return escape_hex(user_value, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYX0123456789"\ + "-_.!~*'()&=+$,;?/"); +} + +string t_url::escape_passwd_value(const string &passwd_value) { + // RFC 3261 + // password = *( unreserved / escaped / "&" / "=" / "+" / "$" / "," ) + // unreserved = alphanum / mark + // mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" + + return escape_hex(passwd_value, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYX0123456789"\ + "-_.!~*'()&=+$,"); +} + +string t_url::escape_hnv(const string &hnv) { + // RFC 3261 + // hname = 1*( hnv-unreserved / unreserved / escaped ) + // hvalue = *( hnv-unreserved / unreserved / escaped ) + // hnv-unreserved = "[" / "]" / "/" / "?" / ":" / "+" / "$" + // unreserved = alphanum / mark + // mark = "-" / "_" / "." / "!" / "~" / "*" / "'" / "(" / ")" + + return escape_hex(hnv, + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYX0123456789"\ + "-_.!~*'()[]/?:+$"); +} + +// Public + +t_url::t_url(void) { + modified = false; + valid = false; + port = 0; + lr = false; + ttl = 0; +} + +t_url::t_url(const string &s) { + set_url(s); +} + +t_url t_url::copy_without_headers(void) const { + t_url u(*this); + u.clear_headers(); + return u; +} + +void t_url::set_url(const string &s) { + string::size_type i; + string r; + + modified = false; + valid = false; + scheme.clear(); + user.clear(); + password.clear(); + host.clear(); + port = 0; + transport.clear(); + maddr.clear(); + lr = false; + user_param.clear(); + method.clear(); + ttl = 0; + other_params.clear(); + headers.clear(); + user_url = false; + + text_format = s; + + // Determine scheme. A scheme is mandatory. There should + // be text following the scheme. + i = s.find(':'); + if (i == string::npos || i == 0 || i == s.size()-1) return; + scheme = tolower(s.substr(0, i)); + r = s.substr(i+1); + + if (r[0] == '/') { + if (r.size() == 1) return; + if (r[1] != '/') return; + construct_machine_url(r.substr(2)); + } else { + construct_user_url(r); + } +} + +string t_url::get_scheme(void) const { + return scheme; +} + +string t_url::get_user(void) const { + return user; +} + +string t_url::get_password(void) const { + return password; +} + +string t_url::get_host(void) const { + return host; +} + +int t_url::get_nport(void) const { + return htons(get_hport()); +} + +int t_url::get_hport(void) const { + if (port != 0) return port; + return get_default_port(scheme); +} + +int t_url::get_port(void) const { + return port; +} + +unsigned long t_url::get_n_ip(void) const { + struct hostent *h; + + // TODO: handle multiple A RR's + + if (scheme == "tel") return 0; + + h = gethostbyname(host.c_str()); + if (h == NULL) return 0; + return *((unsigned long *)h->h_addr); +} + +unsigned long t_url::get_h_ip(void) const { + if (scheme == "tel") return 0; + return gethostbyname(host); +} + +list<unsigned long> t_url::get_h_ip_all(void) const { + if (scheme == "tel") return list<unsigned long>(); + return gethostbyname_all(host); +} + +string t_url::get_ip(void) const { + struct hostent *h; + + // TODO: handle multiple A RR's + + if (scheme == "tel") return 0; + + h = gethostbyname(host.c_str()); + if (h == NULL) return ""; + return inet_ntoa(*((struct in_addr *)h->h_addr)); +} + +list<t_ip_port> t_url::get_h_ip_srv(const string &transport) const { + list<t_ip_port> ip_list; + list<t_dns_result> srv_list; + list<unsigned long> ipaddr_list; + + if (scheme == "tel") return list<t_ip_port>(); + + // RFC 3263 4.2 + // Only do an SRV lookup if host is a hostname and no port is specified. + if (!is_ipaddr(host) && port == 0) { + int ret = insrv_lookup(scheme.c_str(), transport.c_str(), + host.c_str(), srv_list); + + if (ret >= 0 && !srv_list.empty()) { + // SRV RR's found + for (list<t_dns_result>::iterator i = srv_list.begin(); + i != srv_list.end(); i++) + { + // Get A RR's + t_ip_port ip_port; + ipaddr_list = gethostbyname_all(i->hostname); + for (list<unsigned long>::iterator j = ipaddr_list.begin(); + j != ipaddr_list.end(); j++) + { + ip_list.push_back(t_ip_port(transport, *j, i->port)); + } + } + + return ip_list; + } + } + + // No SRV RR's found, do an A RR lookup + t_ip_port ip_port; + ipaddr_list = get_h_ip_all(); + for (list<unsigned long>::iterator j = ipaddr_list.begin(); + j != ipaddr_list.end(); j++) + { + ip_list.push_back(t_ip_port(transport, *j, get_hport())); + } + + return ip_list; +} + +string t_url::get_transport(void) const { + return transport; +} + +string t_url::get_maddr(void) const { + return maddr; +} + +bool t_url::get_lr(void) const { + return lr; +} + +string t_url::get_user_param(void) const { + return user_param; +} + +string t_url::get_method(void) const { + return method; +} + +int t_url::get_ttl(void) const { + return ttl; +} + +string t_url::get_other_params(void) const { + return other_params; +} + +string t_url::get_headers(void) const { + return headers; +} + +void t_url::set_user(const string &u) { + modified = true; + user = u; +} + +void t_url::set_host(const string &h) { + modified = true; + host = h; +} + +void t_url::add_header(const t_header &hdr) { + if (!hdr.is_populated()) return; + + modified = true; + + if (!headers.empty()) headers += ';'; + headers += escape_hnv(hdr.get_name()); + headers += '='; + headers += escape_hnv(hdr.get_value()); +} + +void t_url::clear_headers(void) { + if (headers.empty()) { + // No headers to clear + return; + } + + modified = true; + headers.clear(); +} + +bool t_url::is_valid(void) const { + return valid; +} + +// RCF 3261 19.1.4 +bool t_url::sip_match(const t_url &u) const { + if (!u.is_valid() || !is_valid()) return false; + + // Compare schemes + if (scheme != "sip" && scheme != "sips") return false; + if (u.get_scheme() != "sip" && u.get_scheme() != "sips") { + return false; + } + if (scheme != u.get_scheme()) return false; + + // Compare user info + if (user != u.get_user()) return false; + if (password != u.get_password()) return false; + + // Compare host/port + if (cmp_nocase(host, u.get_host()) != 0) return false; + if (port != u.get_port()) return false; + + // Compare parameters + if (transport != "" && u.get_transport() != "" && + cmp_nocase(transport, u.get_transport()) != 0) + { + return false; + } + + if (maddr != u.get_maddr()) return false; + if (cmp_nocase(user_param, u.get_user_param()) != 0) return false; + if (cmp_nocase(method, u.get_method()) != 0) return false; + if (ttl != u.get_ttl()) return false; + + // TODO: compare other params and headers + + return true; +} + +bool t_url::operator==(const t_url &u) const { + return sip_match(u); +} + +bool t_url::operator!=(const t_url &u) const { + return !sip_match(u); +} + +bool t_url::user_host_match(const t_url &u, bool looks_like_phone, + const string &special_symbols) const +{ + string u1 = get_user(); + string u2 = u.get_user(); + + // For tel-URIs the phone number is in the host part. + if (scheme == "tel") u1 = get_host(); + if (u.scheme == "tel") u2 = u.get_host(); + + bool u1_is_phone = false; + bool u2_is_phone = false; + + if (is_phone(looks_like_phone, special_symbols)) { + u1 = remove_symbols(u1, special_symbols); + u1_is_phone = true; + } + + if (u.is_phone(looks_like_phone, special_symbols)) { + u2 = remove_symbols(u2, special_symbols); + u2_is_phone = true; + } + + if (u1 != u2) return false; + + if (u1_is_phone && u2_is_phone) { + // Both URLs are phone numbers. Do not compare + // the host-part. + return true; + } + + return (get_host() == u.get_host()); +} + +bool t_url::user_looks_like_phone(const string &special_symbols) const { + return looks_like_phone(user, special_symbols); +} + +bool t_url::is_phone(bool looks_like_phone, const string &special_symbols) const { + if (scheme == "tel") return true; + + // RFC 3261 19.1.1 + if (user_param == "phone") return true; + return (looks_like_phone && user_looks_like_phone(special_symbols)); +} + +string t_url::encode(void) const { + if (modified) { + if (!user_url) { + // TODO: machine URL's are currently not used + return text_format; + } + + string s; + + s = scheme; + s += ':'; + + if (!user.empty()) { + s += escape_user_value(user); + + if (!password.empty()) { + s += ':'; + s += escape_passwd_value(password); + } + + s += '@'; + } + + s += host; + + if (port > 0) { + s += ':'; + s += int2str(port); + } + + if (!transport.empty()) { + s += ";transport="; + s += transport; + } + + if (!maddr.empty()) { + s += ";maddr="; + s += maddr; + } + + if (lr) { + s += ";lr"; + } + + if (!user_param.empty()) { + s += ";user="; + s += user_param; + } + + if (!method.empty()) { + s += ";method="; + s += method; + } + + if (ttl > 0) { + s += ";ttl="; + s += int2str(ttl); + } + + if (!other_params.empty()) { + s += other_params; + } + + if (!headers.empty()) { + s += "?"; + s += headers; + } + + return s; + } else { + return text_format; + } +} + +string t_url::encode_noscheme(void) const { + string s = encode(); + string::size_type i = s.find(':'); + + if (i != string::npos && i < s.size()) { + s = s.substr(i + 1); + } + + return s; +} + +string t_url::encode_no_params_hdrs(bool escape) const { + if (!user_url) { + // TODO: machine URL's are currently not used + return text_format; + } + + string s; + + s = scheme; + s += ':'; + + if (!user.empty()) { + if (escape) { + s += escape_user_value(user); + } else { + s += user; + } + + if (!password.empty()) { + s += ':'; + if (escape) { + s += escape_passwd_value(password); + } else { + s += password; + } + } + + s += '@'; + } + + s += host; + + if (port > 0) { + s += ':'; + s += int2str(port); + } + + return s; +} + +void t_url::apply_conversion_rules(t_user *user_config) { + if (scheme == "tel") { + host = user_config->convert_number(host); + } else { + // Convert user part for all other schemes + user = user_config->convert_number(user); + } + + modified = true; +} + + +t_display_url::t_display_url() {} + +t_display_url::t_display_url(const t_url &_url, const string &_display) : + url(_url), display(_display) {} + +bool t_display_url::is_valid() { + return url.is_valid(); +} + +string t_display_url::encode(void) const { + string s; + + if (!display.empty()) { + if (must_quote(display)) s += '"'; + s += display; + if (must_quote(display)) s += '"'; + s += " <"; + } + + s += url.encode(); + + if (!display.empty()) s += '>'; + + return s; +} diff --git a/src/sockets/url.h b/src/sockets/url.h new file mode 100644 index 0000000..eb0ee2d --- /dev/null +++ b/src/sockets/url.h @@ -0,0 +1,279 @@ +/* + Copyright (C) 2005-2009 Michel de Boer <michel@twinklephone.com> + + 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. + + 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 +*/ + +#ifndef _H_URL +#define _H_URL + +#include <list> +#include <string> +#include "parser/header.h" + +/** @name Forward declarations */ +//@{ +class t_user; +//@} + +using namespace std; + +class t_ip_port { +public: + string transport; + unsigned long ipaddr; + unsigned short port; + + t_ip_port() : transport("udp") {}; + t_ip_port(unsigned long _ipaddr, unsigned short _port); + t_ip_port(const string &proto, unsigned long _ipaddr, unsigned short _port); + + void clear(void); + bool is_null(void) const; + bool operator==(const t_ip_port &other) const; + bool operator!=(const t_ip_port &other) const; + string tostring(void) const; +}; + +// Return the default port for a protocol (host order) +unsigned short get_default_port(const string &protocol); + +// Return the first IP address of host name. +// Return 0 if no IP address can be found. +unsigned long gethostbyname(const string &name); + +// Return all IP address of host name +list<unsigned long> gethostbyname_all(const string &name); + +/** + * Get local host name. + * @return Local host name. + */ +string get_local_hostname(void); + +/** + * Get the source IP address that will be used for sending + * a packet to a certain destination. + * @param dst_ip4 [in] The destination IPv4 address. + * @return The source IPv4 address. + * @return 0 if the source address cannot be determined. + */ +unsigned long get_src_ip4_address_for_dst(unsigned long dst_ip4); + +class t_url { +private: + /** + * A t_url object is created with a string represnetation of + * the URL. The encode method just returns this string. + * If one of the components of the t_url object is modified + * however, then encode will build a new string representation. + * The modified flag indicates if the object was modified after + * construction. + */ + bool modified; + + /** URL scheme. */ + string scheme; + + /** The user part of a URL. For a tel URL this is empty. */ + string user; + + /** The user password. */ + string password; + + /** + * The host part of a URL. For a tel URL, it contains the part before + * the first semi-colon. + */ + string host; + + unsigned short port; // host order + + // parameters + string transport; + string maddr; + bool lr; + string user_param; + string method; + int ttl; + string other_params; // unparsed other parameters + // starting with a semi-colon + + // headers + string headers; // unparsed headers + + bool user_url; // true -> user url + // false -> machine + bool valid; // indicates if the url is valid + + string text_format; // url in string format + + void construct_user_url(const string &s); // eg sip:, mailto: + void construct_machine_url(const string &s); // eg http:, ftp: + + // Parse uri parameters and headers. Returns false if parsing + // fails. + bool parse_params_headers(const string &s); + +public: + // Escape reserved symbols in a user value + static string escape_user_value(const string &user_value); + + // Escape reserved symbols in a password value + static string escape_passwd_value(const string &passwd_value); + + // Escape reserved symbols in a header name or value + static string escape_hnv(const string &hnv); + +public: + t_url(); + t_url(const string &s); + + // Return a copy of the URI without headers. + // If the URI does not contain any headers, then the copy is + // identical to the URI. + t_url copy_without_headers(void) const; + + void set_url(const string &s); + + // Returns "" or 0 if item cannot be found + string get_scheme(void) const; + string get_user(void) const; + string get_password(void) const; + string get_host(void) const; + + // The following methods will return the default port if + // no port is present in the url. + int get_nport(void) const; // Get port in network order. + int get_hport(void) const; // get port in host order. + + // The following method returns 0 if no port is present + // in the url. + int get_port(void) const; + + // ip address network order. Return 0 if address not found + // DNS A RR lookup + unsigned long get_n_ip(void) const; + + // ip address host order. Return 0 if address not found + // DNS A RR lookup + unsigned long get_h_ip(void) const; + list<unsigned long> get_h_ip_all(void) const; + + // DNS A RR lookup + string get_ip(void) const; // ip address as string + + // Get list op IP address/ports in host order. + // First do DNS SRV lookup. If no SRV RR's are found, then + // do a DNS A RR lookup. + // transport = the transport protocol for the service + list<t_ip_port> get_h_ip_srv(const string &transport) const; + + /** @name Getters */ + //@{ + string get_transport(void) const; + string get_maddr(void) const; + bool get_lr(void) const; + string get_user_param(void) const; + string get_method(void) const; + int get_ttl(void) const; + string get_other_params(void) const; + string get_headers(void) const; + //@} + + /** @name Setters */ + //@{ + void set_user(const string &u); + void set_host(const string &h); + //@} + + /** + * Add a header to the URI. + * The encoded header will be concatenated to the headers field. + * @param hdr [in] Header to be added. + */ + void add_header(const t_header &hdr); + + /** Remove headers from the URI. */ + void clear_headers(void); + + /** + * Check if the URI is valid. + * @return True if valid, otherwise false. + */ + bool is_valid(void) const; + + // Check if 2 sip or sips url's are equivalent + bool sip_match(const t_url &u) const; + bool operator==(const t_url &u) const; + bool operator!=(const t_url &u) const; + + /** + * Check if the user-host part of 2 url's are equal. + * If the user-part is a phone number, then only compare + * the user parts. If the url is a tel-url then the host + * contains a telephone number for comparison. + * @param u [in] Other URL to compare with. + * @param looks_like_phone [in] Flag to indicate is a SIP URL + * that looks like a phone number must be treated as such. + * @param special_symbols [in] Interpuction symbols in a phone number. + * @return true if the URLs match, false otherwise. + */ + bool user_host_match(const t_url &u, bool looks_like_phone, + const string &special_symbols) const; + + // Return true if the user part looks like a phone number, i.e. + // consists of digits, *, # and special symbols + bool user_looks_like_phone(const string &special_symbols) const; + + // Return true if the URI indicates a phone number, i.e. + // - the user=phone parameter is present + // or + // - if looks_like_phone == true and the user part looks like + // a phone number + bool is_phone(bool looks_like_phone, const string &special_symbols) const; + + // Return string encoding of url + string encode(void) const; + + // Return string encoding of url without scheme information + string encode_noscheme(void) const; + + // Return string encoding of url without parameters/headers + string encode_no_params_hdrs(bool escape = true) const; + + /** + * Apply number conversion rules to modify the URL. + * @param user_config [in] The user profile having the conversion + * rules to apply. + */ + void apply_conversion_rules(t_user *user_config); +}; + +// Display name and url combined + +class t_display_url { +public: + t_url url; + string display; + + t_display_url(); + t_display_url(const t_url &_url, const string &_display); + + bool is_valid(); + string encode(void) const; +}; + +#endif |