summaryrefslogtreecommitdiffstats
path: root/src/listener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/listener.cpp')
-rw-r--r--src/listener.cpp616
1 files changed, 616 insertions, 0 deletions
diff --git a/src/listener.cpp b/src/listener.cpp
new file mode 100644
index 0000000..a16675d
--- /dev/null
+++ b/src/listener.cpp
@@ -0,0 +1,616 @@
+/*
+ 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 <iostream>
+#include "events.h"
+#include "listener.h"
+#include "log.h"
+#include "sys_settings.h"
+#include "translator.h"
+#include "user.h"
+#include "userintf.h"
+#include "util.h"
+#include "im/im_iscomposing_body.h"
+#include "sockets/connection_table.h"
+#include "sockets/socket.h"
+#include "parser/parse_ctrl.h"
+#include "parser/sip_message.h"
+#include "sdp/sdp_parse_ctrl.h"
+#include "stun/stun.h"
+#include "audits/memman.h"
+#include "presence/pidf_body.h"
+
+extern t_phone *phone;
+extern t_socket_udp *sip_socket;
+extern t_socket_tcp *sip_socket_tcp;
+extern t_connection_table *connection_table;
+extern t_event_queue *evq_trans_mgr;
+extern t_event_queue *evq_trans_layer;
+
+// Minimal size of a message. Messages below this size will
+// be silently discarded.
+#define MIN_MESSAGE_SIZE 10
+
+// Maximum number of pending TCP connections.
+#define TCP_BACKLOG 5
+
+void recvd_stun_msg(char *datagram, int datagram_size,
+ unsigned long src_addr, unsigned short src_port)
+{
+ StunMessage m;
+
+ if (!stunParseMessage(datagram, datagram_size, m, false)) {
+ log_file->write_report("Received faulty STUN message",
+ "::recvd_stun_msg", LOG_STUN, LOG_DEBUG);
+ return;
+ }
+
+ log_file->write_header("::recvd_stun_msg", LOG_STUN);
+ log_file->write_raw("Received from: ");
+ log_file->write_raw(h_ip2str(src_addr));
+ log_file->write_raw(":");
+ log_file->write_raw(src_port);
+ log_file->write_endl();
+ log_file->write_raw(stunMsg2Str(m));
+ log_file->write_footer();
+
+ evq_trans_mgr->push_stun_response(&m, 0, 0);
+}
+
+t_sip_body *parse_body(const string &data, const t_sip_message *msg) {
+ if (!msg->hdr_content_type.is_populated()) {
+ // Content-Type header is missing. Pass body
+ // unparsed. The upper application layer will
+ // decide what to do.
+ t_sip_body_opaque *p = new t_sip_body_opaque(data);
+ MEMMAN_NEW(p);
+ return p;
+ }
+
+ if (msg->hdr_content_type.media.type == "application" &&
+ msg->hdr_content_type.media.subtype == "sdp")
+ {
+ // Parse SDP body
+ return t_sdp_parser::parse(data);
+ } else if (msg->hdr_content_type.media.type == "message" &&
+ msg->hdr_content_type.media.subtype == "sipfrag")
+ {
+ t_sip_body_sipfrag *b;
+
+ // Parse sipfrag body (RFC 3420)
+ try {
+ // If the sipfrag does not contain a body itself,
+ // then the CRLF at the end of the headers is optional!
+ // Add an additional CRLF such that the SIP parser will
+ // parse a sipfrag if the CRLF is not present. The SIP
+ // parser will stop after it finds the double CRLF. So
+ // a 3rd CRLF will not be detected by the parser (yuck).
+ list<string> parse_errors;
+ t_sip_message *m = t_parser::parse(data + CRLF, parse_errors);
+ b = new t_sip_body_sipfrag(m);
+ MEMMAN_NEW(b);
+ MEMMAN_DELETE(m);
+ delete m;
+ return b;
+ } catch (int) {
+ // Parsing failed, maybe because a request or status
+ // line is not present, which is not mandatory for a
+ // sipfrag body. Add a fake status line and try to parse
+ // again.
+ string tmp = "SIP/2.0 100 Trying";
+ tmp += CRLF;
+ tmp += data;
+ tmp += CRLF;
+ list<string> parse_errors;
+ t_sip_message *resp = t_parser::parse(tmp, parse_errors);
+
+ // Parsing succeeded. Now strip the fake header
+ t_sip_message *m = new t_sip_message(*resp);
+ MEMMAN_NEW(m);
+ MEMMAN_DELETE(resp);
+ delete (resp);
+ b = new t_sip_body_sipfrag(m);
+ MEMMAN_NEW(b);
+ MEMMAN_DELETE(m);
+ delete m;
+ return b;
+ }
+ } else if (msg->hdr_content_type.media.type == "application" &&
+ msg->hdr_content_type.media.subtype == "dtmf-relay")
+ {
+ t_sip_body_dtmf_relay *b = new t_sip_body_dtmf_relay();
+ MEMMAN_NEW(b);
+ if (b->parse(data)) return b;
+ MEMMAN_DELETE(b);
+ delete b;
+ throw -1;
+ } else if (msg->hdr_content_type.media.type == "application" &&
+ msg->hdr_content_type.media.subtype == "simple-message-summary")
+ {
+ t_simple_msg_sum_body *b = new t_simple_msg_sum_body();
+ MEMMAN_NEW(b);
+ if (b->parse(data)) return b;
+ MEMMAN_DELETE(b);
+ delete b;
+ throw -1;
+ } else if (msg->hdr_content_type.media.type == "text" &&
+ msg->hdr_content_type.media.subtype == "plain")
+ {
+ t_sip_body_plain_text *b = new t_sip_body_plain_text(data);
+ MEMMAN_NEW(b);
+ return b;
+ } else if (msg->hdr_content_type.media.type == "text" &&
+ msg->hdr_content_type.media.subtype == "html")
+ {
+ t_sip_body_html_text *b = new t_sip_body_html_text(data);
+ MEMMAN_NEW(b);
+ return b;
+ } else if (msg->hdr_content_type.media.type == "application" &&
+ msg->hdr_content_type.media.subtype == "pidf+xml")
+ {
+ t_pidf_xml_body *b = new t_pidf_xml_body();
+ MEMMAN_NEW(b);
+ if (b->parse(data)) return b;
+ MEMMAN_DELETE(b);
+ delete b;
+ throw -1;
+ } else if (msg->hdr_content_type.media.type == "application" &&
+ msg->hdr_content_type.media.subtype == "im-iscomposing+xml")
+ {
+ t_im_iscomposing_xml_body *b = new t_im_iscomposing_xml_body();
+ MEMMAN_NEW(b);
+ if (b->parse(data)) return b;
+ MEMMAN_DELETE(b);
+ delete b;
+ throw -1;
+ } else {
+ // Pass other bodies unparsed. The upper application
+ // layer will decide what to do.
+ t_sip_body_opaque *p = new t_sip_body_opaque(data);
+ MEMMAN_NEW(p);
+ return p;
+ }
+}
+
+static void process_sip_msg(t_sip_message *msg, const string &raw_headers, const string &raw_body) {
+ t_event_network *ev_network;
+ string log_msg;
+
+ // SIP message received
+ log_msg = "Received from: ";
+ log_msg += msg->src_ip_port.tostring();
+ log_msg += "\n";
+
+ // Parse body
+ if (!raw_body.empty()) {
+ // The body should only be parsed if it is complete.
+ // NOTE: The Content-length header may be absent (UDP)
+ if (!msg->hdr_content_length.is_populated() ||
+ msg->hdr_content_length.length == raw_body.size())
+ {
+ try {
+ msg->body = parse_body(raw_body, msg);
+ }
+ catch (int) {
+ if (msg->get_type() == MSG_RESPONSE) {
+ // Discard a SIP response if the body is malformed.
+ log_msg += "Invalid SIP message.\n";
+ log_msg += "Parse error in body.\n";
+ log_msg += to_printable(raw_headers);
+ log_msg += to_printable(raw_body);
+ log_file->write_report(log_msg, "::process_sip_msg",
+ LOG_SIP, LOG_DEBUG);
+
+ return;
+ } else {
+ // For a SIP request with a malformed body, the
+ // transaction layer will give an error response.
+ // Set the invalid body indication for the transaction
+ // layer.
+ msg->body = new t_sip_body_opaque();
+ MEMMAN_NEW(msg->body);
+ msg->body->invalid = true;
+ }
+ }
+ } else {
+ log_file->write_report("Received incomplete body", "::process_sip_msg",
+ LOG_NORMAL, LOG_WARNING);
+ }
+ }
+
+ log_msg += to_printable(raw_headers);
+ log_msg += to_printable(raw_body);
+ log_file->write_report(log_msg, "::process_sip_msg", LOG_SIP);
+
+ // If the message does not satisfy the mandatory
+ // requirements from RFC 3261, then discard.
+ // If the error is non-fatal, then the transaction layer
+ // will send a proper error response.
+ // If the message is an invalid response message then
+ // discard the message. The transaction layer cannot
+ // handle an invalid response as it cannot send an
+ // error message back on an answer.
+ bool fatal;
+ string reason;
+ if (!msg->is_valid(fatal, reason) &&
+ (fatal || msg->get_type() == MSG_RESPONSE))
+ {
+ log_file->write_header("::process_sip_msg", LOG_SIP);
+ log_file->write_raw("Discard invalid message.\n");
+ log_file->write_raw(reason);
+ log_file->write_endl();
+ log_file->write_footer();
+
+ return;
+ }
+
+ if (msg->get_type() == MSG_REQUEST) {
+ // RFC 3261 18.2.1
+ // When the server transport receives a request over any transport, it
+ // MUST examine the value of the "sent-by" parameter in the top Via
+ // header field value. If the host portion of the "sent-by" parameter
+ // contains a domain name, or if it contains an IP address that differs
+ // from the packet source address, the server MUST add a "received"
+ // parameter to that Via header field value. This parameter MUST
+ // contain the source address from which the packet was received.
+ string src_ip = h_ip2str(msg->src_ip_port.ipaddr);
+ t_via &top_via = msg->hdr_via.via_list.front();
+ if (top_via.host != src_ip) {
+ top_via.received = src_ip;
+ log_file->write_header("::process_sip_msg", LOG_SIP);
+ log_file->write_raw("Added via-parameter received=");
+ log_file->write_raw(src_ip);
+ log_file->write_endl();
+ log_file->write_footer();
+ }
+
+ // RFC 3581 4
+ // Add rport value if requested
+ // Add received parameter
+ if (top_via.rport_present && top_via.rport == 0) {
+ top_via.rport = msg->src_ip_port.port;
+ top_via.received = src_ip;
+ }
+ }
+
+ ev_network = new t_event_network(msg);
+ MEMMAN_NEW(ev_network);
+ ev_network->src_addr = msg->src_ip_port.ipaddr;
+ ev_network->src_port = msg->src_ip_port.port;
+ ev_network->transport = msg->src_ip_port.transport;
+ evq_trans_mgr->push(ev_network);
+}
+
+void *listen_udp(void *arg) {
+ char buf[sys_config->get_sip_max_udp_size() + 1];
+ int data_size;
+ unsigned long src_addr;
+ unsigned short src_port;
+ t_sip_message *msg;
+ t_event_icmp *ev_icmp;
+ string::size_type pos_body; // position of body in msg
+ string log_msg;
+
+ // Number of consecutive non-icmp errors received
+ int num_non_icmp_errors = 0;
+
+ while(true) {
+ try {
+ data_size = sip_socket->recvfrom(src_addr, src_port, buf,
+ sys_config->get_sip_max_udp_size() + 1);
+ num_non_icmp_errors = 0;
+ } catch (int err) {
+ // Check if an ICMP error has been received
+ t_icmp_msg icmp;
+ if (sip_socket->get_icmp(icmp)) {
+ log_msg = "Received ICMP from: ";
+ log_msg += h_ip2str(icmp.icmp_src_ipaddr);
+ log_msg += "\nICMP type: ";
+ log_msg += int2str(icmp.type);
+ log_msg += "\nICMP code: ";
+ log_msg += int2str(icmp.code);
+ log_msg += "\nDestination of packet causing ICMP: ";
+ log_msg += h_ip2str(icmp.ipaddr);
+ log_msg += ":";
+ log_msg += int2str(icmp.port);
+ log_msg += "\nSocket error: ";
+ log_msg += int2str(err);
+ log_msg += " ";
+ log_msg += get_error_str(err);
+ log_file->write_report(log_msg, "::listen_udp", LOG_NORMAL);
+
+ ev_icmp = new t_event_icmp(icmp);
+ MEMMAN_NEW(ev_icmp);
+ evq_trans_mgr->push(ev_icmp);
+
+ num_non_icmp_errors = 0;
+ } else {
+ // Even if an ICMP message is received this code can get
+ // executed. Sometimes the error is already present on
+ // the socket, but the ICMP message is not yet queued.
+ log_msg = "Failed to receive from SIP UDP socket.\n";
+ log_msg += "Error code: ";
+ log_msg += int2str(err);
+ log_msg += "\n";
+ log_msg += get_error_str(err);
+ log_file->write_report(log_msg, "::listen_udp");
+
+ num_non_icmp_errors++;
+
+ /*
+ * non-ICMP errors occur when a destination on the same
+ * subnet cannot be reached. So this code seems to be
+ * harmful.
+ if (num_non_icmp_errors > 100) {
+ log_msg = "Excessive number of socket errors.";
+ log_file->write_report(log_msg, "::listen_udp",
+ LOG_NORMAL, LOG_CRITICAL);
+ log_msg = TRANSLATE("Excessive number of socket errors.");
+ ui->cb_show_msg(log_msg, MSG_CRITICAL);
+ exit(1);
+ }
+ */
+ }
+
+ continue;
+ }
+
+ // Some SIP proxies send small keep alive packets to keep
+ // NAT bindings open. Discard such small packets as these
+ // are not SIP or STUN messages.
+ if (data_size < MIN_MESSAGE_SIZE) continue;
+
+ // Check if this is a STUN message
+ // The first byte of a STUN message is 0x00 or 0x01.
+ // A SIP message is ASCII so the first byte for SIP is
+ // never 0x00 or 0x01
+ if (buf[0] <= 1)
+ {
+ recvd_stun_msg(buf, data_size, src_addr, src_port);
+ continue;
+ }
+
+ // A SIP message may contain a NULL character (binary body),
+ // do not handle the buffer as a C string.
+ string datagram(buf, data_size);
+
+ // Split body from header
+ string seperator = string(CRLF) + string(CRLF);
+ pos_body = datagram.find(seperator);
+
+ // According to RFC 3261 syntax an empty line at
+ // the end of the headers is mandatory in all SIP messages.
+ // Here a missing empty line is accepted, but maybe
+ // the message should be discarded.
+ if (pos_body != string::npos) {
+ pos_body += seperator.size();
+ if (pos_body >= datagram.size()) {
+ // No body is present
+ pos_body = string::npos;
+ }
+ }
+
+ // Parse SIP headers
+ string raw_headers = datagram.substr(0, pos_body);
+ list<string> parse_errors;
+ try {
+ msg = t_parser::parse(raw_headers, parse_errors);
+ msg->src_ip_port.ipaddr = src_addr;
+ msg->src_ip_port.port = src_port;
+ msg->src_ip_port.transport = "udp";
+ }
+ 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(datagram);
+ log_msg += "\n";
+ log_file->write_report(log_msg, "::listen_udp", LOG_SIP, LOG_DEBUG);
+ continue;
+ }
+
+ // 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, "::listen_udp", LOG_SIP, LOG_DEBUG);
+ }
+
+ // Get raw body
+ string raw_body;
+ if (pos_body != string::npos) {
+ raw_body = datagram.substr(pos_body);
+ }
+
+ process_sip_msg(msg, raw_headers, raw_body);
+
+ MEMMAN_DELETE(msg);
+ delete msg;
+ }
+
+ log_file->write_report("UDP listener terminated.", "::listen_udp");
+ return NULL;
+}
+
+void *listen_for_data_tcp(void *arg) {
+ string log_msg;
+ list<t_connection *> readable_connections;
+
+ while(true) {
+ readable_connections.clear();
+ readable_connections = connection_table->select_read(NULL);
+
+ if (readable_connections.empty()) {
+ // Another thread cancelled the select command.
+ // Stop listening.
+ break;
+ }
+
+ // NOTE: The connection table is now locked.
+
+ for (list<t_connection *>::iterator it = readable_connections.begin();
+ it != readable_connections.end(); ++it)
+ {
+ string raw_headers;
+ string raw_body;
+ unsigned long remote_addr;
+ unsigned short remote_port;
+
+ (*it)->get_remote_address(remote_addr, remote_port);
+
+ try {
+ bool connection_closed;
+ (*it)->read(connection_closed);
+
+ if (connection_closed) {
+ log_msg = "Connection to ";
+ log_msg += h_ip2str(remote_addr);
+ log_msg += ":";
+ log_msg += int2str(remote_port);
+ log_msg += " closed.";
+ log_file->write_report(log_msg, "::listen_for_data_tcp", LOG_SIP, LOG_DEBUG);
+
+ connection_table->remove_connection(*it);
+ MEMMAN_DELETE(*it);
+ delete *it;
+ continue;
+ }
+ } catch (int err) {
+ if (err == EAGAIN || err == EINTR) {
+ continue;
+ }
+
+ log_msg = "Got error on socket to ";
+ log_msg += h_ip2str(remote_addr);
+ log_msg += ":";
+ log_msg += int2str(remote_port);
+ log_msg += " - ";
+ log_msg += get_error_str(err);
+ log_file->write_report(log_msg, "::listen_for_data_tcp", LOG_SIP, LOG_WARNING);
+
+ // Connection is broken.
+ // Signal the transaction layer that the connection is broken for
+ // all associated registered URI's.
+ const list<t_url> &uris = (*it)->get_registered_uri_set();
+ for (list<t_url>::const_iterator it_uri = uris.begin();
+ it_uri != uris.end(); ++it_uri)
+ {
+ evq_trans_layer->push_broken_connection(*it_uri);
+ }
+
+ // Remove the broken connection.
+ connection_table->remove_connection(*it);
+ MEMMAN_DELETE(*it);
+ delete *it;
+
+ continue;
+ }
+
+ // Multiple messages may have been read in one action.
+ // Get all SIP messages from the connection.
+ while (true)
+ {
+ bool error = false;
+ bool msg_too_large = false;
+ t_sip_message *msg = (*it)->get_sip_msg(raw_headers, raw_body, error,
+ msg_too_large);
+
+ if (error) {
+ // The data on the connection could not be interpreted.
+ // Close the connection to a faulty remote end.
+ connection_table->remove_connection(*it);
+ MEMMAN_DELETE(*it);
+ delete *it;
+
+ break;
+ }
+
+ if (msg_too_large) {
+ // Close the connection. The message was too long, we don't want
+ // to receive more data. Now we have an incomplete message,
+ // but as we most likely have all headers we can still
+ // send an error response, so the message is processed.
+ connection_table->remove_connection(*it);
+ MEMMAN_DELETE(*it);
+ delete *it;
+ }
+
+ if (!msg) {
+ // There are no complete messages on the connection.
+ // Stop reading from this connection.
+ break;
+ }
+
+ process_sip_msg(msg, raw_headers, raw_body);
+
+ MEMMAN_DELETE(msg);
+ delete msg;
+
+ if (msg_too_large) {
+ // The connection is closed already. Stop reading.
+ break;
+ }
+ }
+ }
+
+ connection_table->unlock();
+ }
+
+ log_file->write_report("TCP data listener terminated.", "::listen_for_data_tcp");
+
+ return NULL;
+}
+
+void *listen_for_conn_requests_tcp(void *arg) {
+ unsigned long dst_addr;
+ unsigned short dst_port;
+ string log_msg;
+
+ while (true) {
+ try {
+ sip_socket_tcp->listen(TCP_BACKLOG);
+ t_socket_tcp *tcp = sip_socket_tcp->accept(dst_addr, dst_port);
+ t_connection *conn = new t_connection(tcp);
+ MEMMAN_NEW(conn);
+ connection_table->add_connection(conn);
+ } catch (int err) {
+ if (err == EAGAIN || err == EINTR) continue;
+
+ log_file->write_header("::listen_for_conn_requests_tcp", LOG_SIP, LOG_CRITICAL);
+ log_file->write_raw("Error on accept on TCP socket: ");
+ log_file->write_raw(get_error_str(err));
+ log_file->write_endl();
+ log_file->write_footer();
+
+ log_msg = TRANSLATE("Cannot receive incoming TCP connections.");
+ ui->cb_show_msg(log_msg, MSG_CRITICAL);
+
+ break;
+ }
+ }
+
+ log_file->write_report("TCP connection listener terminated.", "::listen_for_conn_requests_tcp");
+ return NULL;
+}