Commit cee09848 authored by deuce's avatar deuce
Browse files

Initial support for Digest authentication.

- nonce is not well generated, nor is it validated in the request.
  This *NEEDS* to be fixed.
- Password must be entered in the same case as it is stored in the user
  database.  I *think* this is always uppercase, but I'm not sure.
parent 85aed003
......@@ -60,11 +60,13 @@
#undef SBBS /* this shouldn't be defined unless building sbbs.dll/libsbbs.so */
#include "sbbs.h"
#include "sbbsdefs.h"
#include "sockwrap.h" /* sendfilesocket() */
#include "threadwrap.h"
#include "semwrap.h"
#include "websrvr.h"
#include "base64.h"
#include "md5.h"
static const char* server_name="Synchronet Web Server";
static const char* newline="\r\n";
......@@ -133,6 +135,45 @@ struct log_data {
struct tm completed;
};
enum auth_type {
AUTHENTICATION_UNKNOWN
,AUTHENTICATION_BASIC
,AUTHENTICATION_DIGEST
};
enum algorithm {
ALGORITHM_UNKNOWN
,ALGORITHM_MD5
,ALGORITHM_MD5_SESS
};
enum qop_option {
QOP_NONE
,QOP_AUTH
,QOP_AUTH_INT
,QOP_UNKNOWN
};
typedef struct {
/* Realm and domain are specified in webctl.ini */
char nonce[128];
BOOL stale;
} authentication_response_t;
typedef struct {
enum auth_type type;
char username[(LEN_ALIAS > LEN_NAME ? LEN_ALIAS : LEN_NAME)+1];
char password[LEN_PASS+1];
char *digest_uri;
char *realm;
char *nonce;
enum algorithm algorithm;
enum qop_option qop_value;
char *cnonce;
char *nonce_count;
unsigned char digest[16]; /* MD5 digest */
} authentication_request_t;
typedef struct {
int method;
char virtual_path[MAX_PATH+1];
......@@ -141,7 +182,7 @@ typedef struct {
time_t if_modified_since;
BOOL keep_alive;
char ars[256];
char auth[128]; /* UserID:Password */
authentication_request_t auth;
char host[128]; /* The requested host. (as used for self-referencing URLs) */
char vhost[128]; /* The requested host. (virtual host) */
int send_location;
......@@ -835,6 +876,13 @@ static void close_request(http_session_t * session)
FREE_AND_NULL(session->req.error_dir);
FREE_AND_NULL(session->req.cgi_dir);
FREE_AND_NULL(session->req.realm);
FREE_AND_NULL(session->req.auth.digest_uri);
FREE_AND_NULL(session->req.auth.cnonce);
FREE_AND_NULL(session->req.auth.realm);
FREE_AND_NULL(session->req.auth.nonce);
FREE_AND_NULL(session->req.auth.nonce_count);
/*
* This causes all active http_session_threads to terminate.
*/
......@@ -1345,16 +1393,13 @@ BOOL http_checkuser(http_session_t * session)
static BOOL check_ars(http_session_t * session)
{
char *username;
char *password;
char *last;
uchar *ar;
BOOL authorized;
char auth_req[MAX_REQUEST_LINE+1];
int i;
user_t thisuser;
if(session->req.auth[0]==0) {
if(session->req.auth.type==AUTHENTICATION_UNKNOWN) {
/* No authentication information... */
if(session->last_user_num!=0) {
if(session->last_user_num>0)
......@@ -1373,19 +1418,9 @@ static BOOL check_ars(http_session_t * session)
/* No auth required, allow */
return(TRUE);
}
SAFECOPY(auth_req,session->req.auth);
username=strtok_r(auth_req,":",&last);
if(username)
password=strtok_r(NULL,":",&last);
else {
username="";
password="";
}
/* Require a password */
if(password==NULL)
password="";
i=matchuser(&scfg, username, FALSE);
i=matchuser(&scfg, session->req.auth.username, FALSE);
if(i==0) {
if(session->last_user_num!=0) {
if(session->last_user_num>0)
......@@ -1395,37 +1430,102 @@ static BOOL check_ars(http_session_t * session)
}
if(!http_checkuser(session))
return(FALSE);
if(scfg.sys_misc&SM_ECHO_PW)
if(scfg.sys_misc&SM_ECHO_PW && session->req.auth.type==AUTHENTICATION_BASIC)
lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s, Password: %s"
,session->socket,username,password);
,session->socket,session->req.auth.username,session->req.auth.password);
else
lprintf(LOG_NOTICE,"%04d !UNKNOWN USER: %s"
,session->socket,username);
,session->socket,session->req.auth.username);
return(FALSE);
}
thisuser.number=i;
getuserdat(&scfg, &thisuser);
if(thisuser.pass[0] && stricmp(thisuser.pass,password)) {
if(session->last_user_num!=0) {
if(session->last_user_num>0)
http_logoff(session,session->socket,__LINE__);
session->user.number=0;
http_logon(session,NULL);
}
if(!http_checkuser(session))
return(FALSE);
/* Should go to the hack log? */
if(scfg.sys_misc&SM_ECHO_PW)
lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
,session->socket,username,password,thisuser.pass);
else
lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
,session->socket,username);
#ifdef _WIN32
if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE))
PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
return(FALSE);
switch(session->req.auth.type) {
case AUTHENTICATION_BASIC:
if(thisuser.pass[0] && stricmp(thisuser.pass,session->req.auth.password)) {
if(session->last_user_num!=0) {
if(session->last_user_num>0)
http_logoff(session,session->socket,__LINE__);
session->user.number=0;
http_logon(session,NULL);
}
if(!http_checkuser(session))
return(FALSE);
/* Should go to the hack log? */
if(scfg.sys_misc&SM_ECHO_PW)
lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s: '%s' expected '%s'"
,session->socket,session->req.auth.username,session->req.auth.password,thisuser.pass);
else
lprintf(LOG_WARNING,"%04d !PASSWORD FAILURE for user %s"
,session->socket,session->req.auth.username);
#ifdef _WIN32
if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE))
PlaySound(startup->hack_sound, NULL, SND_ASYNC|SND_FILENAME);
#endif
return(FALSE);
}
break;
case AUTHENTICATION_DIGEST:
{
unsigned char digest[MD5_DIGEST_SIZE];
char ha1[MD5_DIGEST_SIZE*2+1];
char ha2[MD5_DIGEST_SIZE*2+1];
MD5 ctx;
if(session->req.auth.qop_value==QOP_UNKNOWN)
return(FALSE);
if(session->req.auth.algorithm==ALGORITHM_UNKNOWN)
return(FALSE);
/* H(A1) */
MD5_open(&ctx);
MD5_digest(&ctx, session->req.auth.username, strlen(session->req.auth.username));
MD5_digest(&ctx, ":", 1);
MD5_digest(&ctx, session->req.realm?session->req.realm:scfg.sys_name, strlen(session->req.realm?session->req.realm:scfg.sys_name));
MD5_digest(&ctx, ":", 1);
MD5_digest(&ctx, thisuser.pass, strlen(thisuser.pass));
MD5_close(&ctx, digest);
MD5_hex(ha1, digest);
/* H(A2) */
MD5_open(&ctx);
MD5_digest(&ctx, methods[session->req.method], strlen(methods[session->req.method]));
MD5_digest(&ctx, ":", 1);
MD5_digest(&ctx, session->req.auth.digest_uri, strlen(session->req.auth.digest_uri));
/* TODO QOP==AUTH_INT */
if(session->req.auth.qop_value == QOP_AUTH_INT)
return(FALSE);
MD5_close(&ctx, digest);
MD5_hex(ha2, digest);
MD5_open(&ctx);
MD5_digest(&ctx, ha1, strlen(ha1));
MD5_digest(&ctx, ":", 1);
MD5_digest(&ctx, session->req.auth.nonce, strlen(session->req.auth.nonce));
MD5_digest(&ctx, ":", 1);
if(session->req.auth.qop_value != QOP_NONE) {
MD5_digest(&ctx, session->req.auth.nonce_count, strlen(session->req.auth.nonce_count));
MD5_digest(&ctx, ":", 1);
MD5_digest(&ctx, session->req.auth.cnonce, strlen(session->req.auth.cnonce));
MD5_digest(&ctx, ":", 1);
switch(session->req.auth.qop_value) {
case QOP_AUTH:
MD5_digest(&ctx, "auth", 4);
break;
case QOP_AUTH_INT:
MD5_digest(&ctx, "auth-int", 7);
break;
}
MD5_digest(&ctx, ":", 1);
}
MD5_digest(&ctx, ha2, strlen(ha2));
MD5_close(&ctx, digest);
if(memcmp(digest, session->req.auth.digest, sizeof(digest)))
return(FALSE);
}
}
if(i != session->last_user_num) {
......@@ -1439,7 +1539,7 @@ static BOOL check_ars(http_session_t * session)
if(session->req.ld!=NULL) {
FREE_AND_NULL(session->req.ld->user);
/* FREE()d in http_logging_thread */
session->req.ld->user=strdup(username);
session->req.ld->user=strdup(session->req.auth.username);
}
ar = arstr(NULL,session->req.ars,&scfg);
......@@ -1448,7 +1548,14 @@ static BOOL check_ars(http_session_t * session)
FREE_AND_NULL(ar);
if(authorized) {
add_env(session,"AUTH_TYPE","Basic");
switch(session->req.auth.type) {
case AUTHENTICATION_BASIC:
add_env(session,"AUTH_TYPE","Basic");
break;
case AUTHENTICATION_DIGEST:
add_env(session,"AUTH_TYPE","Digest");
break;
}
/* Should use real name if set to do so somewhere ToDo */
add_env(session,"REMOTE_USER",session->user.alias);
......@@ -1457,7 +1564,7 @@ static BOOL check_ars(http_session_t * session)
/* Should go to the hack log? */
lprintf(LOG_WARNING,"%04d !AUTHORIZATION FAILURE for user %s, ARS: %s"
,session->socket,username,session->req.ars);
,session->socket,session->req.auth.username,session->req.ars);
#ifdef _WIN32
if(startup->hack_sound[0] && !(startup->options&BBS_OPT_MUTE))
......@@ -1842,10 +1949,73 @@ static void js_parse_query(http_session_t * session, char *p) {
}
}
static char *get_token_value(char **p)
{
char *pos=*p;
char *start;
char *out;
BOOL escaped=FALSE;
start=pos;
out=start;
if(*pos=='"') {
for(pos++; *pos; pos++) {
if(escaped && *pos)
*(out++)=*pos;
else if(*pos=='"') {
pos++;
break;
}
else if(*pos=='\\')
escaped=TRUE;
else
*(out++)=*pos;
}
*out=0;
}
else {
for(; *pos; pos++) {
if(iscntrl(*pos))
goto end_of_text;
switch(*pos) {
case 0:
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
case ' ':
case '\t':
goto end_of_text;
}
*(out++)=*pos;
}
end_of_text:
if(*pos)
pos++;
*out=0;
}
*p=pos;
return(start);
}
static BOOL parse_headers(http_session_t * session)
{
char *head_line;
char *value;
char *tvalue;
char *last;
char *p;
int i;
......@@ -1861,12 +2031,99 @@ static BOOL parse_headers(http_session_t * session)
while(*value && *value<=' ') value++;
switch(i) {
case HEAD_AUTH:
if(strtok_r(value," ",&last)) {
p=strtok_r(NULL," ",&last);
if(p==NULL)
break;
while(*p && *p<' ') p++;
b64_decode(session->req.auth,sizeof(session->req.auth),p,strlen(p));
if((p=strtok_r(value," ",&last))!=NULL) {
if(stricmp(p, "Basic")==0) {
p=strtok_r(NULL," ",&last);
if(p==NULL)
break;
while(*p && *p<' ') p++;
b64_decode(p,strlen(p),p,strlen(p));
p=strtok_r(p,":",&last);
if(p) {
if(strlen(p) >= sizeof(session->req.auth.username))
break;
SAFECOPY(session->req.auth.username, p);
p=strtok_r(NULL,":",&last);
if(p) {
if(strlen(p) >= sizeof(session->req.auth.password))
break;
SAFECOPY(session->req.auth.password, p);
session->req.auth.type=AUTHENTICATION_BASIC;
}
}
}
else if(stricmp(p, "Digest")==0) {
p=strtok_r(NULL, "", &last);
/* Defaults */
session->req.auth.algorithm=ALGORITHM_MD5;
session->req.auth.type=AUTHENTICATION_DIGEST;
/* Parse out values one at a time and store */
while(*p) {
while(isspace(*p))
p++;
if(strnicmp(p,"username=",9)==0) {
p+=9;
tvalue=get_token_value(&p);
if(strlen(tvalue) >= sizeof(session->req.auth.username))
break;
SAFECOPY(session->req.auth.username, tvalue);
}
else if(strnicmp(p,"realm=",6)==0) {
p+=6;
session->req.auth.realm=strdup(get_token_value(&p));
}
else if(strnicmp(p,"nonce=",6)==0) {
p+=6;
session->req.auth.nonce=strdup(get_token_value(&p));
}
else if(strnicmp(p,"uri=",4)==0) {
p+=4;
session->req.auth.digest_uri=strdup(get_token_value(&p));
}
else if(strnicmp(p,"response=",9)==0) {
p+=9;
tvalue=get_token_value(&p);
if(strlen(tvalue)==32) {
for(i=0; i<16; i++) {
session->req.auth.digest[i]=digittoint(tvalue[i*2])<<4 | digittoint(tvalue[i*2+1]);
}
}
}
else if(strnicmp(p,"algorithm=",10)==0) {
p+=10;
tvalue=get_token_value(&p);
if(stricmp(tvalue,"MD5")==0) {
session->req.auth.algorithm=ALGORITHM_MD5;
}
else {
session->req.auth.algorithm=ALGORITHM_UNKNOWN;
}
}
else if(strnicmp(p,"cnonce=",7)==0) {
p+=7;
session->req.auth.cnonce=strdup(get_token_value(&p));
}
else if(strnicmp(p,"qop=",4)==0) {
p+=4;
tvalue=get_token_value(&p);
if(stricmp(tvalue,"auth")==0) {
session->req.auth.qop_value=QOP_AUTH;
}
else if (stricmp(tvalue,"auth-int")==0) {
session->req.auth.qop_value=QOP_AUTH_INT;
}
else {
session->req.auth.qop_value=QOP_UNKNOWN;
}
}
else if(strnicmp(p,"nc=",3)==0) {
p+=3;
session->req.auth.nonce_count=strdup(get_token_value(&p));
}
while(*p && !isspace(*p))
p++;
}
}
}
break;
case HEAD_LENGTH:
......@@ -2604,8 +2861,9 @@ static BOOL check_request(http_session_t * session)
if(!check_ars(session)) {
/* No authentication provided */
sprintf(str,"401 Unauthorized%s%s: Basic realm=\"%s\""
,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name);
sprintf(str,"401 Unauthorized%s%s: Basic realm=\"%s\"%s%s: Digest realm=\"%s\" nonce=\"%s\" qop=\"auth\""
,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name
,newline,get_header(HEAD_WWWAUTH),session->req.realm?session->req.realm:scfg.sys_name,"Secret-Nonce");
send_error(session,str);
return(FALSE);
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment