/*
 *  This program 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.
 *
 *  This program 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *  Author : F. Tarek Rogers 		- 01 Sept 2004
 *           Russell Roy
 *           Wolfgang Beck
 *           Dragos Vingarzan   - 02 February 2006 vingarzan@gmail.com
 *                              - split in the auth architecture
 *                              - introduced AKAv1-MD5
 *           Frederique Aurouet	
 */

#if defined( __FreeBSD__) || defined(__DARWIN) || defined(__SUNOS)
#include <sys/types.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "md5.h"
#include "milenage.h"
#include "screen.hpp"

#define MAX_HEADER_LEN  2049
#define MD5_HASH_SIZE 16
#define HASH_HEX_SIZE 2*MD5_HASH_SIZE

extern char               *auth_uri;

/* AKA */

#define KLEN 16
typedef u_char K[KLEN];
#define RANDLEN 16
typedef u_char RAND[RANDLEN];
#define AUTNLEN 16
typedef u_char AUTN[AUTNLEN];

#define AKLEN 6
typedef u_char AK[AKLEN];
#define AMFLEN 2
typedef u_char AMF[AMFLEN];
#define MACLEN 8
typedef u_char MAC[MACLEN];
#define CKLEN 16
typedef u_char CK[CKLEN];
#define IKLEN 16
typedef u_char IK[IKLEN];
#define SQNLEN 6
typedef u_char SQN[SQNLEN];
#define AUTSLEN 14
typedef char AUTS[AUTSLEN];
#define AUTS64LEN 29
typedef char AUTS64[AUTS64LEN];
#define RESLEN 8
typedef unsigned char RES[RESLEN+1];
#define RESHEXLEN 17
typedef char RESHEX[RESHEXLEN];
#define OPLEN 16
typedef u_char OP[OPLEN];

AMF amfstar="\0\0";
SQN sqn_he={0x00,0x00,0x00,0x00,0x00,0x00};

/* end AKA */


int createAuthHeaderMD5(char * user, char * password, int password_len, char * method,
                     char * uri, char * msgbody, char * auth, 
                     char * algo, char * result);
int createAuthHeaderAKAv1MD5(char * user, char * OP,
                             char * AMF,
                             char * K,
                             char * method,
                             char * uri, char * msgbody, char * auth, char *algo,
                             char * result);


/* This function is from RFC 2617 Section 5 */

void hashToHex (md5_byte_t *_b_raw, unsigned char *_h)
{
    unsigned short i;
    unsigned char j;
    unsigned char *_b = (unsigned char *) _b_raw;

    for (i = 0; i < MD5_HASH_SIZE; i++) {
        j = (_b[i] >> 4) & 0xf;
        if (j <= 9) {
            _h[i * 2] = (j + '0');
        } else {
            _h[i * 2] = (j + 'a' - 10);
        }
        j = _b[i] & 0xf;
        if (j <= 9) {
            _h[i * 2 + 1] = (j + '0');
        } else {
            _h[i * 2 + 1] = (j + 'a' - 10);
        }
    };
    _h[HASH_HEX_SIZE] = '\0';
}

char *stristr (const char *s1, const char *s2) {
    char *cp = (char*) s1;
    char *p1, *p2, *endp;
    char l, r;
 
    endp = (char*)s1 + (strlen(s1) - strlen(s2)) ;
    while (*cp && (cp <= endp)) {
        p1 = cp;
        p2 = (char*)s2;
        while (*p1 && *p2) {
            l = toupper(*p1);
            r = toupper(*p2); 
            if (l != r) {
                break;
            }
            p1++;
            p2++;
        }
        if (*p2 == 0) {
            return cp;
        }
        cp++;
    }
    return 0;
}

int createAuthHeader(char * user, char * password, char * method,
                     char * uri, char * msgbody, char * auth, 
                     char * aka_OP,
                     char * aka_AMF,
                     char * aka_K,
                     char * result) {

    char algo[32]="MD5";
    char *start, *end;

    if ((start = stristr(auth, "Digest")) == NULL) {
        sprintf(result, "createAuthHeader: authentication must be digest");
        return 0;
    }

    if ((start = stristr(auth, "algorithm=")) != NULL) {
        start = start + strlen("algorithm=");
        if (*start == '"') { start++; }
        end = start + strcspn(start, " ,\"\r\n");
        strncpy(algo, start, end - start);
        algo[end - start] ='\0';

    }

    if (strncasecmp(algo, "MD5", 3)==0) {
	    return createAuthHeaderMD5(user,password,strlen(password),method,uri,msgbody,auth,algo,result);
	} else if (strncasecmp(algo, "AKAv1-MD5", 9)==0) {
		return createAuthHeaderAKAv1MD5(user, aka_OP,
                                                aka_AMF,
                                                aka_K,
                                                method,uri,msgbody,auth,algo,result);        
    }else{
    	sprintf(result, "createAuthHeader: authentication must use MD5 or AKAv1-MD5");
        return 0;
    }
	
	
}


int getAuthParameter(char *name, char *header, char *result, int len) {
  char *start, *end;

  start = stristr(header, name);
  if (!start) {
    result[0] = '\0';
    return 0;
  }
  start += strlen(name);
  if (*start++ != '=') {
    return getAuthParameter(name, start, result, len);
  }
  if (*start == '"') {
    start++;
    end = start;
    while (*end != '"' && *end) {
	end++;
    }
  } else {
    end = start + strcspn(start, " ,\"\r\n");
  }

  if (end - start >= len) {
    strncpy(result, start, len - 1);
    result[len - 1] = '\0';
  } else {
    strncpy(result, start, end - start);
    result[end - start] = '\0';
  }

  return end - start;
}

int createAuthHeaderMD5(char * user, char * password, int password_len, char * method,
                     char * uri, char * msgbody, char * auth, 
                     char * algo, char * result) {
                     	
    md5_byte_t ha1[MD5_HASH_SIZE], ha2[MD5_HASH_SIZE];
    md5_byte_t resp[MD5_HASH_SIZE], body[MD5_HASH_SIZE];
    unsigned char ha1_hex[HASH_HEX_SIZE+1], ha2_hex[HASH_HEX_SIZE+1];
    unsigned char resp_hex[HASH_HEX_SIZE+1], body_hex[HASH_HEX_SIZE+1];
    char tmp[MAX_HEADER_LEN], authtype[16], cnonce[32], nc[32], opaque[64];
    static unsigned int mync = 1;
    int has_opaque = 0;
    md5_state_t Md5Ctx;
    char tmpbuf[2048];

    // Extract the Auth Type - If not present, using 'none' 
    cnonce[0] = '\0';
    if (getAuthParameter("qop", auth, authtype, sizeof(authtype))) {
        sprintf(cnonce, "%x", rand());
        sprintf(nc, "%08x", mync);
    }

    // Extract the Opaque value - if present
    if (getAuthParameter("opaque", auth, opaque, sizeof(opaque))) {
        has_opaque = 1;
    }

    // Extract the Realm 
    if (!getAuthParameter("realm", auth, tmp, sizeof(tmp))) {
        sprintf(result, "createAuthHeaderMD5: couldn't parse realm in '%s'", auth);
        return 0;
    }

    // Load in A1 
    md5_init(&Md5Ctx);
    md5_append(&Md5Ctx, user, strlen(user));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, tmp, strlen(tmp));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, password, password_len);
    md5_finish(&Md5Ctx, ha1);
    hashToHex(&ha1[0], &ha1_hex[0]);

    sprintf(result, "Digest username=\"%s\",realm=\"%s\"",user,tmp);

    // Construct the URI 
    if (auth_uri == NULL) {
      sprintf(tmp, "sip:%s", uri);
    } else {
      sprintf(tmp, "sip:%s", auth_uri);
    }

    // If using Auth-Int make a hash of the body - which is NULL for REG 
    if (stristr(authtype, "auth-int") != NULL) {
        md5_init(&Md5Ctx);
        md5_append(&Md5Ctx, msgbody, strlen(msgbody));
        md5_finish(&Md5Ctx, body);
        hashToHex(&body[0], &body_hex[0]);
        sprintf(authtype, "auth-int");
    }

    // Load in A2 
    md5_init(&Md5Ctx);
    md5_append(&Md5Ctx, method, strlen(method));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, tmp, strlen(tmp));
    if (stristr(authtype, "auth-int") != NULL) {
        md5_append(&Md5Ctx, ":", 1);
        md5_append(&Md5Ctx, (md5_byte_t *) &body_hex, HASH_HEX_SIZE);
    }
    md5_finish(&Md5Ctx, ha2);
    hashToHex(&ha2[0], &ha2_hex[0]);

    if (cnonce[0] != '\0') {
        snprintf(tmpbuf, 2048, ",cnonce=\"%s\",nc=%s,qop=%s",cnonce,nc,authtype);
        strcat(result,tmpbuf);
    }
    snprintf(tmpbuf, 2048, ",uri=\"%s\"",tmp);
    strcat(result,tmpbuf);

    // Extract the Nonce 
    if (!getAuthParameter("nonce", auth, tmp, sizeof(tmp))) {
        sprintf(result, "createAuthHeader: couldn't parse nonce");
        return 0;
    }

    md5_init(&Md5Ctx);
    md5_append(&Md5Ctx, (md5_byte_t *) &ha1_hex, HASH_HEX_SIZE);
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, tmp, strlen(tmp));
    if (cnonce[0] != '\0') {
        md5_append(&Md5Ctx, ":", 1);
        md5_append(&Md5Ctx, nc, strlen(nc));
        md5_append(&Md5Ctx, ":", 1);
        md5_append(&Md5Ctx, cnonce, strlen(cnonce));
        md5_append(&Md5Ctx, ":", 1);
        md5_append(&Md5Ctx, authtype, strlen(authtype));
    }
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, (md5_byte_t *) &ha2_hex, HASH_HEX_SIZE);
    md5_finish(&Md5Ctx, resp);
    hashToHex(&resp[0], &resp_hex[0]);

    snprintf(tmpbuf, 2048, ",nonce=\"%s\",response=\"%s\",algorithm=%s",tmp,resp_hex,algo);
    strcat(result,tmpbuf);

    if (has_opaque) {
        snprintf(tmpbuf, 2048, ",opaque=\"%s\"",opaque);
        strcat(result,tmpbuf);
    }

    return 1;
}

int createAuthResponseMD5(char * user, char * password, int password_len, char * method,
                     char * uri, char * realm, char *nonce, unsigned char * result) {
    md5_byte_t ha1[MD5_HASH_SIZE], ha2[MD5_HASH_SIZE];
    md5_byte_t resp[MD5_HASH_SIZE];
    unsigned char ha1_hex[HASH_HEX_SIZE+1], ha2_hex[HASH_HEX_SIZE+1];
    char tmp[MAX_HEADER_LEN];
    md5_state_t Md5Ctx;

    // Load in A1 
    md5_init(&Md5Ctx);
    md5_append(&Md5Ctx, user, strlen(user));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, realm, strlen(realm));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, password, password_len);
    md5_finish(&Md5Ctx, ha1);
    hashToHex(&ha1[0], &ha1_hex[0]);

    if (auth_uri) {
      sprintf(tmp, "sip:%s", auth_uri);
    } else {
      strcpy(tmp, uri);
    }

    // Load in A2 
    md5_init(&Md5Ctx);
    md5_append(&Md5Ctx, method, strlen(method));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, tmp, strlen(tmp));
    md5_finish(&Md5Ctx, ha2);
    hashToHex(&ha2[0], &ha2_hex[0]);

    md5_init(&Md5Ctx);
    md5_append(&Md5Ctx, (md5_byte_t *) &ha1_hex, HASH_HEX_SIZE);
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, nonce, strlen(nonce));
    md5_append(&Md5Ctx, ":", 1);
    md5_append(&Md5Ctx, (md5_byte_t *) &ha2_hex, HASH_HEX_SIZE);
    md5_finish(&Md5Ctx, resp);
    hashToHex(&resp[0], result);

    return 1;
}

int verifyAuthHeader(char * user, char * password, char * method, char * auth) {
  char algo[MAX_HEADER_LEN];
  unsigned char result[HASH_HEX_SIZE];
  char response[HASH_HEX_SIZE + 2];
  char realm[MAX_HEADER_LEN];
  char nonce[MAX_HEADER_LEN];
  char uri[MAX_HEADER_LEN];
  char *start;
  int len;

  if ((start = stristr(auth, "Digest")) == NULL) {
    WARNING("verifyAuthHeader: authentication must be digest is %s", auth);
    return 0;
  }

  len = getAuthParameter("algorithm", auth, algo, sizeof(algo));
  if (algo[0] == '\0') {
    strcpy(algo, "MD5");
  }
  if (strncasecmp(algo, "MD5", 3)==0) {
    getAuthParameter("realm", auth, realm, sizeof(realm));
    getAuthParameter("uri", auth, uri, sizeof(uri));
    getAuthParameter("nonce", auth, nonce, sizeof(nonce));
    createAuthResponseMD5(user,password,strlen(password),method,uri,realm,nonce,result);
    getAuthParameter("response", auth, response, sizeof(response));
    return !strcmp((char *)result, response);
  }else{
    WARNING("createAuthHeader: authentication must use MD5 or AKAv1-MD5, value is '%s'", algo);
    return 0;
  }
}



/*"
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";*/
static int base64_val(char x)\
{
	switch(x){
		case '=': return -1;
		case 'A': return 0;
		case 'B': return 1;
		case 'C': return 2;
		case 'D': return 3;
		case 'E': return 4;
		case 'F': return 5;
		case 'G': return 6;
		case 'H': return 7;
		case 'I': return 8;
		case 'J': return 9;
		case 'K': return 10;
		case 'L': return 11;
		case 'M': return 12;
		case 'N': return 13;
		case 'O': return 14;
		case 'P': return 15;
		case 'Q': return 16;
		case 'R': return 17;
		case 'S': return 18;
		case 'T': return 19;
		case 'U': return 20;
		case 'V': return 21;
		case 'W': return 22;
		case 'X': return 23;
		case 'Y': return 24;
		case 'Z': return 25;
		case 'a': return 26;
		case 'b': return 27;
		case 'c': return 28;
		case 'd': return 29;
		case 'e': return 30;
		case 'f': return 31;
		case 'g': return 32;
		case 'h': return 33;
		case 'i': return 34;
		case 'j': return 35;
		case 'k': return 36;
		case 'l': return 37;
		case 'm': return 38;
		case 'n': return 39;
		case 'o': return 40;
		case 'p': return 41;
		case 'q': return 42;
		case 'r': return 43;
		case 's': return 44;
		case 't': return 45;
		case 'u': return 46;
		case 'v': return 47;
		case 'w': return 48;
		case 'x': return 49;
		case 'y': return 50;
		case 'z': return 51;
		case '0': return 52;
		case '1': return 53;
		case '2': return 54;
		case '3': return 55;
		case '4': return 56;
		case '5': return 57;
		case '6': return 58;
		case '7': return 59;
		case '8': return 60;
		case '9': return 61;
		case '+': return 62;
		case '/': return 63;
	}
	return 0;
}

char * base64_decode_string( const char *buf, unsigned int len, int *newlen )
{
	int i,j,x1,x2,x3,x4;
	char *out;
	out = (char *)malloc( ( len * 3/4 ) + 8 );
	for(i=0,j=0;i+3<len;i+=4){
		x1=base64_val(buf[i]);
		x2=base64_val(buf[i+1]);
		x3=base64_val(buf[i+2]);
		x4=base64_val(buf[i+3]);
		out[j++]=(x1<<2) | ((x2 & 0x30)>>4);
		out[j++]=((x2 & 0x0F)<<4) | ((x3 & 0x3C)>>2);
		out[j++]=((x3 & 0x03)<<6) | (x4 & 0x3F);
	}
	if (i<len) {
		x1 = base64_val(buf[i]);
		if (i+1<len)
			x2=base64_val(buf[i+1]);
		else 
			x2=-1;
		if (i+2<len)		
			x3=base64_val(buf[i+2]);
		else
			x3=-1;
		if(i+3<len)	
			x4=base64_val(buf[i+3]);
		else x4=-1;
		if (x2!=-1) {
			out[j++]=(x1<<2) | ((x2 & 0x30)>>4);
			if (x3==-1) {
				out[j++]=((x2 & 0x0F)<<4) | ((x3 & 0x3C)>>2);
				if (x4==-1) {
					out[j++]=((x3 & 0x03)<<6) | (x4 & 0x3F);
				}
			}
		}
			
	}
			
	out[j++] = 0;
	*newlen=j;
	return out;
}

char base64[64]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char * base64_encode_string( const char *buf, unsigned int len, int *newlen )
{
	int i,k;
	int triplets,rest;
	char *out,*ptr;	

	triplets = len/3;
	rest = len%3;
	out = (char *)malloc( ( triplets * 4 ) + 8 );
	
	ptr = out;
	for(i=0;i<triplets*3;i+=3){
		k = (((unsigned char) buf[i])&0xFC)>>2;
		*ptr=base64[k];ptr++;

		k = (((unsigned char) buf[i])&0x03)<<4;
		k |=(((unsigned char) buf[i+1])&0xF0)>>4;
		*ptr=base64[k];ptr++;

		k = (((unsigned char) buf[i+1])&0x0F)<<2;
		k |=(((unsigned char) buf[i+2])&0xC0)>>6;
		*ptr=base64[k];ptr++;

		k = (((unsigned char) buf[i+2])&0x3F);
		*ptr=base64[k];ptr++;
	}
	i=triplets*3;
	switch(rest){
		case 0:
			break;
		case 1:
			k = (((unsigned char) buf[i])&0xFC)>>2;
			*ptr=base64[k];ptr++;

			k = (((unsigned char) buf[i])&0x03)<<4;
			*ptr=base64[k];ptr++;

			*ptr='=';ptr++;

			*ptr='=';ptr++;
			break;
		case 2:
			k = (((unsigned char) buf[i])&0xFC)>>2;
			*ptr=base64[k];ptr++;

			k = (((unsigned char) buf[i])&0x03)<<4;
			k |=(((unsigned char) buf[i+1])&0xF0)>>4;
			*ptr=base64[k];ptr++;

			k = (((unsigned char) buf[i+1])&0x0F)<<2;
			*ptr=base64[k];ptr++;

			*ptr='=';ptr++;
			break;
	}
	// fprintf(stderr,"base64=%.*s >> %d\n",ptr-out,out,ptr-out);
	*newlen = ptr-out;
	return out;
}




char hexa[16]="0123456789abcdef";
int createAuthHeaderAKAv1MD5(char * user, char * aka_OP, 
                     char * aka_AMF, 
                     char * aka_K, 
                     char * method,
                     char * uri, char * msgbody, char * auth, char *algo,
                     char * result) {
                     	
  char tmp[MAX_HEADER_LEN];
  char *start, *end;
  int has_auts = 0, resuf = 1;
  char *nonce64, *nonce;
  int noncelen;
  RESHEX resp_hex;
  AMF amf;
  OP op;
  RAND rnd;
  AUTS auts_bin;
  AUTS64 auts_hex;
  MAC mac,xmac;
  SQN sqn, sqnxoraka, sqn_ms;
  K k;
  RES res;
  CK ck;
  IK ik;
  AK ak;
  int i;

  // Extract the Nonce 
  if ((start = stristr(auth, "nonce=")) == NULL) {
      sprintf(result, "createAuthHeaderAKAv1MD5: couldn't parse nonce");
      return 0;
  }
  start = start + strlen("nonce=");
  if (*start == '"') { start++; }
  end = start + strcspn(start, " ,\"\r\n");
  strncpy(tmp, start, end - start);
  tmp[end - start] ='\0';

  /* Compute the AKA RES */
  resp_hex[0]=0;
  nonce64 = tmp;
  nonce = base64_decode_string(nonce64,end-start,&noncelen);
  if (noncelen<RANDLEN+AUTNLEN) {
    sprintf(result,"createAuthHeaderAKAv1MD5 : Nonce is too short %d < %d expected \n",
    noncelen,RANDLEN+AUTNLEN);
    if(nonce) free(nonce);
    return 0;
  }
  memcpy(rnd,nonce,RANDLEN);
  memcpy(sqnxoraka,nonce+RANDLEN,SQNLEN);
  memcpy(mac,nonce+RANDLEN+SQNLEN+AMFLEN,MACLEN);
  memcpy(k,aka_K,KLEN);
  memcpy(amf,aka_AMF,AMFLEN);
  memcpy(op,aka_OP,OPLEN);

  /* Compute the AK, response and keys CK IK */
  f2345(k,rnd,res,ck,ik,ak,op);
  res[RESLEN]='\0';

  /* Compute sqn encoded in AUTN */
  for (i=0; i < SQNLEN; i++)
    sqn[i] = sqnxoraka[i] ^ ak[i];

  /* compute XMAC */
  f1(k,rnd,sqn,(unsigned char *) aka_AMF,xmac,op);
  if (memcmp(mac,xmac,MACLEN)!=0) {
    sprintf(result,"createAuthHeaderAKAv1MD5 : MAC != eXpectedMAC -> Server might not know the secret (man-in-the-middle attack?) \n");
    //return 0;
  }

  /* Check SQN, compute AUTS if needed and authorization parameter */
  /* the condition below is wrong.
   * Should trigger synchronization when sqn_ms>>3!=sqn_he>>3 for example.
   * Also, we need to store the SQN per user or put it as auth parameter. */
  if (1/*sqn[5] > sqn_he[5]*/) {
    sqn_he[5] = sqn[5];
    has_auts = 0;
    /* RES has to be used as password to compute response */
    resuf = createAuthHeaderMD5(user, (char *) res, RESLEN, method, uri, msgbody, auth, algo, result);   
  } else {
    sqn_ms[5] = sqn_he[5] + 1;
    f5star(k, rnd, ak, op);
    for(i=0; i<SQNLEN; i++)
      auts_bin[i]=sqn_ms[i]^ak[i];
    f1star(k, rnd, sqn_ms, amf, (unsigned char * ) (auts_bin+SQNLEN), op);
    has_auts = 1;
    /* When re-synchronisation occurs an empty password has to be used */
    /* to compute MD5 response (Cf. rfc 3310 section 3.2) */
    resuf=createAuthHeaderMD5(user,"",0,method,uri,msgbody,auth,algo,result);
  }
  if (has_auts) {
    /* Format data for output in the SIP message */
    for(i=0;i<AUTSLEN;i++){
      auts_hex[2*i]=hexa[(auts_bin[i]&0xF0)>>4];
      auts_hex[2*i+1]=hexa[auts_bin[i]&0x0F];
    }
    auts_hex[AUTS64LEN-1]=0;

    sprintf(result, "%s,auts=\"%s\"",result,auts_hex);
  }
  free(nonce);
  return 1;
}


