mirror of https://github.com/sipwise/sipsak.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
362 lines
12 KiB
362 lines
12 KiB
/*
|
|
* $Id: auth.c 397 2006-01-28 21:11:50Z calrissian $
|
|
*
|
|
* Copyright (C) 2002-2004 Fhg Fokus
|
|
* Copyright (C) 2004-2005 Nils Ohlmeier
|
|
*
|
|
* This file belongs to sipsak, a free sip testing tool.
|
|
*
|
|
* sipsak 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.
|
|
*
|
|
* sipsak 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.
|
|
*/
|
|
#include "sipsak.h"
|
|
#include "auth.h"
|
|
#include "exit_code.h"
|
|
#include "helper.h"
|
|
#include "md5.h"
|
|
|
|
#ifdef HAVE_OPENSSL_SHA1
|
|
# include <openssl/sha.h>
|
|
#endif
|
|
|
|
#define SIPSAK_ALGO_MD5 1
|
|
#define SIPSAK_ALGO_SHA1 2
|
|
|
|
/* converts a hash into hex output
|
|
taken from the RFC 2617 */
|
|
void cvt_hex(unsigned char *_b, unsigned char *_h, int length)
|
|
{
|
|
unsigned short i;
|
|
unsigned char j;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
j = (_b[i] >> 4) & 0xf;
|
|
if (j <= (unsigned char)9) {
|
|
_h[i * 2] = (j + (unsigned char)'0');
|
|
} else {
|
|
_h[i * 2] = (unsigned char)(j + (unsigned char)'a' - (unsigned char)10);
|
|
}
|
|
j = _b[i] & 0xf;
|
|
if (j <= (unsigned char)9) {
|
|
_h[i * 2 + 1] = (j + (unsigned char)'0');
|
|
} else {
|
|
_h[i * 2 + 1] = (unsigned char)(j + (unsigned char)'a' - (unsigned char)10);
|
|
}
|
|
};
|
|
_h[2*length] = '\0';
|
|
}
|
|
|
|
/* check for, create and insert a auth header into the message */
|
|
void insert_auth(char *message, char *authreq)
|
|
{
|
|
char *auth, *begin, *end, *insert, *backup, *realm, *usern, *nonce;
|
|
char *method, *uri;
|
|
char *qop_tmp = NULL;
|
|
unsigned char ha1[SIPSAK_HASHLEN], ha2[SIPSAK_HASHLEN], resp[SIPSAK_HASHLEN];
|
|
unsigned char ha1_hex[SIPSAK_HASHHEXLEN+1], ha2_hex[SIPSAK_HASHHEXLEN+1], resp_hex[SIPSAK_HASHHEXLEN+1];
|
|
int qop_auth=0, proxy_auth=0, algo=0;
|
|
unsigned int cnonce;
|
|
MD5_CTX Md5Ctx;
|
|
#ifdef HAVE_OPENSSL_SHA1
|
|
SHA_CTX Sha1Ctx;
|
|
#endif
|
|
|
|
auth=begin=end=insert=backup=realm=usern=nonce=method=uri = NULL;
|
|
|
|
memset(&ha1[0], '\0', SIPSAK_HASHLEN);
|
|
memset(&ha2[0], '\0', SIPSAK_HASHLEN);
|
|
memset(&resp[0], '\0', SIPSAK_HASHLEN);
|
|
memset(&ha1_hex[0], '\0', SIPSAK_HASHHEXLEN+1);
|
|
memset(&ha2_hex[0], '\0', SIPSAK_HASHHEXLEN+1);
|
|
memset(&resp_hex[0], '\0', SIPSAK_HASHHEXLEN+1);
|
|
|
|
/* prevent double auth insertion */
|
|
if ((begin=STRCASESTR(message, AUTH_STR))!=NULL ||
|
|
(begin=STRCASESTR(message, PROXYAUZ_STR))!=NULL) {
|
|
fprintf(stderr, "request:\n%s\nresponse:\n%s\nerror: authorization failed\n "
|
|
" request already contains (Proxy-) Authorization, but "
|
|
"received 40[1|7], see above\n", message, authreq);
|
|
exit_code(2, __PRETTY_FUNCTION__, "failed to add auth header, because request contained already one");
|
|
}
|
|
/* make a backup of all except the request line because for
|
|
simplicity we insert the auth header direct behind the request line */
|
|
insert=strchr(message, '\n');
|
|
if (!insert) {
|
|
printf("failed to find newline\n");
|
|
return;
|
|
}
|
|
insert++;
|
|
backup=str_alloc(strlen(insert)+1);
|
|
strncpy(backup, insert, strlen(insert));
|
|
|
|
begin=STRCASESTR(authreq, WWWAUTH_STR);
|
|
if (begin==NULL) {
|
|
begin=STRCASESTR(authreq, PROXYAUTH_STR);
|
|
proxy_auth = 1;
|
|
}
|
|
if (begin) {
|
|
/* make a copy of the auth header to prevent that our searches
|
|
hit content of other header fields */
|
|
end=strchr(begin, '\n');
|
|
auth=str_alloc((size_t)(end-begin+1));
|
|
strncpy(auth, begin, (size_t)(end-begin));
|
|
/* we support Digest with MD5 or SHA1 */
|
|
if ((begin=STRCASESTR(auth, "Basic"))!=NULL) {
|
|
fprintf(stderr, "%s\nerror: authentication method Basic is deprecated since"
|
|
" RFC 3261 and not supported by sipsak\n", authreq);
|
|
exit_code(3, __PRETTY_FUNCTION__, "authentication method 'Basic' is deprecated");
|
|
}
|
|
if ((begin=STRCASESTR(auth, "Digest"))==NULL) {
|
|
fprintf(stderr, "%s\nerror: couldn't find authentication method Digest in "
|
|
"the 40[1|7] response above\n", authreq);
|
|
exit_code(3, __PRETTY_FUNCTION__, "missing authentication method 'Digest' in reply");
|
|
}
|
|
if ((begin=STRCASESTR(auth, "algorithm="))!=NULL) {
|
|
begin+=10;
|
|
if ((STRNCASECMP(begin, "MD5", 3))==0 || (STRNCASECMP(begin, "\"MD5\"", 5))==0) {
|
|
algo = SIPSAK_ALGO_MD5;
|
|
}
|
|
#ifdef HAVE_OPENSSL_SHA1
|
|
else if ((STRNCASECMP(begin, "SHA1", 3))==0 || (STRNCASECMP(begin, "\"SHA1\"", 5))==0) {
|
|
algo = SIPSAK_ALGO_SHA1;
|
|
}
|
|
#endif
|
|
else {
|
|
fprintf(stderr, "\n%s\nerror: unsupported authentication algorithm\n", authreq);
|
|
exit_code(2, __PRETTY_FUNCTION__, "unsupported authentication algorithm");
|
|
}
|
|
}
|
|
else {
|
|
algo = SIPSAK_ALGO_MD5;
|
|
}
|
|
/* we need the username at some points */
|
|
if (auth_username != NULL) {
|
|
usern = auth_username;
|
|
}
|
|
else {
|
|
usern=str_alloc(strlen(username)+11);
|
|
if (nameend>0)
|
|
snprintf(usern, strlen(username)+10, "%s%i", username, namebeg);
|
|
else
|
|
snprintf(usern, strlen(username)+10, "%s", username);
|
|
}
|
|
/* extract the method from the original request */
|
|
end=strchr(message, ' ');
|
|
method=str_alloc((size_t)(end-message+1));
|
|
strncpy(method, message, (size_t)(end-message));
|
|
/* extract the uri also */
|
|
begin=end++;
|
|
begin++;
|
|
end=strchr(end, ' ');
|
|
uri=str_alloc((size_t)(end-begin+1));
|
|
strncpy(uri, begin, (size_t)(end-begin));
|
|
|
|
/* lets start with some basic stuff... username, uri and algorithm */
|
|
if (proxy_auth == 1) {
|
|
snprintf(insert, PROXYAUZ_STR_LEN+1, PROXYAUZ_STR);
|
|
insert+=PROXYAUZ_STR_LEN;
|
|
}
|
|
else {
|
|
snprintf(insert, AUTH_STR_LEN+1, AUTH_STR);
|
|
insert+=AUTH_STR_LEN;
|
|
}
|
|
snprintf(insert, strlen(usern)+14, "username=\"%s\", ", usern);
|
|
insert+=strlen(insert);
|
|
snprintf(insert, strlen(uri)+9, "uri=\"%s\", ", uri);
|
|
insert+=strlen(insert);
|
|
snprintf(insert, ALGO_STR_LEN+1, ALGO_STR);
|
|
insert+=ALGO_STR_LEN;
|
|
if (algo == SIPSAK_ALGO_MD5) {
|
|
snprintf(insert, MD5_STR_LEN+1, MD5_STR);
|
|
insert+=MD5_STR_LEN;
|
|
}
|
|
#ifdef HAVE_OPENSSL_SHA1
|
|
else if (algo == SIPSAK_ALGO_SHA1) {
|
|
snprintf(insert, SHA1_STR_LEN+1, SHA1_STR);
|
|
insert+=SHA1_STR_LEN;
|
|
}
|
|
#endif
|
|
/* search for the realm, copy it to request and extract it for hash*/
|
|
if ((begin=STRCASESTR(auth, REALM_STR))!=NULL) {
|
|
end=strchr(begin, ',');
|
|
if (!end)
|
|
end=strchr(begin, '\r');
|
|
strncpy(insert, begin, (size_t)(end-begin+1));
|
|
insert=insert+(end-begin+1);
|
|
if (*(insert-1) == '\r')
|
|
*(insert-1)=',';
|
|
snprintf(insert, 2, " ");
|
|
insert++;
|
|
begin+=REALM_STR_LEN+1;
|
|
end--;
|
|
realm=str_alloc((size_t)(end-begin+1));
|
|
strncpy(realm, begin, (size_t)(end-begin));
|
|
}
|
|
else {
|
|
fprintf(stderr, "%s\nerror: realm not found in 401 above\n", authreq);
|
|
exit_code(3, __PRETTY_FUNCTION__, "realm not found in reply");
|
|
}
|
|
/* copy opaque if needed */
|
|
if ((begin=STRCASESTR(auth, OPAQUE_STR))!=NULL) {
|
|
end=strchr(begin, ',');
|
|
if (!end) {
|
|
end=strchr(begin, '\r');
|
|
}
|
|
strncpy(insert, begin, (size_t)(end-begin+1));
|
|
insert=insert+(end-begin+1);
|
|
if (*(insert-1) == '\r')
|
|
*(insert-1)=',';
|
|
snprintf(insert, 2, " ");
|
|
insert++;
|
|
}
|
|
/* lets see if qop=auth is uspported */
|
|
if ((begin=STRCASESTR(auth, QOP_STR))!=NULL) {
|
|
if (STRCASESTR(begin, QOPAUTH_STR)==NULL) {
|
|
fprintf(stderr, "response\n%s\nerror: qop \"auth\" not supported by"
|
|
" server\n", authreq);
|
|
exit_code(3, __PRETTY_FUNCTION__, "qop 'auth' is not supported by server");
|
|
}
|
|
qop_auth=1;
|
|
}
|
|
/* search, copy and extract the nonce */
|
|
if ((begin=STRCASESTR(auth, NONCE_STR))!=NULL) {
|
|
end=strchr(begin, ',');
|
|
if (!end)
|
|
end=strchr(begin, '\r');
|
|
strncpy(insert, begin, (size_t)(end-begin+1));
|
|
insert=insert+(end-begin+1);
|
|
if (*(insert-1) == '\r')
|
|
*(insert-1)=',';
|
|
snprintf(insert, 2, " ");
|
|
insert++;
|
|
begin+=NONCE_STR_LEN+1;
|
|
end--;
|
|
nonce=str_alloc((size_t)(end-begin+1));
|
|
strncpy(nonce, begin, (size_t)(end-begin));
|
|
}
|
|
else {
|
|
fprintf(stderr, "%s\nerror: nonce not found in 401 above\n", authreq);
|
|
exit_code(3, __PRETTY_FUNCTION__, "missing nonce in reply");
|
|
}
|
|
/* if qop is supported we need som additional header */
|
|
if (qop_auth == 1) {
|
|
snprintf(insert, QOP_STR_LEN+QOPAUTH_STR_LEN+3, "%s%s, ", QOP_STR, QOPAUTH_STR);
|
|
insert+=strlen(insert);
|
|
nonce_count++;
|
|
snprintf(insert, NC_STR_LEN+11, "%s%08x, ", NC_STR, nonce_count);
|
|
insert+=strlen(insert);
|
|
cnonce=(unsigned int)rand();
|
|
snprintf(insert, 12+8, "cnonce=\"%x\", ", cnonce);
|
|
insert+=strlen(insert);
|
|
/* hopefully 100 is enough */
|
|
qop_tmp=str_alloc(100);
|
|
snprintf(qop_tmp, 8+8+8, "%08x:%x:auth:", nonce_count, cnonce);
|
|
}
|
|
/* if no password is given we try it with empty password */
|
|
if (!password)
|
|
password = EMPTY_STR;
|
|
|
|
if (algo == SIPSAK_ALGO_MD5) {
|
|
if (authhash) {
|
|
strncpy((char*)&ha1_hex[0], authhash, SIPSAK_HASHHEXLEN_MD5);
|
|
}
|
|
else {
|
|
MD5Init(&Md5Ctx);
|
|
MD5Update(&Md5Ctx, usern, (unsigned int)strlen(usern));
|
|
MD5Update(&Md5Ctx, ":", 1);
|
|
MD5Update(&Md5Ctx, realm, (unsigned int)strlen(realm));
|
|
MD5Update(&Md5Ctx, ":", 1);
|
|
MD5Update(&Md5Ctx, password, (unsigned int)strlen(password));
|
|
MD5Final(&ha1[0], &Md5Ctx);
|
|
cvt_hex(&ha1[0], &ha1_hex[0], SIPSAK_HASHLEN_MD5);
|
|
}
|
|
|
|
MD5Init(&Md5Ctx);
|
|
MD5Update(&Md5Ctx, method, (unsigned int)strlen(method));
|
|
MD5Update(&Md5Ctx, ":", 1);
|
|
MD5Update(&Md5Ctx, uri, (unsigned int)strlen(uri));
|
|
MD5Final(&ha2[0], &Md5Ctx);
|
|
cvt_hex(&ha2[0], &ha2_hex[0], SIPSAK_HASHLEN_MD5);
|
|
|
|
MD5Init(&Md5Ctx);
|
|
MD5Update(&Md5Ctx, &ha1_hex, SIPSAK_HASHHEXLEN_MD5);
|
|
MD5Update(&Md5Ctx, ":", 1);
|
|
MD5Update(&Md5Ctx, nonce, (unsigned int)strlen(nonce));
|
|
MD5Update(&Md5Ctx, ":", 1);
|
|
if (qop_auth == 1) {
|
|
MD5Update(&Md5Ctx, qop_tmp, (unsigned int)strlen(qop_tmp));
|
|
}
|
|
MD5Update(&Md5Ctx, &ha2_hex, SIPSAK_HASHHEXLEN_MD5);
|
|
MD5Final(&resp[0], &Md5Ctx);
|
|
cvt_hex(&resp[0], &resp_hex[0], SIPSAK_HASHLEN_MD5);
|
|
}
|
|
#ifdef HAVE_OPENSSL_SHA1
|
|
else if (algo == SIPSAK_ALGO_SHA1) {
|
|
if (authhash) {
|
|
strncpy((char*)&ha1_hex[0], authhash, SIPSAK_HASHHEXLEN_SHA1);
|
|
}
|
|
else {
|
|
SHA1_Init(&Sha1Ctx);
|
|
SHA1_Update(&Sha1Ctx, usern, (unsigned int)strlen(usern));
|
|
SHA1_Update(&Sha1Ctx, ":", 1);
|
|
SHA1_Update(&Sha1Ctx, realm, (unsigned int)strlen(realm));
|
|
SHA1_Update(&Sha1Ctx, ":", 1);
|
|
SHA1_Update(&Sha1Ctx, password, (unsigned int)strlen(password));
|
|
SHA1_Final(&ha1[0], &Sha1Ctx);
|
|
cvt_hex(&ha1[0], &ha1_hex[0], SIPSAK_HASHLEN_SHA1);
|
|
}
|
|
|
|
SHA1_Init(&Sha1Ctx);
|
|
SHA1_Update(&Sha1Ctx, method, (unsigned int)strlen(method));
|
|
SHA1_Update(&Sha1Ctx, ":", 1);
|
|
SHA1_Update(&Sha1Ctx, uri, (unsigned int)strlen(uri));
|
|
SHA1_Final(&ha2[0], &Sha1Ctx);
|
|
cvt_hex(&ha2[0], &ha2_hex[0], SIPSAK_HASHLEN_SHA1);
|
|
|
|
SHA1_Init(&Sha1Ctx);
|
|
SHA1_Update(&Sha1Ctx, &ha1_hex, SIPSAK_HASHHEXLEN_SHA1);
|
|
SHA1_Update(&Sha1Ctx, ":", 1);
|
|
SHA1_Update(&Sha1Ctx, nonce, (unsigned int)strlen(nonce));
|
|
SHA1_Update(&Sha1Ctx, ":", 1);
|
|
if (qop_auth == 1) {
|
|
SHA1_Update(&Sha1Ctx, qop_tmp, (unsigned int)strlen(qop_tmp));
|
|
}
|
|
SHA1_Update(&Sha1Ctx, &ha2_hex, SIPSAK_HASHHEXLEN_SHA1);
|
|
SHA1_Final(&resp[0], &Sha1Ctx);
|
|
cvt_hex(&resp[0], &resp_hex[0], SIPSAK_HASHLEN_SHA1);
|
|
}
|
|
#endif
|
|
|
|
snprintf(insert, RESPONSE_STR_LEN+1, RESPONSE_STR);
|
|
insert+=RESPONSE_STR_LEN;
|
|
snprintf(insert, sizeof(resp_hex) + 8,"\"%s\"\r\n", &resp_hex[0]);
|
|
insert+=strlen(insert);
|
|
/* the auth header is complete, reinsert the rest of the request */
|
|
strncpy(insert, backup, strlen(backup));
|
|
}
|
|
else {
|
|
fprintf(stderr, "%s\nerror: couldn't find Proxy- or WWW-Authentication header"
|
|
" in the 401 response above\n", authreq);
|
|
exit_code(3, __PRETTY_FUNCTION__, "missing authentication header in reply");
|
|
}
|
|
if (verbose>1)
|
|
printf("authorizing\n");
|
|
/* hopefully we free all here */
|
|
free(backup);
|
|
free(auth);
|
|
free(method);
|
|
free(uri);
|
|
free(realm);
|
|
free(nonce);
|
|
if (auth_username == NULL) free(usern);
|
|
if (qop_auth == 1) free(qop_tmp);
|
|
}
|
|
|