1 /*
2  * Digest Authentication - Database support
3  *
4  * Copyright (C) 2001-2003 FhG Fokus
5  *
6  * This file is part of ser, a free SIP server.
7  *
8  * ser is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version
12  *
13  * For a license to use the ser software under conditions
14  * other than those described here, or to purchase support for this
15  * software, please contact iptel.org by e-mail at the following addresses:
16  *    info@iptel.org
17  *
18  * ser is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
26  *
27  */
28 
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include "../../core/ut.h"
34 #include "../../core/str.h"
35 #include "../../lib/srdb2/db.h"
36 #include "../../core/dprint.h"
37 #include "../../core/parser/digest/digest.h"
38 #include "../../core/parser/hf.h"
39 #include "../../core/parser/parser_f.h"
40 #include "../../core/usr_avp.h"
41 #include "../../core/mem/mem.h"
42 #include "../../core/config.h"
43 #include "../../core/id.h"
44 #include "../../core/sr_module.h"
45 #include "../../modules/auth/api.h"
46 #include "uid_auth_db_mod.h"
47 
48 
49 #define IS_NULL(f)	((f).flags & DB_NULL)
50 
get_ha1(struct username * username,str * did,str * realm,authdb_table_info_t * table_info,char * ha1,db_res_t ** res,db_rec_t ** row)51 static inline int get_ha1(struct username* username, str* did, str* realm,
52 		authdb_table_info_t *table_info, char* ha1, db_res_t** res, db_rec_t** row)
53 {
54 	str result;
55 	db_cmd_t *q = NULL;
56 
57 	if (calc_ha1) {
58 		q = table_info->query_password;
59 		LM_DBG("querying plain password\n");
60 	} else {
61 		if (username->domain.len) {
62 			q = table_info->query_pass2;
63 			LM_DBG("querying ha1b\n");
64 		} else {
65 			q = table_info->query_pass;
66 			LM_DBG("querying ha1\n");
67 		}
68 	}
69 
70 	q->match[0].v.lstr = username->user;
71 	q->match[1].v.lstr = *realm;
72 
73 	if (use_did) q->match[2].v.lstr = *did;
74 
75 	if (db_exec(res, q) < 0 ) {
76 		LM_ERR("Error while querying database\n");
77 		return -1;
78 	}
79 
80 	if (*res) *row = db_first(*res);
81 	else *row = NULL;
82 	while (*row) {
83 		if (IS_NULL((*row)->fld[0]) || IS_NULL((*row)->fld[1])) {
84 			LM_ERR("Credentials for '%.*s'@'%.*s' contain NULL value,"
85 					" skipping\n",
86 					username->user.len, ZSW(username->user.s),
87 					realm->len, ZSW(realm->s));
88 		} else {
89 			if ((*row)->fld[1].v.int4 & SRDB_DISABLED) {
90 				/* disabled rows ignored */
91 			} else {
92 				if ((*row)->fld[1].v.int4 & SRDB_LOAD_SER) {
93 					/* *row = i; */
94 					break;
95 				}
96 			}
97 		}
98 		*row = db_next(*res);
99 	}
100 
101 	if (!*row) {
102 		LM_DBG("Credentials for '%.*s'@'%.*s' not found\n",
103 				username->user.len, ZSW(username->user.s),
104 				realm->len, ZSW(realm->s));
105 		return 1;
106 	}
107 
108 	result.s = (*row)->fld[0].v.cstr;
109 	result.len = strlen(result.s);
110 
111 	if (calc_ha1) {
112 		/* Only plaintext passwords are stored in database,
113 		 * we have to calculate HA1 */
114 		auth_api.calc_HA1(HA_MD5, &username->whole, realm, &result, 0, 0, ha1);
115 		LM_DBG("HA1 string calculated: %s\n", ha1);
116 	} else {
117 		memcpy(ha1, result.s, result.len);
118 		ha1[result.len] = '\0';
119 	}
120 
121 	return 0;
122 }
123 
124 /*
125  * Calculate the response and compare with the given response string
126  * Authorization is successful if this two strings are same
127  */
check_response(dig_cred_t * cred,str * method,char * ha1)128 static inline int check_response(dig_cred_t* cred, str* method, char* ha1)
129 {
130 	HASHHEX resp, hent;
131 
132 	/*
133 	 * First, we have to verify that the response received has
134 	 * the same length as responses created by us
135 	 */
136 	if (cred->response.len != 32) {
137 		LM_DBG("Receive response len != 32\n");
138 		return 1;
139 	}
140 
141 	/*
142 	 * Now, calculate our response from parameters received
143 	 * from the user agent
144 	 */
145 	auth_api.calc_response(ha1, &(cred->nonce),
146 			&(cred->nc), &(cred->cnonce),
147 			&(cred->qop.qop_str), cred->qop.qop_parsed == QOP_AUTHINT,
148 			method, &(cred->uri), hent, resp);
149 
150 	LM_DBG("Our result = \'%s\'\n", resp);
151 
152 	/*
153 	 * And simply compare the strings, the user is
154 	 * authorized if they match
155 	 */
156 	if (!memcmp(resp, cred->response.s, 32)) {
157 		LM_DBG("Authorization is OK\n");
158 		return 0;
159 	} else {
160 		LM_DBG("Authorization failed\n");
161 		return 2;
162 	}
163 }
164 
165 
166 /*
167  * Generate AVPs from the database result
168  */
generate_avps(db_res_t * result,db_rec_t * row)169 static int generate_avps(db_res_t* result, db_rec_t *row)
170 {
171 	int i;
172 	int_str iname, ivalue;
173 	str value;
174 	char buf[32];
175 
176 	for (i = 2; i < credentials_n + 2; i++) {
177 		value = row->fld[i].v.lstr;
178 
179 		if (IS_NULL(row->fld[i]))
180 			continue;
181 
182 		switch (row->fld[i].type) {
183 			case DB_STR:
184 				value = row->fld[i].v.lstr;
185 				break;
186 
187 			case DB_INT:
188 				value.len = sprintf(buf, "%d", row->fld[i].v.int4);
189 				value.s = buf;
190 				break;
191 
192 			default:
193 				abort();
194 				break;
195 		}
196 
197 		if (value.s == NULL)
198 			continue;
199 
200 		iname.s = credentials[i - 2];
201 		ivalue.s = value;
202 
203 		if (add_avp(AVP_NAME_STR | AVP_VAL_STR | AVP_CLASS_USER,
204 					iname, ivalue) < 0) {
205 			LM_ERR("Error while creating AVPs\n");
206 			return -1;
207 		}
208 
209 		LM_DBG("set string AVP \'%.*s = %.*s\'\n",
210 				iname.s.len, ZSW(iname.s.s), value.len, ZSW(value.s));
211 	}
212 
213 	return 0;
214 }
215 
216 /* this is a dirty work around to check the credentials of all users,
217  * if the database query returned more then one result
218  *
219  * Fills res (which must be db_free'd afterwards if the call was succesfull)
220  * returns  0 on success, 1 on no match (?)
221  *          and -1 on error (memory, db a.s.o).
222  * WARNING: if -1 is returned res _must_ _not_ be freed (it's empty)
223  *
224  */
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)225 static inline int check_all_ha1(struct sip_msg* msg, struct hdr_field* hdr,
226 		dig_cred_t* dig, str* method, str* did, str* realm,
227 		authdb_table_info_t *table_info, db_res_t** res)
228 {
229 	char ha1[256];
230 	db_rec_t *row;
231 	str result;
232 	db_cmd_t *q;
233 
234 	if (calc_ha1) {
235 		q = table_info->query_password;
236 		LM_DBG("querying plain password\n");
237 	}
238 	else {
239 		if (dig->username.domain.len) {
240 			q = table_info->query_pass2;
241 			LM_DBG("querying ha1b\n");
242 		}
243 		else {
244 			q = table_info->query_pass;
245 			LM_DBG("querying ha1\n");
246 		}
247 	}
248 
249 	q->match[0].v.lstr = dig->username.user;
250 	if (dig->username.domain.len)
251 		q->match[1].v.lstr = dig->username.domain;
252 	else
253 		q->match[1].v.lstr = *realm;
254 
255 	if (use_did) q->match[2].v.lstr = *did;
256 
257 	if (db_exec(res, q) < 0 ) {
258 		LM_ERR("Error while querying database\n");
259 	}
260 
261 	if (*res) row = db_first(*res);
262 	else row = NULL;
263 	while (row) {
264 		if (IS_NULL(row->fld[0]) || IS_NULL(row->fld[1])) {
265 			LM_ERR("Credentials for '%.*s'@'%.*s' contain NULL value,"
266 					" skipping\n",
267 					dig->username.user.len, ZSW(dig->username.user.s),
268 					realm->len, ZSW(realm->s));
269 		}
270 		else {
271 			if (row->fld[1].v.int4 & SRDB_DISABLED) {
272 				/* disabled rows ignored */
273 			}
274 			else {
275 				if (row->fld[1].v.int4 & SRDB_LOAD_SER) {
276 					result.s = row->fld[0].v.cstr;
277 					result.len = strlen(result.s);
278 					if (calc_ha1) {
279 						/* Only plaintext passwords are stored in database,
280 						 * we have to calculate HA1 */
281 						auth_api.calc_HA1(HA_MD5, &(dig->username.whole),
282 								realm, &result, 0, 0, ha1);
283 						LM_DBG("HA1 string calculated: %s\n", ha1);
284 					} else {
285 						memcpy(ha1, result.s, result.len);
286 						ha1[result.len] = '\0';
287 					}
288 
289 					if (!check_response(dig, method, ha1)) {
290 						if (auth_api.post_auth(msg, hdr, ha1)
291 								== AUTHENTICATED) {
292 							generate_avps(*res, row);
293 							return 0;
294 						}
295 					}
296 				}
297 			}
298 		}
299 		row = db_next(*res);
300 	}
301 
302 	if (!row) {
303 		LM_DBG("Credentials for '%.*s'@'%.*s' not found",
304 				dig->username.user.len, ZSW(dig->username.user.s),
305 				realm->len, ZSW(realm->s));
306 	}
307 	return 1;
308 
309 
310 }
311 
312 
313 /*
314  * Authenticate digest credentials
315  * Returns:
316  *      -3 -- Bad Request
317  *      -2 -- Error while checking credentials (such as malformed message or database problem)
318  *      -1 -- Authentication failed
319  *       1 -- Authentication successful
320  */
authenticate(struct sip_msg * msg,str * realm,authdb_table_info_t * table,hdr_types_t hftype)321 static inline int authenticate(struct sip_msg* msg, str* realm,
322 		authdb_table_info_t *table, hdr_types_t hftype)
323 {
324 	char ha1[256];
325 	int res, ret;
326 	db_rec_t *row;
327 	struct hdr_field* h;
328 	auth_body_t* cred;
329 	db_res_t* result;
330 	str did;
331 
332 	cred = 0;
333 	result = 0;
334 	ret = -1;
335 
336 	switch(auth_api.pre_auth(msg, realm, hftype, &h, NULL)) {
337 		case NONCE_REUSED:
338 			LM_DBG("nonce reused");
339 			ret = AUTH_NONCE_REUSED;
340 			goto end;
341 		case STALE_NONCE:
342 			LM_DBG("stale nonce\n");
343 			ret = AUTH_STALE_NONCE;
344 			goto end;
345 		case NO_CREDENTIALS:
346 			LM_DBG("no credentials\n");
347 			ret = AUTH_NO_CREDENTIALS;
348 			goto end;
349 		case ERROR:
350 		case BAD_CREDENTIALS:
351 			ret = -3;
352 			goto end;
353 		case CREATE_CHALLENGE:
354 			LM_ERR("CREATE_CHALLENGE is not a valid state\n");
355 			ret = -2;
356 			goto end;
357 		case DO_RESYNCHRONIZATION:
358 			LM_ERR("DO_RESYNCHRONIZATION is not a valid state\n");
359 			ret = -2;
360 			goto end;
361 
362 		case NOT_AUTHENTICATED:
363 			ret = -1;
364 			goto end;
365 
366 		case DO_AUTHENTICATION:
367 			break;
368 
369 		case AUTHENTICATED:
370 			ret = 1;
371 			goto end;
372 	}
373 
374 	cred = (auth_body_t*)h->parsed;
375 
376 	if (use_did) {
377 		if (msg->REQ_METHOD == METHOD_REGISTER) {
378 			ret = get_to_did(&did, msg);
379 		} else {
380 			ret = get_from_did(&did, msg);
381 		}
382 		if (ret == 0) {
383 			did.s = DEFAULT_DID;
384 			did.len = sizeof(DEFAULT_DID) - 1;
385 		}
386 	} else {
387 		did.len = 0;
388 		did.s = 0;
389 	}
390 
391 
392 	if (check_all) {
393 		res = check_all_ha1(msg, h, &(cred->digest),
394 				&msg->first_line.u.request.method, &did, realm, table, &result);
395 		if (res < 0) {
396 			ret = -2;
397 			goto end;
398 		}
399 		else if (res > 0) {
400 			ret = -1;
401 			goto end;
402 		}
403 		else {
404 			ret = 1;
405 			goto end;
406 		}
407 	} else {
408 		res = get_ha1(&cred->digest.username, &did, realm, table, ha1,
409 				&result, &row);
410 		if (res < 0) {
411 			ret = -2;
412 			goto end;
413 		}
414 		if (res > 0) {
415 			/* Username not found in the database */
416 			ret = -1;
417 			goto end;
418 		}
419 	}
420 
421 	/* Recalculate response, it must be same to authorize successfully */
422 	if (!check_response(&(cred->digest), &msg->first_line.u.request.method,
423 				ha1)) {
424 		switch(auth_api.post_auth(msg, h, ha1)) {
425 			case ERROR:
426 			case BAD_CREDENTIALS:
427 				ret = -2;
428 				break;
429 
430 			case NOT_AUTHENTICATED:
431 				ret = -1;
432 				break;
433 
434 			case AUTHENTICATED:
435 				generate_avps(result, row);
436 				ret = 1;
437 				break;
438 
439 			default:
440 				ret = -1;
441 				break;
442 		}
443 	} else {
444 		ret = -1;
445 	}
446 
447 end:
448 	if (result) db_res_free(result);
449 	if (ret < 0) {
450 		if (auth_api.build_challenge(msg, (cred ? cred->stale : 0), realm,
451 					NULL, NULL, hftype) < 0) {
452 			LM_ERR("Error while creating challenge\n");
453 			ret = -2;
454 		}
455 	}
456 	return ret;
457 }
458 
459 
460 /*
461  * Authenticate using Proxy-Authorize header field
462  */
proxy_authenticate(struct sip_msg * msg,char * p1,char * p2)463 int proxy_authenticate(struct sip_msg* msg, char* p1, char* p2)
464 {
465 	str realm;
466 
467 	if (get_str_fparam(&realm, msg, (fparam_t*)p1) < 0) {
468 		LM_ERR("Cannot obtain digest realm from parameter '%s'\n",
469 				((fparam_t*)p1)->orig);
470 		return -1;
471 	}
472 
473 	return authenticate(msg, &realm, (authdb_table_info_t*)p2, HDR_PROXYAUTH_T);
474 }
475 
476 
477 /*
478  * Authorize using WWW-Authorize header field
479  */
www_authenticate(struct sip_msg * msg,char * p1,char * p2)480 int www_authenticate(struct sip_msg* msg, char* p1, char* p2)
481 {
482 	str realm;
483 
484 	if (get_str_fparam(&realm, msg, (fparam_t*)p1) < 0) {
485 		LM_ERR("Cannot obtain digest realm from parameter '%s'\n",
486 				((fparam_t*)p1)->orig);
487 		return -1;
488 	}
489 
490 	return authenticate(msg, &realm, (authdb_table_info_t*)p2,
491 			HDR_AUTHORIZATION_T);
492 }
493