/*
 	keyring.c
 
 	Key management functions of 'sks' project
 	
	Copyright (C) 2004-2007  Manuel Pancorbo Castro

	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.

	Manuel Pancorbo Castro <mpancorbo@gmail.com>
 
*/

#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
#include <zlib.h>
#include "gflib.h"
#include "binasc.h"
#include "sks.h"



const char pubkey_head  [] = "-----SKS KEY-----";
const char pubkey_magic [] = "key: ";

keyring default_kr = {NULL, NULL, 0};
extern char err_open_failed[];
extern char err_bad_public_key[];
extern char err_corr_public_key[];
extern char msg_added_key[];
extern char msg_key_exists[];
extern char msg_deleted_key[];
extern char ask_idnum[];

FILE * chkopen( char * s, char * mode )
{
  FILE * result = fopen(s,mode);
  if (!result)
  {
    fputs(CHCONV(err_open_failed), stderr );
    fputs( s, stderr );
	fputs("\n", stderr);
  }
  return result;
}

void get_full_path(char *name, char * buf)
{
	strcpy(buf, "");

#ifdef LINUX
	
	struct passwd *pwd;
	pwd = getpwuid(getuid());
	strcat(buf, pwd->pw_dir);
	strcat(buf, name);

#elif defined WIN32
	strcat(buf, getenv(HOME_VAR));
	strcat(buf, KEY_DIR);
	strcat(buf, name);
#endif	
}

FILE * open_keyring(char * mode )
{
	char buf[SMALL_BUF];
	FILE *result;
	
	get_full_path(KEYRING, buf);
	result = fopen(buf,mode);
	if(result == NULL){
		if(mode[0] == 'r')	result = fopen(buf, "w+");
	}
	if(result == NULL){
	    fputs( CHCONV(err_open_failed), stderr );
	    fputs( buf, stderr );
		fputs("\n", stderr);
	}	
	return result;
}

FILE * open_prngfile(void)
{
	char buf[SMALL_BUF];
	FILE *result;
	
	get_full_path(PRNGFILE, buf);
	result = fopen(buf, "r+");
	if(result == NULL)
		result = fopen(buf, "w+");
		
	return result;
}

ERROR get_keyid(PubKey key, unsigned long *id)
{
	static unsigned long int check =
	    (GF_M << 24) + (GF_T << 16) + (EC_B << 4) 
		#ifdef EC_A
		+ EC_A
		#endif
		;	
		
	*id = crc32(check, key, KEY_SIZE);
	return 0;
}


ERROR write_pubkey(FILE *ptr,  keychain *key)
{
		
	assert((ptr != NULL) && (key != NULL));
	unsigned long int id;
	fputs(pubkey_magic, ptr);

	fwritePlus(key->val, 1, KEY_SIZE, ptr, 0);
	
	id = key->id;	
	/** Three low bytes of id. are output as crc-like integrity check **/
	fputcPlus((id & 0xFF), ptr, 0);
	id >>= 8;
	fputcPlus((id & 0xFF), ptr, 0);
	id >>= 8;
	fputcPlus((id & 0xFF), ptr, 0);
	flushArmour(ptr);
	
	fputs(key->lab, ptr);
	fputs("\n", ptr);
	return 0;
}

ERROR get_pubkey(FILE *ptr, keychain *b)
{
	char buf[SMALL_BUF];
	int size, len;
	unsigned long int id1, a;

	
	len = strlen(pubkey_magic);
	fread(buf, 1, len, ptr);
	if(strncmp(buf, pubkey_magic, len))
		return -3;
	
	size = freadPlus(b->val, 1, KEY_SIZE, ptr, 0);
	if(size != KEY_SIZE) return -2;
	
	/** Check integrity **/
	get_keyid(b->val, &(b->id));
	
	freadPlus(buf, 1, 3, ptr, 0);
	id1 = buf[0] & 0xff;
	a = buf[1] & 0xff;
	id1 += a  << 8;
	a = buf[2] & 0xff;
	id1 += a << 16;
	if(id1 != (b->id  & 0x00ffffff)) return -1;
	
	burnBinasc();
	return 0;

}


ERROR new_key_chain(keyring *kr)
{
	assert(kr != NULL);
	kr->vector = realloc(kr->vector, (kr->N + 1) * sizeof(keychain));
	if(kr->vector == NULL) return -1;
	kr->actual = &(kr->vector[kr->N]);

	kr->N++;
	return 0;
}

void close_keyring(keyring *kr)
{
	
	if(kr->N == 0) return; /** Nothing to do **/
	
	free(kr->vector);
	kr->N = 0;
	kr->vector = kr->actual = NULL;
}

ERROR add_to_keyring(keyring *kr, keychain *a)
{
	assert(kr != NULL);
	assert(a != NULL);

	if (new_key_chain(kr)) return -1;
	memcpy(kr->actual, a, sizeof(keychain));

	return 0;
}



ERROR load_keyring(FILE *inp, keyring *kr)
{
	char buf[SMALL_BUF];
	ERROR err = 0;
	int main_kr = 0, i = 0;
	keychain  a;


	assert(kr != NULL);
	kr->N = 0;

	if(inp == NULL){
	      #ifdef WIN32
		  #include <dirent.h>
		if(kr == &default_kr){
			char dir_buf[SMALL_BUF];
			DIR *check;

			strcpy(dir_buf, getenv(HOME_VAR));
			strcat(dir_buf, KEY_DIR);
		 	if((check = opendir(dir_buf)) == NULL){
		 		char order[SMALL_BUF];

				strcpy(order, "mkdir \"");
				strcat(order, dir_buf);
				strcat(order, "\"");
				system(order);
		  	}
		}
		#endif
		if((inp = open_keyring("r")) == NULL) return -1;
		main_kr = 1;
	}

	while(!position(inp, pubkey_head) && !feof(inp)){
		if((err = get_pubkey(inp, &a))){
			if(err == -3){
				fputs( CHCONV(err_bad_public_key), stderr );
				fputs( pubkey_magic, stderr );
				fputs( "\n", stderr );
			}
			else
				fputs( CHCONV(err_corr_public_key), stderr );
		}

		fgets(a.lab, SMALL_BUF, inp);
		
		add_to_keyring(kr, &a);
		if (kr == &default_kr){
			fgets(buf, SMALL_BUF, inp);
			
		}
		++i;
	}
	kr->actual = &(kr->vector[0]);
	if(main_kr)
		fclose(inp);

	return err;
}

ERROR write_keyring()
{
	keyring *kr = &default_kr;
	FILE *kr_file;
	int i;
	
	kr_file = open_keyring("w");
	for(i = 0; i < kr->N; ++i){
		fputs(pubkey_head, kr_file), fputs("\n", kr_file);
		if(write_pubkey(kr_file, kr->vector + i )) return -1;
		
	}
	fclose(kr_file);

	return 0;
}

int search_pubkey(keyring *inp, int *ext, char **pattern, FILE *out, int mode)
{
	char idstring[16];
	ERROR err = 0;
	int i, j, hit =  0, total = 0;
	

	for(i = 0; i < inp->N; ++i){
		if(pattern == NULL) hit = 1;
		else{
			/*get_keyid(inp->vector[i].val, &id);*/
			sprintf(idstring, "%08lx", inp->vector[i].id);
			for(j = 0; (pattern[j] != NULL) && (hit == 0); ++j){
				if((str_match(inp->vector[i].lab, pattern[j]))  ||
					(str_match(idstring, pattern[j])) ){
					hit = 1;
				}
			}
		}
		if(hit){
			if(ext != NULL){
				*ext = i;
				++ext;
			}
			if(out != NULL){ /** Put out mode **/
				if(mode == 'l' || mode == 'f'){ 
					fprintf(out, " [%08lx]: ", inp->vector[i].id);
					fputs(inp->vector[i].lab, out);
					if(mode == 'f'){
						fputs("  ", out);
						/*
						for(j = 0; j < KEY_SIZE; ++j){
							fprintf(out, "%02X", inp->vector[i].val[j]);
							if(!((j+1)%4)) fputs(" ", out);
						}
						*/
						fputs_base36(inp->vector[i].val, out);
						fputs("\n\n", out);
					}
				}
				
				
				else {	/** mode == EXPORT **/
					fputs(pubkey_head, out), fputs("\n", out);
					if((err = write_pubkey(out, inp->vector + i/*[i].val, pubkey_magic*/))) return -1;
				}
			}
			total++;
		}
		hit = 0;
	}
	
	return total;
}


ERROR import_keyring(FILE *inp)
{
	keyring ext = {NULL, NULL, 0};
	int i, j, coincid, err = 0, change = 0;
	char idstring[16], *pattern[2];
	
	if(( err = load_keyring(inp, &ext))) return err;
	pattern[1] = NULL;
	for(i = 0; i < ext.N; ++i){
		/** Look for coincidences in actual keyring **/
		coincid = 0;
		for(j = 0; j < default_kr.N; ++j){
			if(!memcmp(ext.vector[i].val, default_kr.vector[j].val, KEY_SIZE)){
				coincid = 1;
				break;
			}
		}
		if (!coincid){
			add_to_keyring(&default_kr, ext.vector + i);
			change = 1;
			fprintf(stderr, "%s [%08lx]: %s", CHCONV(msg_added_key), ext.vector[i].id, ext.vector[i].lab);
		}
		else{
			sprintf(idstring, "%08lx", ext.vector[i].id);
			fprintf(stderr, "->%s<- %s", idstring, CHCONV(msg_key_exists));
			pattern[0] = &(idstring[0]);
			search_pubkey(&default_kr, NULL, pattern, stderr, 'l');
		}
			
	}

	if(change) write_keyring();
	close_keyring(&ext);
	return 0;
}

ERROR delete_element(char *idstring)
{
	keyring ext = {NULL, NULL, /*-1,*/ 0};
	int i,  change = 0;
 	ERROR err = 0;
	unsigned long int id;
	char buf[SMALL_BUF];

	if(idstring == NULL){
		search_pubkey(&default_kr, NULL, NULL, stderr, 'l');
		get_text(stdin, buf, CHCONV(ask_idnum));
		idstring = buf;
	}
	sscanf(idstring, "%lx", &id);
	fprintf(stderr, "  [%08lx] ", id);
	for(i = 0; i < default_kr.N; ++i){
		if((err = add_to_keyring(&ext, default_kr.vector + i)))
		  break;
	}
	close_keyring(&default_kr);
	for(i = 0; i < ext.N; ++i){
		if(id != ext.vector[i].id){
			if((err = add_to_keyring(&default_kr, ext.vector + i)))
			  break;
		}
		else
			change = 1;
	}

	if(change){
		fputs(CHCONV(msg_deleted_key), stderr);
		search_pubkey(&default_kr, NULL, NULL, stderr, 'l');

		write_keyring();
	}
	else{
		search_pubkey(&default_kr, NULL, NULL, NULL, 'l');
		err = -1;
	}

	if(ext.N) close_keyring(&ext);
	return err;
}

