/*
Copyright (C) 2005-2007 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 "request.h"
#include "util.h"
#include "parse_ctrl.h"
#include "protocol.h"
#include "audits/memman.h"
#include <sstream>
#include <cc++/digest.h>
using namespace ost;
bool t_request::authorize(const t_challenge &chlg,
const string &username, const string &passwd, unsigned long nc,
const string &cnonce, t_credentials &cr, string &fail_reason) const
{
string A1, A2;
string qop = "";
// Only Digest authentication is supported
if (chlg.auth_scheme != AUTH_DIGEST) {
fail_reason = "Authentication scheme " + chlg.auth_scheme;
fail_reason += " not supported.";
return false;
}
const t_digest_challenge &dchlg = chlg.digest_challenge;
// Only MD5 algorithm is supported
if (dchlg.algorithm != ALG_MD5) {
fail_reason = "Authentication algorithm " + dchlg.algorithm;
fail_reason += " not supported.";
return false;
}
// Determine QOP
if (!dchlg.qop_options.empty()) {
const list<string>::const_iterator i = find(
dchlg.qop_options.begin(), dchlg.qop_options.end(),
QOP_AUTH_INT);
const list<string>::const_iterator j = find(
dchlg.qop_options.begin(), dchlg.qop_options.end(),
QOP_AUTH);
if (i != dchlg.qop_options.end())
qop = QOP_AUTH_INT;
else {
if (j != dchlg.qop_options.end())
qop = QOP_AUTH;
else {
fail_reason = "Non of the qop values are supported.";
return false;
}
}
}
// RFC 2617 3.2.2.2
A1 = username + ":" + dchlg.realm + ":" + passwd;
// RFC 2617 3.2.2.3
if (qop == QOP_AUTH || qop == "") {
A2 = method2str(method, unknown_method) + ":" + uri.encode();
} else {
A2 = method2str(method, unknown_method) + ":" + uri.encode();
A2 += ":";
if (body) {
MD5Digest MD5body;
MD5body << body->encode();
ostringstream os;
os << MD5body;
A2 += os.str();
} else {
MD5Digest MD5body;
MD5body << "";
ostringstream os;
os << MD5body;
A2 += os.str();
}
}
// RFC 2716 3.2.2.1
// Caculate digest
MD5Digest MD5A1;
MD5Digest MD5A2;
ostringstream HA1;
ostringstream HA2;
MD5A1 << A1;
MD5A2 << A2;
HA1 << MD5A1;
HA2 << MD5A2;
string x;
if (qop == QOP_AUTH || qop == QOP_AUTH_INT) {
x = HA1.str() + ":";
x += dchlg.nonce + ":";
x += int2str(nc, "%08x") + ":";
x += cnonce + ":";
x += qop + ":";
x += HA2.str();
} else {
x = HA1.str() + ":";
x += dchlg.nonce + ":";
x += HA2.str();
}
// Create credentials
cr.auth_scheme = AUTH_DIGEST;
t_digest_response &dr = cr.digest_response;
MD5Digest digest;
digest << x;
ostringstream dresp;
dresp << digest;
dr.dresponse = dresp.str();
dr.username = username;
dr.realm = dchlg.realm;
dr.nonce = dchlg.nonce;
dr.digest_uri = uri;
dr.algorithm = ALG_MD5;
dr.opaque = dchlg.opaque;
// RFC 2617 3.2.2
if (qop != "") {
dr.message_qop = qop;
dr.cnonce = cnonce;
dr.nonce_count = nc;
}
return true;
}
t_request::t_request() : t_sip_message() {
method = METHOD_UNKNOWN;
}
t_request::t_request(const t_request &r) : t_sip_message(r),
uri(r.uri),
method(r.method),
unknown_method(r.unknown_method),
destinations(r.destinations)
{
}
t_request::t_request(const t_method m) : t_sip_message() {
method = m;
}
void t_request::set_method(const string &s) {
method = str2method(s);
if (method == METHOD_UNKNOWN) {
unknown_method = s;
}
}
string t_request::encode(bool add_content_length) {
string s;
s = method2str(method, unknown_method) + ' ' + uri.encode();
s += " SIP/";
s += version;
s += CRLF;
s += t_sip_message::encode(add_content_length);
return s;
}
list<string> t_request::encode_env(void) {
string s;
list<string> l = t_sip_message::encode_env();
s = "SIPREQUEST_METHOD=";
s += method2str(method, unknown_method);
l.push_back(s);
s = "SIPREQUEST_URI=";
s += uri.encode();
l.push_back(s);
return l;
}
t_sip_message *t_request::copy(void) const {
t_sip_message *m = new t_request(*this);
MEMMAN_NEW(m);
return m;
}
t_response *t_request::create_response(int code, string reason) const
{
t_response *r;
r = new t_response(code, reason);
MEMMAN_NEW(r);
r->hdr_from = hdr_from;
r->hdr_call_id = hdr_call_id;
r->hdr_cseq = hdr_cseq;
r->hdr_via = hdr_via;
r->hdr_to = hdr_to;
// Create a to-tag if none was present in the request
// NOTE: 100 Trying should not get a to-tag
if (hdr_to.tag.size() == 0 && code != R_100_TRYING) {
r->hdr_to.set_tag(NEW_TAG);
}
// Server
SET_HDR_SERVER(r->hdr_server);
return r;
}
bool t_request::is_valid(bool &fatal, string &reason) const {
if (!t_sip_message::is_valid(fatal, reason)) return false;
fatal = false;
if (t_parser::check_max_forwards && !hdr_max_forwards.is_populated()) {
reason = "Max-Forwards header missing";
return false;
}
switch(method) {
case INVITE:
if (!hdr_contact.is_populated()) {
reason = "Contact header missing";
return false;
}
break;
case PRACK:
// RFC 3262 7.1
if (!hdr_rack.is_populated()) {
reason = "RAck header missing";
return false;
}
break;
case SUBSCRIBE:
// RFC 3265 7.1, 7.2
if (!hdr_contact.is_populated()) {
reason = "Contact header missing";
return false;
}
if (!hdr_event.is_populated()) {
reason = "Event header missing";
return false;
}
break;
case NOTIFY:
// RFC 3265 7.1, 7.2
if (!hdr_contact.is_populated()) {
reason = "Contact header missing";
return false;
}
if (!hdr_event.is_populated()) {
reason = "Event header missing";
return false;
}
// RFC 3265 7.2
// Subscription-State header is mandatory
// As an exception Twinkle allows an unsollicited NOTIFY for MWI
// without a Subscription-State header. Asterisk sends
// unsollicited NOTIFY requests.
if (!hdr_to.tag.empty() ||
hdr_event.event_type != SIP_EVENT_MSG_SUMMARY)
{
if (!hdr_subscription_state.is_populated()) {
reason = "Subscription-State header missing";
return false;
}
}
// The Subscription-State header is mandatory.
// However, Asterisk uses an expired draft for sending
// unsollicitied NOTIFY messages without a Subscription-State
// header. As Asterisk is popular, Twinkle allows this.
break;
case REFER:
// RFC 3515 2.4.1
if (!hdr_refer_to.is_populated()) {
reason = "Refer-To header missing";
return false;
}
break;
}
if (hdr_replaces.is_populated()) {
// RFC 3891 3
if (method != INVITE) {
reason = "Replaces header not allowed";
return false;
}
}
return true;
}
void t_request::calc_destinations(const t_user &user_profile) {
destinations.clear();
// Send a REGISTER to the registrar if provisioned.
if (method == REGISTER && user_profile.get_use_registrar()) {
destinations = user_profile.get_registrar().get_h_ip_srv("udp");
return;
}
// Bypass the proxy for an out-of-dialog SUBSCRIBE if provisioned.
if (method == SUBSCRIBE && hdr_to.tag.empty()) {
if (hdr_event.event_type == SIP_EVENT_MSG_SUMMARY) {
if (!user_profile.get_mwi_via_proxy()) {
// Take Request-URI
destinations = uri.get_h_ip_srv("udp");
return;
}
}
}
if (!user_profile.get_use_outbound_proxy() ||
(hdr_to.tag != "" && !user_profile.get_all_requests_to_proxy())) {
// A mid dialog request will go to the host in the contact
// header (put in the request-URI in this request) or route list
// specified in the final response of the invite (the Route-header in
// this request).
// Note that an ACK for a failed INVITE (3XX-6XX) will be
// sent by the transaction layer to the ipaddr/port of the
// INVITE.
if (hdr_route.is_populated() && hdr_route.route_to_first_route) {
// Take URI from first route-header
t_url &u = hdr_route.route_list.front().uri;
destinations = u.get_h_ip_srv("udp");
} else {
// Take Request-URI
destinations = uri.get_h_ip_srv("udp");
}
}
// Send request to outbound proxy if configured
if (user_profile.get_use_outbound_proxy()) {
if (user_profile.get_non_resolvable_to_proxy() && !destinations.empty())
{
// The destination has been resolved, so do not
// use the outbound proxy in this case.
return;
}
if (user_profile.get_all_requests_to_proxy() || hdr_to.tag == "") {
// All requests should go to the proxy.
// Override destination by the outbound proxy address.
destinations = user_profile.get_outbound_proxy().get_h_ip_srv("udp");
}
}
}
void t_request::get_destination(unsigned long &ipaddr, unsigned short &port,
const t_user &user_profile)
{
if (destinations.empty()) calc_destinations(user_profile);
get_current_destination(ipaddr, port);
}
void t_request::get_current_destination(unsigned long &ipaddr, unsigned short &port) {
if (destinations.empty()) {
// No destinations could be found.
ipaddr =0;
port = 0;
} else {
// Return first destination
ipaddr = destinations.front().ipaddr;
port = destinations.front().port;
}
}
bool t_request::next_destination(void) {
if (destinations.size() <= 1) return false;
// Remove current destination
destinations.pop_front();
return true;
}
void t_request::set_destination(unsigned long ipaddr, unsigned short port) {
destinations.clear();
destinations.push_back(t_ip_port(ipaddr, port));
}
bool t_request::www_authorize(const t_challenge &chlg, const string &username,
const string &passwd, unsigned long nc,
const string &cnonce, t_credentials &cr, string &fail_reason)
{
if (!authorize(chlg, username, passwd, nc, cnonce, cr, fail_reason)) {
return false;
}
hdr_authorization.add_credentials(cr);
return true;
}
bool t_request::proxy_authorize(const t_challenge &chlg, const string &username,
const string &passwd, unsigned long nc,
const string &cnonce, t_credentials &cr, string &fail_reason)
{
if (!authorize(chlg, username, passwd, nc, cnonce, cr, fail_reason)) {
return false;
}
hdr_proxy_authorization.add_credentials(cr);
return true;
}
syntax highlighted by Code2HTML, v. 0.9.1