/* Copyright (C) 2005-2009 Michel de Boer 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, see . */ #include #include #include #include #include #include #include #include #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 gethostbyname_all(const string &name) { struct hostent *h; list 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 param_lst = split(param_str, ';'); // Parse the parameters for (vector::iterator i = param_lst.begin(); i != param_lst.end(); i++) { string pname; string pvalue; vector 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 t_url::get_h_ip_all(void) const { if (scheme == "tel") return list(); 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_url::get_h_ip_srv(const string &transport) const { list ip_list; list srv_list; list ipaddr_list; if (scheme == "tel") return list(); // 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::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::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::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; }