src/modules/uid_auth_db/authorize.c
ae1479cc
 /*
  * Digest Authentication - Database support
  *
95072403
  * Copyright (C) 2001-2003 FhG Fokus
ae1479cc
  *
  * This file is part of ser, a free SIP server.
  *
  * ser 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
  *
  * For a license to use the ser software under conditions
  * other than those described here, or to purchase support for this
  * software, please contact iptel.org by e-mail at the following addresses:
  *    info@iptel.org
  *
  * ser 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.
  *
6eb9f46b
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
9e1ff448
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
ae1479cc
  *
  */
 
 
b61db5f9
 #include <stdio.h>
 #include <stdlib.h>
ae1479cc
 #include <string.h>
cf83221d
 #include "../../core/ut.h"
 #include "../../core/str.h"
7a7f1a7b
 #include "../../lib/srdb2/db.h"
cf83221d
 #include "../../core/dprint.h"
 #include "../../core/parser/digest/digest.h"
 #include "../../core/parser/hf.h"
 #include "../../core/parser/parser_f.h"
 #include "../../core/usr_avp.h"
 #include "../../core/mem/mem.h"
 #include "../../core/config.h"
 #include "../../core/id.h"
 #include "../../core/sr_module.h"
a9ca27b4
 #include "../../modules/auth/api.h"
0440bae1
 #include "uid_auth_db_mod.h"
ae1479cc
 
 
18b78285
 #define IS_NULL(f)	((f).flags & DB_NULL)
 
8ee734e1
 static inline int get_ha1(struct username* username, str* did, str* realm,
6eb9f46b
 		authdb_table_info_t *table_info, char* ha1, db_res_t** res, db_rec_t** row)
ae1479cc
 {
a1504b6b
 	str result;
18b78285
 	db_cmd_t *q = NULL;
6eb9f46b
 
18b78285
 	if (calc_ha1) {
 		q = table_info->query_password;
6eb9f46b
 		LM_DBG("querying plain password\n");
a1504b6b
 	} else {
 		if (username->domain.len) {
18b78285
 			q = table_info->query_pass2;
6eb9f46b
 			LM_DBG("querying ha1b\n");
a1504b6b
 		} else {
18b78285
 			q = table_info->query_pass;
6eb9f46b
 			LM_DBG("querying ha1\n");
18b78285
 		}
 	}
6eb9f46b
 
a1504b6b
 	q->match[0].v.lstr = username->user;
 	q->match[1].v.lstr = *realm;
6d7d9574
 
aa72024a
 	if (use_did) q->match[2].v.lstr = *did;
18b78285
 
 	if (db_exec(res, q) < 0 ) {
6eb9f46b
 		LM_ERR("Error while querying database\n");
3258e63c
 		return -1;
ae1479cc
 	}
18b78285
 
 	if (*res) *row = db_first(*res);
 	else *row = NULL;
 	while (*row) {
 		if (IS_NULL((*row)->fld[0]) || IS_NULL((*row)->fld[1])) {
6eb9f46b
 			LM_ERR("Credentials for '%.*s'@'%.*s' contain NULL value,"
 					" skipping\n",
 					username->user.len, ZSW(username->user.s),
 					realm->len, ZSW(realm->s));
a1504b6b
 		} else {
7a7f1a7b
 			if ((*row)->fld[1].v.int4 & SRDB_DISABLED) {
18b78285
 				/* disabled rows ignored */
a1504b6b
 			} else {
7a7f1a7b
 				if ((*row)->fld[1].v.int4 & SRDB_LOAD_SER) {
18b78285
 					/* *row = i; */
 					break;
 				}
 			}
 		}
 		*row = db_next(*res);
ae1479cc
 	}
18b78285
 
 	if (!*row) {
6eb9f46b
 		LM_DBG("Credentials for '%.*s'@'%.*s' not found\n",
 				username->user.len, ZSW(username->user.s),
 				realm->len, ZSW(realm->s));
18b78285
 		return 1;
6eb9f46b
 	}
18b78285
 
 	result.s = (*row)->fld[0].v.cstr;
 	result.len = strlen(result.s);
 
 	if (calc_ha1) {
 		/* Only plaintext passwords are stored in database,
 		 * we have to calculate HA1 */
1babd807
 		auth_api.calc_HA1(HA_MD5, &username->whole, realm, &result, 0, 0, ha1);
6eb9f46b
 		LM_DBG("HA1 string calculated: %s\n", ha1);
18b78285
 	} else {
 		memcpy(ha1, result.s, result.len);
 		ha1[result.len] = '\0';
 	}
 
 	return 0;
ae1479cc
 }
 
 /*
  * Calculate the response and compare with the given response string
95072403
  * Authorization is successful if this two strings are same
ae1479cc
  */
c1954da6
 static inline int check_response(dig_cred_t* cred, str* method, char* ha1)
ae1479cc
 {
a1504b6b
 	HASHHEX resp, hent;
6eb9f46b
 
a1504b6b
 	/*
 	 * First, we have to verify that the response received has
 	 * the same length as responses created by us
 	 */
 	if (cred->response.len != 32) {
6eb9f46b
 		LM_DBG("Receive response len != 32\n");
a1504b6b
 		return 1;
 	}
6eb9f46b
 
a1504b6b
 	/*
 	 * Now, calculate our response from parameters received
 	 * from the user agent
 	 */
6eb9f46b
 	auth_api.calc_response(ha1, &(cred->nonce),
 			&(cred->nc), &(cred->cnonce),
 			&(cred->qop.qop_str), cred->qop.qop_parsed == QOP_AUTHINT,
 			method, &(cred->uri), hent, resp);
 
 	LM_DBG("Our result = \'%s\'\n", resp);
 
a1504b6b
 	/*
 	 * And simply compare the strings, the user is
 	 * authorized if they match
 	 */
 	if (!memcmp(resp, cred->response.s, 32)) {
6eb9f46b
 		LM_DBG("Authorization is OK\n");
a1504b6b
 		return 0;
 	} else {
6eb9f46b
 		LM_DBG("Authorization failed\n");
a1504b6b
 		return 2;
 	}
ae1479cc
 }
 
 
55a0ab79
 /*
  * Generate AVPs from the database result
  */
18b78285
 static int generate_avps(db_res_t* result, db_rec_t *row)
55a0ab79
 {
a1504b6b
 	int i;
 	int_str iname, ivalue;
 	str value;
 	char buf[32];
6eb9f46b
 
18b78285
 	for (i = 2; i < credentials_n + 2; i++) {
032bb449
 		value = row->fld[i].v.lstr;
18b78285
 
b61db5f9
 		if (IS_NULL(row->fld[i]))
18b78285
 			continue;
b61db5f9
 
 		switch (row->fld[i].type) {
6eb9f46b
 			case DB_STR:
 				value = row->fld[i].v.lstr;
 				break;
 
 			case DB_INT:
 				value.len = sprintf(buf, "%d", row->fld[i].v.int4);
 				value.s = buf;
 				break;
 
 			default:
 				abort();
 				break;
18b78285
 		}
 
b61db5f9
 		if (value.s == NULL)
a1504b6b
 			continue;
b61db5f9
 
18b78285
 		iname.s = credentials[i - 2];
 		ivalue.s = value;
 
6eb9f46b
 		if (add_avp(AVP_NAME_STR | AVP_VAL_STR | AVP_CLASS_USER,
 					iname, ivalue) < 0) {
 			LM_ERR("Error while creating AVPs\n");
18b78285
 			return -1;
 		}
 
6eb9f46b
 		LM_DBG("set string AVP \'%.*s = %.*s\'\n",
 				iname.s.len, ZSW(iname.s.s), value.len, ZSW(value.s));
8ee734e1
 	}
6eb9f46b
 
a1504b6b
 	return 0;
55a0ab79
 }
 
1babd807
 /* this is a dirty work around to check the credentials of all users,
  * if the database query returned more then one result
  *
f72c5743
  * Fills res (which must be db_free'd afterwards if the call was successful)
1babd807
  * returns  0 on success, 1 on no match (?)
  *          and -1 on error (memory, db a.s.o).
  * WARNING: if -1 is returned res _must_ _not_ be freed (it's empty)
  *
  */
6eb9f46b
 static inline int check_all_ha1(struct sip_msg* msg, struct hdr_field* hdr,
 		dig_cred_t* dig, str* method, str* did, str* realm,
 		authdb_table_info_t *table_info, db_res_t** res)
1babd807
 {
 	char ha1[256];
 	db_rec_t *row;
 	str result;
 	db_cmd_t *q;
6eb9f46b
 
1babd807
 	if (calc_ha1) {
 		q = table_info->query_password;
6eb9f46b
 		LM_DBG("querying plain password\n");
1babd807
 	}
 	else {
6eb9f46b
 		if (dig->username.domain.len) {
1babd807
 			q = table_info->query_pass2;
6eb9f46b
 			LM_DBG("querying ha1b\n");
1babd807
 		}
 		else {
 			q = table_info->query_pass;
6eb9f46b
 			LM_DBG("querying ha1\n");
1babd807
 		}
 	}
6eb9f46b
 
1babd807
 	q->match[0].v.lstr = dig->username.user;
6eb9f46b
 	if (dig->username.domain.len)
1babd807
 		q->match[1].v.lstr = dig->username.domain;
 	else
 		q->match[1].v.lstr = *realm;
 
 	if (use_did) q->match[2].v.lstr = *did;
 
 	if (db_exec(res, q) < 0 ) {
6eb9f46b
 		LM_ERR("Error while querying database\n");
1babd807
 	}
 
 	if (*res) row = db_first(*res);
 	else row = NULL;
 	while (row) {
 		if (IS_NULL(row->fld[0]) || IS_NULL(row->fld[1])) {
6eb9f46b
 			LM_ERR("Credentials for '%.*s'@'%.*s' contain NULL value,"
 					" skipping\n",
 					dig->username.user.len, ZSW(dig->username.user.s),
 					realm->len, ZSW(realm->s));
1babd807
 		}
 		else {
7a7f1a7b
 			if (row->fld[1].v.int4 & SRDB_DISABLED) {
1babd807
 				/* disabled rows ignored */
 			}
 			else {
7a7f1a7b
 				if (row->fld[1].v.int4 & SRDB_LOAD_SER) {
1babd807
 					result.s = row->fld[0].v.cstr;
 					result.len = strlen(result.s);
 					if (calc_ha1) {
6eb9f46b
 						/* Only plaintext passwords are stored in database,
 						 * we have to calculate HA1 */
 						auth_api.calc_HA1(HA_MD5, &(dig->username.whole),
 								realm, &result, 0, 0, ha1);
 						LM_DBG("HA1 string calculated: %s\n", ha1);
1babd807
 					} else {
 						memcpy(ha1, result.s, result.len);
 						ha1[result.len] = '\0';
 					}
 
 					if (!check_response(dig, method, ha1)) {
6eb9f46b
 						if (auth_api.post_auth(msg, hdr, ha1)
 								== AUTHENTICATED) {
1babd807
 							generate_avps(*res, row);
 							return 0;
 						}
 					}
 				}
 			}
 		}
 		row = db_next(*res);
 	}
 
 	if (!row) {
6eb9f46b
 		LM_DBG("Credentials for '%.*s'@'%.*s' not found",
 				dig->username.user.len, ZSW(dig->username.user.s),
 				realm->len, ZSW(realm->s));
 	}
1babd807
 	return 1;
 
 
 }
 
55a0ab79
 
ae1479cc
 /*
c1954da6
  * Authenticate digest credentials
8ee734e1
  * Returns:
  *      -3 -- Bad Request
  *      -2 -- Error while checking credentials (such as malformed message or database problem)
  *      -1 -- Authentication failed
  *       1 -- Authentication successful
ae1479cc
  */
6eb9f46b
 static inline int authenticate(struct sip_msg* msg, str* realm,
 		authdb_table_info_t *table, hdr_types_t hftype)
ae1479cc
 {
a1504b6b
 	char ha1[256];
 	int res, ret;
18b78285
 	db_rec_t *row;
a1504b6b
 	struct hdr_field* h;
 	auth_body_t* cred;
 	db_res_t* result;
 	str did;
6eb9f46b
 
a1504b6b
 	cred = 0;
 	result = 0;
 	ret = -1;
6eb9f46b
 
1babd807
 	switch(auth_api.pre_auth(msg, realm, hftype, &h, NULL)) {
6eb9f46b
 		case NONCE_REUSED:
 			LM_DBG("nonce reused");
 			ret = AUTH_NONCE_REUSED;
 			goto end;
 		case STALE_NONCE:
 			LM_DBG("stale nonce\n");
 			ret = AUTH_STALE_NONCE;
 			goto end;
 		case NO_CREDENTIALS:
 			LM_DBG("no credentials\n");
 			ret = AUTH_NO_CREDENTIALS;
 			goto end;
 		case ERROR:
 		case BAD_CREDENTIALS:
 			ret = -3;
 			goto end;
 		case CREATE_CHALLENGE:
 			LM_ERR("CREATE_CHALLENGE is not a valid state\n");
 			ret = -2;
 			goto end;
 		case DO_RESYNCHRONIZATION:
 			LM_ERR("DO_RESYNCHRONIZATION is not a valid state\n");
 			ret = -2;
 			goto end;
 
 		case NOT_AUTHENTICATED:
 			ret = -1;
 			goto end;
 
 		case DO_AUTHENTICATION:
 			break;
 
 		case AUTHENTICATED:
 			ret = 1;
 			goto end;
a1504b6b
 	}
6eb9f46b
 
a1504b6b
 	cred = (auth_body_t*)h->parsed;
6eb9f46b
 
a1504b6b
 	if (use_did) {
3258e63c
 		if (msg->REQ_METHOD == METHOD_REGISTER) {
 			ret = get_to_did(&did, msg);
 		} else {
 			ret = get_from_did(&did, msg);
 		}
 		if (ret == 0) {
 			did.s = DEFAULT_DID;
 			did.len = sizeof(DEFAULT_DID) - 1;
 		}
a1504b6b
 	} else {
3258e63c
 		did.len = 0;
 		did.s = 0;
6eb9f46b
 	}
 
1babd807
 
 	if (check_all) {
6eb9f46b
 		res = check_all_ha1(msg, h, &(cred->digest),
 				&msg->first_line.u.request.method, &did, realm, table, &result);
1babd807
 		if (res < 0) {
 			ret = -2;
 			goto end;
 		}
 		else if (res > 0) {
 			ret = -1;
 			goto end;
 		}
 		else {
 			ret = 1;
 			goto end;
 		}
6eb9f46b
 	} else {
 		res = get_ha1(&cred->digest.username, &did, realm, table, ha1,
 				&result, &row);
1babd807
 		if (res < 0) {
 			ret = -2;
 			goto end;
 		}
 		if (res > 0) {
 			/* Username not found in the database */
 			ret = -1;
 			goto end;
6eb9f46b
 		}
 	}
 
3258e63c
 	/* Recalculate response, it must be same to authorize successfully */
6eb9f46b
 	if (!check_response(&(cred->digest), &msg->first_line.u.request.method,
 				ha1)) {
2a91141e
 		switch(auth_api.post_auth(msg, h, ha1)) {
6eb9f46b
 			case ERROR:
 			case BAD_CREDENTIALS:
 				ret = -2;
 				break;
 
 			case NOT_AUTHENTICATED:
 				ret = -1;
 				break;
 
 			case AUTHENTICATED:
 				generate_avps(result, row);
 				ret = 1;
 				break;
 
 			default:
 				ret = -1;
 				break;
3258e63c
 		}
a1504b6b
 	} else {
b1839f87
 		ret = -1;
 	}
a1504b6b
 
6eb9f46b
 end:
a1504b6b
 	if (result) db_res_free(result);
 	if (ret < 0) {
6eb9f46b
 		if (auth_api.build_challenge(msg, (cred ? cred->stale : 0), realm,
 					NULL, NULL, hftype) < 0) {
 			LM_ERR("Error while creating challenge\n");
3258e63c
 			ret = -2;
 		}
a1504b6b
 	}
 	return ret;
ae1479cc
 }
 
 
c1954da6
 /*
  * Authenticate using Proxy-Authorize header field
  */
cb83a7cb
 int proxy_authenticate(struct sip_msg* msg, char* p1, char* p2)
c1954da6
 {
18b78285
 	str realm;
c1954da6
 
18b78285
 	if (get_str_fparam(&realm, msg, (fparam_t*)p1) < 0) {
6eb9f46b
 		LM_ERR("Cannot obtain digest realm from parameter '%s'\n",
 				((fparam_t*)p1)->orig);
18b78285
 		return -1;
 	}
c1954da6
 
18b78285
 	return authenticate(msg, &realm, (authdb_table_info_t*)p2, HDR_PROXYAUTH_T);
c1954da6
 }
 
 
ae1479cc
 /*
cb83a7cb
  * Authorize using WWW-Authorize header field
ae1479cc
  */
cb83a7cb
 int www_authenticate(struct sip_msg* msg, char* p1, char* p2)
ae1479cc
 {
a1504b6b
 	str realm;
ae1479cc
 
18b78285
 	if (get_str_fparam(&realm, msg, (fparam_t*)p1) < 0) {
6eb9f46b
 		LM_ERR("Cannot obtain digest realm from parameter '%s'\n",
 				((fparam_t*)p1)->orig);
18b78285
 		return -1;
 	}
ae1479cc
 
6eb9f46b
 	return authenticate(msg, &realm, (authdb_table_info_t*)p2,
 			HDR_AUTHORIZATION_T);
ae1479cc
 }