/*
 * Digest credentials parser
 *
 * Copyright (C) 2001-2003 FhG Fokus
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * History:
 * --------
 * 2003-03-02: Added parse_domain function (janakj)
 */


#include "digest_parser.h"
#include "../../trim.h"	  /* trim_leading */
#include <string.h>		  /* strncasecmp */
#include "param_parser.h" /* Digest parameter name parser */
#include "../../ut.h"	  /* q_memchr */


#define DIGEST_SCHEME "digest"
#define DIG_LEN 6

#define QOP_AUTH_STR "auth"
#define QOP_AUTH_STR_LEN 4

#define QOP_AUTHINT_STR "auth-int"
#define QOP_AUTHINT_STR_LEN 8

#define ALG_MD5_STR "MD5"
#define ALG_MD5_STR_LEN 3

#define ALG_MD5SESS_STR "MD5-sess"
#define ALG_MD5SESS_STR_LEN 8


/*
 * Parse quoted string in a parameter body
 * return the string without quotes in _r
 * parameter and update _s to point behind the
 * closing quote
 */
static inline int parse_quoted(str *_s, str *_r)
{
	char *end_quote;

	/* The string must have at least
	      * surrounding quotes
	      */
	if(_s->len < 2) {
		return -1;
	}

	/* Skip opening quote */
	_s->s++;
	_s->len--;


	/* Find closing quote */
	end_quote = q_memchr(_s->s, '\"', _s->len);

	/* Not found, return error */
	if(!end_quote) {
		return -2;
	}

	/* Let _r point to the string without
	      * surrounding quotes
	      */
	_r->s = _s->s;
	_r->len = end_quote - _s->s;

	/* Update _s parameter to point
	      * behind the closing quote
	      */
	_s->len -= (end_quote - _s->s + 1);
	_s->s = end_quote + 1;

	/* Everything went OK */
	return 0;
}


/*
 * Parse unquoted token in a parameter body
 * let _r point to the token and update _s
 * to point right behind the token
 */
static inline int parse_token(str *_s, str *_r)
{
	int i;

	/* Save the beginning of the
	      * token in _r->s
	      */
	_r->s = _s->s;

	/* Iterate through the
	      * token body
	      */
	for(i = 0; i < _s->len; i++) {

		/* All these characters
		      * mark end of the token
		      */
		switch(_s->s[i]) {
			case ' ':
			case '\t':
			case '\r':
			case '\n':
			case ',':
				/* So if you find
			      * any of them
			      * stop iterating
			      */
				goto out;
		}
	}
out:
	/* Empty token is error */
	if(i == 0) {
		return -2;
	}

	/* Save length of the token */
	_r->len = i;

	/* Update _s parameter so it points
	      * right behind the end of the token
	      */
	_s->s = _s->s + i;
	_s->len -= i;

	/* Everything went OK */
	return 0;
}


/*
 * Parse a digest parameter
 */
static inline int parse_digest_param(str *_s, dig_cred_t *_c)
{
	dig_par_t t;
	str *ptr;
	str dummy;

	/* Get type of the parameter */
	if(parse_param_name(_s, &t) < 0) {
		return -1;
	}

	_s->s++; /* skip = */
	_s->len--;

	/* Find the beginning of body */
	trim_leading(_s);

	if(_s->len == 0) {
		return -2;
	}

	/* Decide in which attribute the
	      * body content will be stored
	      */
	switch(t) {
		case PAR_USERNAME:
			ptr = &_c->username.whole;
			break;
		case PAR_REALM:
			ptr = &_c->realm;
			break;
		case PAR_NONCE:
			ptr = &_c->nonce;
			break;
		case PAR_URI:
			ptr = &_c->uri;
			break;
		case PAR_RESPONSE:
			ptr = &_c->response;
			break;
		case PAR_CNONCE:
			ptr = &_c->cnonce;
			break;
		case PAR_OPAQUE:
			ptr = &_c->opaque;
			break;
		case PAR_QOP:
			ptr = &_c->qop.qop_str;
			break;
		case PAR_NC:
			ptr = &_c->nc;
			break;
		case PAR_ALGORITHM:
			ptr = &_c->alg.alg_str;
			break;
		case PAR_OTHER:
			ptr = &dummy;
			break;
		default:
			ptr = &dummy;
			break;
	}

	/* If the first character is quote, it is
	      * a quoted string, otherwise it is a token
	      */
	if(_s->s[0] == '\"') {
		if(parse_quoted(_s, ptr) < 0) {
			return -3;
		}
	} else {
		if(parse_token(_s, ptr) < 0) {
			return -4;
		}
	}

	return 0;
}


/*
 * Parse qop parameter body
 */
void parse_qop(struct qp *_q)
{
	str s;

	s.s = _q->qop_str.s;
	s.len = _q->qop_str.len;

	trim(&s);

	if(s.len == 0) {
		_q->qop_parsed = QOP_UNSPEC;
	} else if((s.len == QOP_AUTH_STR_LEN)
			  && !strncasecmp(s.s, QOP_AUTH_STR, QOP_AUTH_STR_LEN)) {
		_q->qop_parsed = QOP_AUTH;
	} else if((s.len == QOP_AUTHINT_STR_LEN)
			  && !strncasecmp(s.s, QOP_AUTHINT_STR, QOP_AUTHINT_STR_LEN)) {
		_q->qop_parsed = QOP_AUTHINT;
	} else {
		_q->qop_parsed = QOP_OTHER;
	}
}


/*
 * Parse algorithm parameter body
 */
static inline void parse_algorithm(struct algorithm *_a)
{
	str s;

	s.s = _a->alg_str.s;
	s.len = _a->alg_str.len;

	trim(&s);

	if((s.len == ALG_MD5_STR_LEN)
			&& !strncasecmp(s.s, ALG_MD5_STR, ALG_MD5_STR_LEN)) {
		_a->alg_parsed = ALG_MD5;
	} else if((s.len == ALG_MD5SESS_STR_LEN)
			  && !strncasecmp(s.s, ALG_MD5SESS_STR, ALG_MD5SESS_STR_LEN)) {
		_a->alg_parsed = ALG_MD5SESS;
	} else {
		_a->alg_parsed = ALG_OTHER;
	}
}


/*
 * Parse username for user and domain parts
 */
static inline void parse_username(struct username *_u)
{
	char *d;

	_u->user = _u->whole;
	if(_u->whole.len <= 2)
		return;

	/* get domain - it can be: username@domain */
	d = q_memchr(_u->whole.s, '@', _u->whole.len);

	if(d) {
		_u->domain.s = d + 1;
		_u->domain.len = _u->whole.len - (d - _u->whole.s) - 1;
		_u->user.len = d - _u->user.s;
	}

	/* get user - it can be: sip:username@domain */
	d = q_memchr(_u->user.s, ':', _u->user.len);
	if(d) {
		_u->user.len = _u->user.s + _u->user.len - d - 1;
		_u->user.s = d + 1;
	}
}


/*
 * Parse Digest credentials parameter, one by one
 */
static inline int parse_digest_params(str *_s, dig_cred_t *_c)
{
	char *comma;

	do {
		/* Parse the first parameter */
		if(parse_digest_param(_s, _c) < 0) {
			return -1;
		}

		/* Try to find the next parameter */
		comma = q_memchr(_s->s, ',', _s->len);
		if(comma) {
			/* Yes, there is another,
			      * remove any leading white-spaces
			      * and let _s point to the next
			      * parameter name
			      */
			_s->len -= comma - _s->s + 1;
			_s->s = comma + 1;
			trim_leading(_s);
		}
	} while(comma); /* Repeat while there are next parameters */

	/* Parse QOP body if the parameter was present */
	if(_c->qop.qop_str.s != 0) {
		parse_qop(&_c->qop);
	}

	/* Parse algorithm body if the parameter was present */
	if(_c->alg.alg_str.s != 0) {
		parse_algorithm(&_c->alg);
	}

	if(_c->username.whole.s != 0) {
		parse_username(&_c->username);
	}

	return 0;
}


/*
 * We support Digest authentication only
 *
 * Returns:
 *  0 - if everything is OK
 * -1 - Error while parsing
 *  1 - Unknown scheme
 */
int parse_digest_cred(str *_s, dig_cred_t *_c)
{
	str tmp;

	/* Make a temporary copy, we are
	      * going to modify it
	      */
	tmp.s = _s->s;
	tmp.len = _s->len;

	/* Remove any leading spaces, tabs, \r and \n */
	trim_leading(&tmp);

	/* Check the string length */
	if(tmp.len < (DIG_LEN + 1))
		return 1; /* Too short, unknown scheme */

	/* Now test, if it is digest scheme, since it is the only
	      * scheme we are able to parse here
	      */
	if(!strncasecmp(tmp.s, DIGEST_SCHEME, DIG_LEN)
			&& ((tmp.s[DIG_LEN] == ' ') || /* Test for one of LWS chars */
					(tmp.s[DIG_LEN] == '\r') || (tmp.s[DIG_LEN] == '\n')
					|| (tmp.s[DIG_LEN] == '\t') || (tmp.s[DIG_LEN] == ','))) {
		/* Scheme is Digest */
		tmp.s += DIG_LEN + 1;
		tmp.len -= DIG_LEN + 1;

		/* Again, skip all white-spaces */
		trim_leading(&tmp);

		/* And parse digest parameters */
		if(parse_digest_params(&tmp, _c) < 0) {
			return -2; /* We must not return -1 in this function ! */
		} else {
			return 0;
		}
	} else {
		return 1; /* Unknown scheme */
	}
}


/*
 * Initialize a digest credentials structure
 */
void init_dig_cred(dig_cred_t *_c)
{
	memset(_c, 0, sizeof(dig_cred_t));
}
