// HTTP authentication module used by surgeweb for calendar
// and file access via http.
// Supports: BASIC, DIGEST, OAUTH 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <errno.h>

#include "lib.h"
#define DMID 27 // # WAS DMID 28 BUT WANT trace.c with it's own one, so will be sure it's not coming from a bug in oauth...
#include "debug.h"
#include "lprintf.h"
#include "md5.h"
#include "tcplib.h"
#include "liboauth_oauth.h"

#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#include "oauth.h"

OAuth *oa_new()
{
	OAuth *oa=(OAuth *)calloc(1,sizeof(OAuth));
	return oa;
}

int oa_free(OAuth **x)
{
	OAuth *oa=*x;
	if (x==NULL) return TRUE;
	ffree(oa->auth_type);
	ffree(oa->user);

	ffree(oa->pass);				// BASIC

	ffree(oa->stored_digest);		// DIGEST

	ffree(oa->auth_token);			// OAUTH
	ffree(oa->token_secret);		// OAUTH

	ffree(oa->timestamp);	
	ffree(oa->nonce);
	ffree(oa->consumer_key);
	ffree(oa->app_secret);
	ffree(oa->signature);
	ffree(oa->signature_method);

	free(oa);
	*x = NULL;
	return TRUE;
}

//	callback to return authorisation header as needed for web request
int oa_auth_cb(void *param, char*challenge, char*method, char*url, char *header, int bfsz)
{
	OAuth *oa=(OAuth *)param;
	if(!oa || !oa->auth_type || *header) return FALSE;

	ncpy(header,"",bfsz-1);
	if(strcmpnc(oa->auth_type,"oauth")==0){
		oa_oauth_auth(oa, challenge, method, url, header);
	} else if(strcmpnc(oa->auth_type,"digest")==0){
		oa_digest_auth(oa, challenge, method, url, header);
	} else if(strcmpnc(oa->auth_type,"basic")==0){
		oa_basic_auth(oa, header, bfsz);
	} else{
		return FALSE;
	}
	return TRUE;
}

/// *** BASIC IMPLEMENTATION ***

OAuth *oa_new_basic(char *user, char*pass)
{
	OAuth *oa=oa_new();
	oa->user=strdup(user); 
	oa->pass=strdup(pass);
	oa->auth_type=strdup("basic");
	return oa;
}
int oa_basic_auth(OAuth *oa, char *header, int bfsz)
{
	char bf[BFSZ], bf2[BFSZ];
	if(!oa->user || !oa->pass) return FALSE;
	sprintf(bf,"%.100s:%.100s",oa->user,oa->pass);
	tcp_mime_encode_base64(bf,lib_strlen(bf)+1,bf2,BFSZ-1);
	lprintf(header,bfsz,"Basic %s",bf2);
	return TRUE;
}

/// *** DIGEST IMPLEMENTATION ***

OAuth *oa_new_digest(char *user, char*pass)
{
	OAuth *oa=oa_new();
	oa->user=strdup(user); 
	oa->pass=strdup(pass);
	oa->auth_type=strdup("digest");
	return oa;
}

int oa_digest_auth(OAuth *oa, char*challenge, char*method, char*url, char *digest)
{
	char bf[BFSZ], tmp[BFSZ],  ha1[BFSZ], ha2[BFSZ], response[BFSZ], *qop, nonceCount[BFSZ], clientNonce[BFSZ];
	unsigned char hash[16];
	static int client_count=0;
	Pair *pr=NULL;
	
	if(oa->stored_digest && lib_strlen(oa->stored_digest)>0){
		ncpy(digest,oa->stored_digest,BFSZ-1);
		return TRUE;
	}
	if(!lib_starts_with_nc(challenge,"Digest ")) return FALSE;
	challenge+=7;
	if(lib_strlen(challenge)==0) return FALSE;

	ncpy(bf,challenge,BFSZ-1);
	remove_unquoted_chars(bf,",");
	pair_token_split(bf, &pr,  tmp, BFSZ);

	qop=pair_find(pr,"qop");

// Authorization: Digest username="marijn@netwinsite.com", realm="ftpdav.test.ftpdav.com", nonce="082f5f1f113a7a586268", uri="/B.txt", 
//	response="6a18e971e6294d0ca703184f5f1cbb29", opaque="placeholder", cnonce="2dd8a12bfd3e3daf07fb8395e372b589", nc=00000001, qop="auth"

	lprintf(tmp,BFSZ,"%s:%s:%s",oa->user,pair_find(pr,"realm"),oa->pass);
	md5_digest(tmp,lib_strlen(tmp),hash);
	md5_print(hash,ha1,BFSZ);
	strlwr(ha1);

	if(strcmp(qop,"auth")==0 || lib_strlen(qop)==0){
		lprintf(tmp,BFSZ,"%s:%s",method,url);
		md5_digest(tmp,lib_strlen(tmp),hash);
		md5_print(hash,ha2,BFSZ);
		strlwr(ha2);
	}else{
		return FALSE;	// unimplemented...
	}

	client_count++;
	lprintf(nonceCount,BFSZ,"%08d", client_count);
	lprintf(clientNonce,BFSZ,"%06d%06d", myrand(), myrand());

	if(strcmp(qop,"auth")==0 || strcmp(qop,"auth-init")==0){
		lprintf(tmp,BFSZ,"%s:%s:%s:%s:%s:%s",ha1,pair_find(pr,"nonce"),nonceCount,clientNonce,qop,ha2);
		md5_digest(tmp,lib_strlen(tmp),hash);
		md5_print(hash,response,BFSZ);
		strlwr(response);
	}else{
		lprintf(tmp,BFSZ,"%s:%s:%s",ha1,pair_find(pr,"nonce"),ha2);
		md5_digest(tmp,lib_strlen(tmp),hash);
		md5_print(hash,response,BFSZ);
		strlwr(response);
	}

	lprintf(digest,BFSZ,"Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\", opaque=\"%s\", cnonce=\"%s\", nc=%s, qop=\"%s\"",
						oa->user, pair_find(pr,"realm"), pair_find(pr,"nonce"), url, response,  pair_find(pr,"opaque"), clientNonce, nonceCount, pair_find(pr,"qop"));
						
	return TRUE;
}


/// *** OAUTH IMPLEMENTATION ***

OAuth *oa_new_oauth(char *user, char*consumer_key, char*app_secret, char *auth_token, char*token_secret)
{
	OAuth *oa=oa_new();
	oa->user=strdup(user); 
	oa->auth_type=strdup("oauth");
	oa->consumer_key=strdup(consumer_key); 
	oa->app_secret=strdup(app_secret); 
	oa->auth_token=strdup(auth_token); 
	oa->token_secret=strdup(token_secret); 
	oa->state=OA_UNREQUESTED;
	return oa;
}
// update with stuff non persisted stuff
int oa_oauth_prepare(OAuth *oa, char*consumer_key, char*app_secret)
{
	if(oa->consumer_key) free(oa->consumer_key);
	oa->consumer_key=strdup(nullok(consumer_key));

	if(oa->app_secret) free(oa->app_secret);
	oa->app_secret=strdup(nullok(app_secret));

	return TRUE;
}

int oa_valid(OAuth *oa)
{
	if(!oa->auth_token || lib_strlen(oa->auth_token)==0) return FALSE;
	if(!oa->token_secret || lib_strlen(oa->token_secret)==0) return FALSE;
	if(oa->state<3) return FALSE;
	return TRUE;
}
int oa_auth_sooner(OAuth *oa, int sooner)
{
	oa->sooner=sooner;
	return TRUE;
}

int oa_oauth_auth(OAuth *oa, char*challenge, char*method, char*url, char *digest)
{
//	const char *request_token_uri = "http://term.ie/oauth/example/request_token.php";
	char *req_url = NULL, *req_hdr = NULL;
	char *postarg = NULL;
	int  argc;
	char **argv = NULL;

	ncpy(digest,"",BFSZ-1);

	// format a fullly specified or info retrival header as appropriate :-)
	if(oa->state==OA_UNREQUESTED){
		// Sign for temporary token request
		argc = oauth_split_url_parameters(url, &argv);
		oauth_sign_array2_process(&argc, &argv, NULL, OA_HMAC, method, oa->consumer_key, oa->app_secret, NULL, NULL);
		req_hdr = oauth_serialize_url_sep(argc, 1, argv, ", ", 6);
		req_url = oauth_serialize_url_sep(argc, 0, argv, "&", 1);
		oauth_free_array(&argc, &argv);
	}else if(oa->state>OA_UNREQUESTED && oa->state<OA_ACCESS_RECEIVED){
		// sign for permanent access token retrieval
		argc = oauth_split_url_parameters(url, &argv);
		oauth_sign_array2_process(&argc, &argv, NULL, OA_HMAC, method, oa->consumer_key, oa->app_secret, oa->auth_token, oa->token_secret);
		req_hdr = oauth_serialize_url_sep(argc, 1, argv, ", ", 6);
		req_url = oauth_serialize_url_sep(argc, 0, argv, "&", 1);
		oauth_free_array(&argc, &argv);
	}else if(oa->state==OA_ACCESS_RECEIVED){
		// normal request signing
		argc = oauth_split_url_parameters(url, &argv);
		oauth_sign_array2_process(&argc, &argv, NULL, OA_HMAC, method, oa->consumer_key, oa->app_secret, oa->auth_token, oa->token_secret);
		req_hdr = oauth_serialize_url_sep(argc, 1, argv, ", ", 6);
		req_url = oauth_serialize_url_sep(argc, 0, argv, "&", 1);
		oauth_free_array(&argc, &argv);
	}
	if(!url || !req_hdr){
		return FALSE;
	}
	/*
		oauth_consumer_key: The Consumer Key.
		oauth_signature_method: The signature method the Consumer used to sign the request.
		oauth_signature: The signature as defined in Signing Requests.
		oauth_timestamp: As defined in Nonce and Timestamp.
		oauth_nonce: As defined in Nonce and Timestamp.

		oauth_version: OPTIONAL. If present, value MUST be 1.0 . Service Providers MUST assume the protocol version to be 1.0 if 
			this parameter is not present. Service Providers’ response to non-1.0 value is left undefined.
		Additional parameters: Any additional parameters, as defined by the Service Provider.
	*/
	lprintf(digest,BFSZ,"OAuth realm=\"%s\", %s", url, req_hdr);

	return TRUE;
}

int oa_assign_request_token(OAuth *oa, char *token_secret, char *oauth_token, char *uid)
{
	if(oa->auth_token) free(oa->auth_token);
	if(oa->token_secret) free(oa->token_secret);

	oa->auth_token = strdup(oauth_token);
	oa->token_secret = strdup(token_secret);
	
	return TRUE;
}


int oa_generate_signature(OAuth *oa, char *sig)
{
	ncpy(sig,"foo",BFSZ-1);
	return TRUE;
}


int oa_request_token(OAuth *oa)
{
	// REQUEST: https://api.dropbox.com/1/oauth/request_token (POST)
	// OAUTH PARAM: http://oauth.net/core/1.0/#rfc.section.6.1.1
/*
		oauth_consumer_key: The Consumer Key.
		oauth_signature_method: The signature method the Consumer used to sign the request.
				 HMAC-SHA1, RSA-SHA1, and PLAINTEXT
		oauth_signature: The signature as defined in Signing Requests.
		oauth_timestamp: As defined in Nonce and Timestamp.
		oauth_nonce: As defined in Nonce and Timestamp.
		oauth_version: OPTIONAL. If present, value MUST be 1.0 . Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. Service Providers’ response to non-1.0 value is left undefined.
		Additional parameters: Any additional parameters, as defined by the Service Provider.
*/
	// DROPBOX PARAM: NONE
	// RESPONSE: oauth_token_secret=b9q1n5il4lcc&oauth_token=mh7an9dkrg59
	char oa_consumer_key[BFSZ], oa_nonce[BFSZ], oa_timestamp[BFSZ];
	char oa_signature[BFSZ], oa_signature_method[BFSZ];

	lprintf(oa_consumer_key,BFSZ,"at1p8pnn56jyf3y");	// or secret: r170deqeo9wo883 ??
	lprintf(oa_nonce,BFSZ,"%d",myrand());
	lprintf(oa_timestamp,BFSZ,"%d",int_time(NULL));
	lprintf(oa_signature_method,BFSZ,"RSA-SHA1");
	
	// use the other parameters to generate the signature
    //		c# : private string GenerateSignature(IRestRequest request)
	lprintf(oa_signature,BFSZ,"??");

	// get a response and store the resulting oa_auth_token"" and oa_token_secret=""

	return TRUE;	
}
int oa_dir_list(OAuth *oa)
{
	// REQUEST: https://api.dropbox.com/1/metadata/<root>/<path> (GET)
	// OAUTH param:??
	// DROPBOX PARAM:
/*
		file_limit: Default is 10,000 (max is 25,000). When listing a folder, the service will not report listings 
			containing more than the specified amount of files and will instead respond with a 406 (Not Acceptable) 
			status response.
		hash: Each call to /metadata on a folder will return a hash field, generated by hashing all of the metadata 
			contained in that response. On later calls to /metadata, you should provide that value via this parameter 
			so that if nothing has changed, the response will be a 304 (Not Modified) status code instead of the full, 
			potentially very large, folder listing. This parameter is ignored if the specified path is associated with 
			a file or if list=false. A folder shared between two users will have the same hash for each user.
		list: The strings true and false are valid values. true is the default. If true, the folder's metadata will 
			include a contents field with a list of metadata entries for the contents of the folder. If false, the 
			contents field will be omitted.
		include_deleted: If this parameter is set to true, then files and folders that have been deleted will also be 
			included in the metadata call.
		rev: If you include a particular revision number, then only the metadata for that revision will be returned.
		locale: The metadata returned will have its size field translated based on the given locale. For more information 
			see above.
*/	
	// RESPONSE: 
/* 
	{
		"size": "0 bytes",
		"hash": "37eb1ba1849d4b0fb0b28caf7ef3af52",
		"bytes": 0,
		"thumb_exists": false,
		"rev": "714f029684fe",
		"modified": "Wed, 27 Apr 2011 22:18:51 +0000",
		"path": "/Public",
		"is_dir": true,
		"icon": "folder_public",
		"root": "dropbox",
		"contents": [
			{
				"size": "0 bytes",
				"rev": "35c1f029684fe",
				"thumb_exists": false,
				"bytes": 0,
				"modified": "Mon, 18 Jul 2011 20:13:43 +0000",
				"client_mtime": "Wed, 20 Apr 2011 16:20:19 +0000",
				"path": "/Public/latest.txt",
				"is_dir": false,
				"icon": "page_white_text",
				"root": "dropbox",
				"mime_type": "text/plain",
				"revision": 220191
			}
		],
		"revision": 29007
	}
*/	

	// Send a request with the aforementioned get parameters + OAUTH header

	// receive response as JSON

	// parse JSON and output our directory listing

	return TRUE;	
}


/*
 Authorization: OAuth realm="http://sp.example.com/",
                oauth_consumer_key="0685bd9184jfhq22",
                oauth_token="ad180jjd733klru7",
                oauth_signature_method="HMAC-SHA1",
                oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D",
                oauth_timestamp="137131200",
                oauth_nonce="4572616e48616d6d65724c61686176",
                oauth_version="1.0"
*/

