1 /*
2 Copyright (C) 2002-2008 Thomas Ries <tries@gmx.net>
3
4 This file is part of Siproxd.
5
6 Siproxd is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Siproxd is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Siproxd; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include <sys/time.h>
29
30 #include <netinet/in.h>
31
32 #include <osipparser2/osip_parser.h>
33 #include <osipparser2/osip_md5.h>
34
35 #include "digcalc.h"
36
37 #include "siproxd.h"
38 #include "log.h"
39
40 static char const ident[]="$Id: auth.c 392 2008-07-22 11:17:49Z hb9xar $";
41
42 /* configuration storage */
43 extern struct siproxd_config configuration;
44
45 /* Global File instance on pw file */
46 extern FILE *siproxd_passwordfile;
47
48 /* local protorypes */
49 static char *auth_generate_nonce(void);
50 static int auth_check(osip_proxy_authorization_t *proxy_auth);
51 static char *auth_getpwd(char *username);
52
53 /*
54 * perform proxy authentication
55 *
56 * RETURNS
57 * STS_SUCCESS : authentication ok / not needed
58 * STS_FAILURE : authentication failed
59 * STS_NEEDAUTH: authentication needed
60 */
authenticate_proxy(osip_message_t * sipmsg)61 int authenticate_proxy(osip_message_t *sipmsg) {
62 osip_proxy_authorization_t *proxy_auth=NULL;
63
64 /* required by config? */
65 if (configuration.proxy_auth_realm == NULL) {
66 return STS_SUCCESS;
67 }
68
69 /* supplied by UA? */
70 osip_message_get_proxy_authorization(sipmsg, 0, &proxy_auth);
71 if (proxy_auth == NULL) {
72 DEBUGC(DBCLASS_AUTH,"proxy-auth required, not supplied by UA");
73 return STS_NEED_AUTH;
74 }
75
76 /* verify supplied authentication */
77 if (auth_check(proxy_auth) == STS_SUCCESS) {
78 DEBUGC(DBCLASS_AUTH,"proxy-auth succeeded");
79 return STS_SUCCESS;
80 }
81
82 /* authentication failed */
83 WARN("authenticate_proxy failed");
84 return STS_FAILURE;
85 }
86
87 /*
88 * includes proxy authentication header in SIP message
89 *
90 * RETURNS
91 * STS_SUCCESS
92 * STS_FAILURE
93 */
auth_include_authrq(osip_message_t * sipmsg)94 int auth_include_authrq(osip_message_t *sipmsg) {
95 osip_proxy_authenticate_t *p_auth;
96 char *realm=NULL;
97
98 if (osip_proxy_authenticate_init(&p_auth) != 0) {
99 ERROR("proxy_authenticate_init failed");
100 return STS_FAILURE;
101 }
102
103 osip_proxy_authenticate_set_auth_type(p_auth, osip_strdup("Digest"));
104 osip_proxy_authenticate_set_nonce(p_auth, osip_strdup(auth_generate_nonce()));
105 realm=osip_malloc(strlen(configuration.proxy_auth_realm)+3); /* add 2x" and \0 */
106 if (realm) {
107 sprintf(realm,"\"%s\"",configuration.proxy_auth_realm);
108 osip_proxy_authenticate_set_realm(p_auth, realm);
109 } else {
110 ERROR("unable to malloc() %ld bytes for authentication realm",
111 (long)strlen(configuration.proxy_auth_realm)+3);
112 return STS_FAILURE;
113 }
114
115 osip_list_add (&(sipmsg->proxy_authenticates), p_auth, -1);
116
117 DEBUGC(DBCLASS_AUTH,"added authentication header");
118
119 return STS_SUCCESS;
120 }
121
122 /*
123 * generates a nonce string
124 *
125 * RETURNS nonce string
126 */
auth_generate_nonce()127 static char *auth_generate_nonce() {
128 static char nonce[40];
129 struct timeval tv;
130
131 gettimeofday (&tv, NULL);
132
133 /* yeah, I know... should be a better algorithm */
134 /* enclose it in double quotes, as libosip does *not* do it (2.0.6) */
135 sprintf(nonce, "\"%8.8lx%8.8lx%8.8x%8.8x\"",
136 (long)tv.tv_sec, (long)tv.tv_usec, rand(), rand() );
137
138 DEBUGC(DBCLASS_AUTH,"created nonce=\"%s\"",nonce);
139 return nonce;
140 }
141
142
143 /*
144 * verify the supplied authentication information from UA
145 *
146 * RETURNS
147 * STS_SUCCESS if succeeded
148 * STS_FAILURE if failed
149 */
auth_check(osip_proxy_authorization_t * proxy_auth)150 static int auth_check(osip_proxy_authorization_t *proxy_auth) {
151 char *password=NULL;
152 int sts;
153
154 HASHHEX HA1;
155 HASHHEX HA2 = "";
156 HASHHEX Lcl_Response;
157
158 char *Username = NULL;
159 char *Realm = NULL;
160 char *Nonce = NULL;
161 char *CNonce = NULL;
162 char *NonceCount = NULL;
163 char *Qpop = NULL;
164 char *Uri = NULL;
165 char *Response = NULL;
166
167 /* if item exists, allocate& copy string without quotes */
168 if (proxy_auth->username)
169 Username=osip_strdup_without_quote(proxy_auth->username);
170
171 if (proxy_auth->realm)
172 Realm=osip_strdup_without_quote(proxy_auth->realm);
173
174 if (proxy_auth->nonce)
175 Nonce=osip_strdup_without_quote(proxy_auth->nonce);
176
177 if (proxy_auth->cnonce)
178 CNonce=osip_strdup_without_quote(proxy_auth->cnonce);
179
180 if (proxy_auth->nonce_count)
181 NonceCount=osip_strdup_without_quote(proxy_auth->nonce_count);
182
183 if (proxy_auth->message_qop)
184 Qpop=osip_strdup_without_quote(proxy_auth->message_qop);
185
186 if (proxy_auth->uri)
187 Uri=osip_strdup_without_quote(proxy_auth->uri);
188
189 if (proxy_auth->response)
190 Response=osip_strdup_without_quote(proxy_auth->response);
191
192 /* get password */
193 if (configuration.proxy_auth_pwfile) {
194 /* check in passwd file */
195 password=auth_getpwd(Username);
196 } else if (configuration.proxy_auth_passwd) {
197 /* get password from configuration */
198 password=configuration.proxy_auth_passwd;
199 }
200
201 if (password == NULL) {
202 DEBUGC(DBCLASS_AUTH,"user [%s] not in password file!",
203 (Username)?Username:"*NULL*");
204 return STS_FAILURE;
205 }
206
207 DEBUGC(DBCLASS_BABBLE," username=\"%s\"",Username );
208 DEBUGC(DBCLASS_BABBLE," realm =\"%s\"",Realm );
209 DEBUGC(DBCLASS_BABBLE," nonce =\"%s\"",Nonce );
210 DEBUGC(DBCLASS_BABBLE," cnonce =\"%s\"",CNonce );
211 DEBUGC(DBCLASS_BABBLE," nonce_nc=\"%s\"",NonceCount);
212 DEBUGC(DBCLASS_BABBLE," qpop =\"%s\"",Qpop );
213 DEBUGC(DBCLASS_BABBLE," uri =\"%s\"",Uri );
214 DEBUGC(DBCLASS_BABBLE," response=\"%s\"",Response );
215
216 /* calculate the MD5 digest (heavily inspired from linphone code) */
217 DigestCalcHA1("MD5", Username, Realm, password, Nonce, CNonce, HA1);
218 DigestCalcResponse(HA1, Nonce, NonceCount, CNonce, Qpop,
219 "REGISTER", Uri, HA2, Lcl_Response);
220
221 DEBUGC(DBCLASS_BABBLE,"calculated Response=\"%s\"", Lcl_Response);
222
223 if (strcmp((char*)Lcl_Response, Response)==0) {
224 DEBUGC(DBCLASS_AUTH,"Authentication succeeded");
225 sts = STS_SUCCESS;
226 } else {
227 DEBUGC(DBCLASS_AUTH,"Authentication failed");
228 sts = STS_FAILURE;
229 }
230
231 /* free allocated memory from above */
232 if (Username) free(Username);
233 if (Realm) free(Realm);
234 if (Nonce) free(Nonce);
235 if (CNonce) free(CNonce);
236 if (NonceCount) free(NonceCount);
237 if (Qpop) free(Qpop);
238 if (Uri) free(Uri);
239 if (Response) free(Response);
240
241 return sts;
242 }
243
244
245 /*
246 * lookup in the password file and return
247 * the user specific password for 'username'
248 *
249 * RETURNS
250 * password for user or NULL if not found
251 */
auth_getpwd(char * username)252 static char *auth_getpwd(char *username) {
253 typedef struct {
254 char username[USERNAME_SIZE];
255 char password[PASSWORD_SIZE];
256 } auth_cache_t;
257
258 char buff[128];
259 int i;
260 static auth_cache_t *auth_cache=NULL;
261 void *tmpptr;
262 static int auth_cache_size=0;
263 static int auth_cache_count=0;
264
265 if (auth_cache==NULL) {
266 DEBUGC(DBCLASS_AUTH,"initialize password cache");
267
268 /* config file not found or unable to open for read */
269 if (siproxd_passwordfile==NULL) {
270 ERROR ("could not open password file: %s", strerror(errno));
271 return NULL;
272 }
273
274 rewind(siproxd_passwordfile);
275
276 while (fgets(buff,sizeof(buff),siproxd_passwordfile) != NULL) {
277 /* life insurance */
278 buff[sizeof(buff)-1]='\0';
279
280 /* strip newline if present */
281 if (buff[strlen(buff)-1]=='\n') buff[strlen(buff)-1]='\0';
282
283 /* strip emty lines */
284 if (strlen(buff) == 0) continue;
285
286 /* strip comments and line with only whitespaces */
287 for (i=0;i<strlen(buff);i++) {
288 if ((buff[i] == ' ') || (buff[i] == '\t')) continue;
289 if (buff[i] =='#') i=strlen(buff);
290 break;
291 }
292 if (i == strlen(buff)) continue;
293
294 /* allocate space whenever needed */
295 if (auth_cache_count >= auth_cache_size) {
296 auth_cache_size+=10;
297 tmpptr=realloc(auth_cache, auth_cache_size*sizeof(auth_cache_t));
298 if (tmpptr != NULL) {
299 auth_cache= (auth_cache_t *)tmpptr;
300 } else {
301 ERROR("realloc failed! this is not good");
302 auth_cache_size-=10;
303 return NULL;
304 }
305 } /* cnt > size */
306
307 i=sscanf(buff,"%s %s",auth_cache[auth_cache_count].username,
308 auth_cache[auth_cache_count].password);
309 /* if I got username & passwd, make it valid and increment counter */
310 if (i == 2) auth_cache_count++;
311 }
312
313 } /* initialize cache */
314
315 /* search cache for user */
316 DEBUGC(DBCLASS_AUTH,"searching password entry for user %s",username);
317 for (i=0; i< auth_cache_count;i++) {
318 if (strcmp(username, auth_cache[i].username)==0) {
319 DEBUGC(DBCLASS_AUTH,"found password entry for user %s",username);
320 return auth_cache[i].password;
321 }
322 }
323
324 DEBUGC(DBCLASS_AUTH,"no password entry found for user %s",username);
325 return NULL;
326 }
327
328
329 /*-------------------------------------------------------------------------
330 -------------------------------------------------------------------------
331 The routines below have been taken from linphone
332 (osipua/src/authentication.c)
333 -------------------------------------------------------------------------
334 -------------------------------------------------------------------------*/
335
CvtHex(IN HASH Bin,OUT HASHHEX Hex)336 void CvtHex(
337 IN HASH Bin,
338 OUT HASHHEX Hex
339 )
340 {
341 unsigned short i;
342 unsigned char j;
343
344 for (i = 0; i < HASHLEN; i++) {
345 j = (Bin[i] >> 4) & 0xf;
346 if (j <= 9)
347 Hex[i*2] = (j + '0');
348 else
349 Hex[i*2] = (j + 'a' - 10);
350 j = Bin[i] & 0xf;
351 if (j <= 9)
352 Hex[i*2+1] = (j + '0');
353 else
354 Hex[i*2+1] = (j + 'a' - 10);
355 };
356 Hex[HASHHEXLEN] = '\0';
357 }
358
359 /* calculate H(A1) as per spec */
DigestCalcHA1(IN char * pszAlg,IN char * pszUserName,IN char * pszRealm,IN char * pszPassword,IN char * pszNonce,IN char * pszCNonce,OUT HASHHEX SessionKey)360 void DigestCalcHA1(
361 IN char * pszAlg,
362 IN char * pszUserName,
363 IN char * pszRealm,
364 IN char * pszPassword,
365 IN char * pszNonce,
366 IN char * pszCNonce,
367 OUT HASHHEX SessionKey
368 )
369 {
370 osip_MD5_CTX Md5Ctx;
371 HASH HA1;
372
373 osip_MD5Init(&Md5Ctx);
374 if (pszUserName) osip_MD5Update(&Md5Ctx, (unsigned char*)pszUserName,
375 strlen(pszUserName));
376 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
377 if (pszRealm) osip_MD5Update(&Md5Ctx, (unsigned char*)pszRealm,
378 strlen(pszRealm));
379 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
380 if (pszPassword) osip_MD5Update(&Md5Ctx, (unsigned char*)pszPassword,
381 strlen(pszPassword));
382 osip_MD5Final(HA1, &Md5Ctx);
383
384 if ((pszAlg!=NULL) && (osip_strcasecmp(pszAlg, "md5-sess") == 0)) {
385 osip_MD5Init(&Md5Ctx);
386 osip_MD5Update(&Md5Ctx, HA1, HASHLEN);
387 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
388 if (pszNonce) osip_MD5Update(&Md5Ctx, (unsigned char*)pszNonce,
389 strlen(pszNonce));
390 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
391 if (pszCNonce) osip_MD5Update(&Md5Ctx, (unsigned char*)pszCNonce,
392 strlen(pszCNonce));
393 osip_MD5Final(HA1, &Md5Ctx);
394 };
395 CvtHex(HA1, SessionKey);
396 }
397
398 /* calculate request-digest/response-digest as per HTTP Digest spec */
DigestCalcResponse(IN HASHHEX HA1,IN char * pszNonce,IN char * pszNonceCount,IN char * pszCNonce,IN char * pszQop,IN char * pszMethod,IN char * pszDigestUri,IN HASHHEX HEntity,OUT HASHHEX Response)399 void DigestCalcResponse(
400 IN HASHHEX HA1, /* H(A1) */
401 IN char * pszNonce, /* nonce from server */
402 IN char * pszNonceCount, /* 8 hex digits */
403 IN char * pszCNonce, /* client nonce */
404 IN char * pszQop, /* qop-value: "", "auth", "auth-int" */
405 IN char * pszMethod, /* method from the request */
406 IN char * pszDigestUri, /* requested URL */
407 IN HASHHEX HEntity, /* H(entity body) if qop="auth-int" */
408 OUT HASHHEX Response /* request-digest or response-digest */
409 )
410 {
411 osip_MD5_CTX Md5Ctx;
412 HASH HA2;
413 HASH RespHash;
414 HASHHEX HA2Hex;
415
416 /* calculate H(A2) */
417 osip_MD5Init(&Md5Ctx);
418 if (pszMethod) osip_MD5Update(&Md5Ctx, (unsigned char*)pszMethod,
419 strlen(pszMethod));
420 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
421 if (pszDigestUri) osip_MD5Update(&Md5Ctx, (unsigned char*)pszDigestUri,
422 strlen(pszDigestUri));
423
424 if (pszQop!=NULL) {
425 goto auth_withqop;
426 };
427
428 /* auth_withoutqop: */
429 osip_MD5Final(HA2, &Md5Ctx);
430 CvtHex(HA2, HA2Hex);
431
432 /* calculate response */
433 osip_MD5Init(&Md5Ctx);
434 osip_MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
435 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
436 if (pszNonce) osip_MD5Update(&Md5Ctx, (unsigned char*)pszNonce, strlen(pszNonce));
437 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
438
439 goto end;
440
441 auth_withqop:
442
443 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
444 osip_MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
445 osip_MD5Final(HA2, &Md5Ctx);
446 CvtHex(HA2, HA2Hex);
447
448 /* calculate response */
449 osip_MD5Init(&Md5Ctx);
450 osip_MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
451 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
452 if (pszNonce) osip_MD5Update(&Md5Ctx, (unsigned char*)pszNonce, strlen(pszNonce));
453 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
454 if (pszNonceCount)osip_MD5Update(&Md5Ctx, (unsigned char*)pszNonceCount, strlen(pszNonceCount));
455 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
456 if (pszCNonce) osip_MD5Update(&Md5Ctx, (unsigned char*)pszCNonce, strlen(pszCNonce));
457 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
458 if (pszQop) osip_MD5Update(&Md5Ctx, (unsigned char*)pszQop, strlen(pszQop));
459 osip_MD5Update(&Md5Ctx, (unsigned char*)":", 1);
460
461 end:
462 osip_MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
463 osip_MD5Final(RespHash, &Md5Ctx);
464 CvtHex(RespHash, Response);
465 }
466
467
468