// vim600:fdm=marker
#include "cygwin.h"
#include "sFile.h"
#include "cSocket.h"
#include "sTag.h"
#include "db_macro.h"

#ifdef __cplusplus
extern "C" {
#endif
#include "md4.h"
#ifdef __cplusplus
	}
#endif
#include "donkey.h"
#include "misc.h"
#include "opcodes.h"

// zlib stream handling {{{
void* zalloc (void *, unsigned  items, unsigned size) {
	return MALLOC(items*size);
	}
void zfree   (void *,void * ptr) {
	free(ptr);
	}
z_stream *init_Infalte (void) {{{
	z_stream *ZIP = reinterpret_cast<z_stream *>(MALLOC (sizeof(z_stream)));
	ZIP->zalloc         = zalloc;
	ZIP->zfree          = zfree;
	ZIP->opaque         = NULL;
	ZIP->avail_in       = 0;
	ZIP->avail_out      = 0;
	ZIP->total_in       = 0;
	ZIP->total_out      = 0;
	int err = inflateInit(ZIP);
	if (err != Z_OK) {
		free (ZIP);
		return NULL;
		}
	return ZIP;
	}}}
z_stream *ende_Infalte (z_stream *ZIP) {{{
	if (ZIP == NULL) return NULL;
	int err = inflateEnd(ZIP);
	if (err != Z_OK) printf("ende_Infalte FAILED\n");
	Free2 (ZIP);
	return NULL;
	}}}
int Infalte (z_stream *ZIP,char *in, int in_cnt, char *out, int *out_cnt) {{{
	// ZIP zlib stream buffer
	// in      := Buffer that contain compressed data
	// in_cnt  := Size of the Buffer
	// out	   := Buffer for decompressed data
	// out_cnt := Size used in the decompressed buffer
	ZIP->next_in   = reinterpret_cast<Bytef*>(in);
	ZIP->avail_in  = in_cnt;
	ZIP->next_out  = reinterpret_cast<Bytef*>(out);
	ZIP->avail_out = *out_cnt;
	int err = inflate(ZIP, Z_NO_FLUSH);
	if (err != Z_OK && err != Z_STREAM_END) {{{
		out_cnt = 0;
		printf("ZIP Err %i\n", err);
		return -1;
		}}}
	int done = (*out_cnt) - ZIP->avail_out;
	(*out_cnt) = done;
	return in_cnt - ZIP->avail_in;
	}}}
// }}}

uint32_t IP4Name(const char *host) {{{
	struct in_addr ip;
	//      struct sockaddr_in *sa = NULL;
	ip.s_addr = 0;
#ifdef HAVE_GETADDRINFO
	struct addrinfo *addr = NULL;
	int err = getaddrinfo (host, NULL, NULL, &addr);
	if (err != 0) return 0;
	if (addr->ai_family != PF_INET) return 0;       // Normaly i should check all entry's
	ip = reinterpret_cast<struct sockaddr_in *>(addr->ai_addr)->sin_addr;
	if (addr != NULL) freeaddrinfo(addr);
	return ip.s_addr;
#elif HAVE_GETHOSTBYNAME
	// I think getaddrinfo checks for ip string, so we only need this for gethostbyname
#ifdef  HAVE_INET_ADDR
	if (inet_addr(host) != 0xFFFFFFFF) return inet_addr(host);
#elif   HAVE_INET_ATON
	if (inet_aton(host, &ip) ) return ip.s_addr;
#elif   HAVE_INET_PTON
	if (inet_pton(AF_INET, host, &ip)>0) return ip.s_addr;
#endif
	struct hostent *hostent = gethostbyname(host);
	if (hostent == NULL) return 0;
	if (hostent->h_addrtype != PF_INET) return 0;   // Normaly i should check all entry's
	ip = *((struct in_addr*) hostent->h_addr_list[0]);
	return ip.s_addr;
	//      sa = (struct sockaddr_in *)hostent->h_addr_list[0];
	//      return sa->sin_addr.s_addr;
#else
	printf("ERROR: no getaddrinfo or gethostbyname: Cannot resolve HOSTNAME's !!!\n");
	return 0;
#endif
	}}}

// Hash Conversion {{{
const unsigned char *md4(char *txt, size_t len) {{{
static unsigned char hash[MD4_DIGEST_LENGTH];
	if (txt == NULL) return NULL;
	MD4_CTX ctx;
	MD4Init   (&ctx);
	MD4Update (&ctx, reinterpret_cast<unsigned char *>(txt), len);
	MD4Final  (hash, &ctx);
	return hash; // Binary
	}}}
/// Returns a pointer (to a statically allocated buffer) in which the 32 chars
/// wide string representation of the given hash resides. Subsequent calls will
/// overwrite the buffer. 
/// @param hash the hash to bring into textual representation
const unsigned char *hash_bin2hex (const unsigned char *hash) {{{
	const  unsigned char hexdigits[] = "0123456789abcdef";
	static unsigned char m_hash_str[33];
	for (int j=0; j<16; j++) {
		m_hash_str[(j<<1)  ] = hexdigits[(((hash[j]) & 0xf0) >> 4)];
		m_hash_str[(j<<1)+1] = hexdigits[(((hash[j]) & 0x0f)     )];
		}
	return m_hash_str;
	}}}
const unsigned char *hash_hex2bin (const char *hash_str) {{{
	static unsigned char m_hash[16];
	int val;
	const char* str = hash_str;
	for (int i = 0; i < 16; i++) {
		if( (val = HexDigit2Int(*str++)) < 0 ) break;
		m_hash[i]  = (unsigned char) val << 4;
		if( (val = HexDigit2Int(*str++)) < 0 ) break;
		m_hash[i] |= (unsigned char) val;
		}
	return m_hash;
	}}}
// }}}

int cmp_u32(DB *, const DBT *A, const DBT *B) {{{
	uint32_t *a = reinterpret_cast<uint32_t*>(A->data);
	uint32_t *b = reinterpret_cast<uint32_t*>(B->data);
	if (*a > *b) 
		return  1;
	if (*a < *b) 
		return -1;
		return 0;
	}}}

// ---------------------------- IP CHECK ---------------------------
// {{{ Block list of IP's
DB  *ip_block     = NULL;
DBC *ip_block_cur = NULL;
struct sIP_range {
	unsigned von;
	unsigned bis;
	unsigned val;
	};

void read_IP_list(const char *name) {{{
	int err = 0;
	if (ip_block == NULL) {{{
		DB_create (ip_block);
		DB_set_alloc(ip_block);
		DB_set_bt_compare2 (ip_block ,cmp_u32);
		DB_open2 ( ip_block, NULL, NULL, DB_BTREE, DB_CREATE, 0);
		}}}
	FILE *fip = fopen (name, "r");
	if (fip == NULL) {{{
		printf("Can not read IP list!\n");
		return;
		}}}
	char buf[512];
	char ip_von[16];
	char ip_bis[16];
	char ip_val[ 4];
	ip_von[15]=0;
	ip_bis[15]=0;
	ip_val[ 3]=0;
	unsigned ip_key = 0;	
	struct sIP_range range;
	INIT_DBT(key, &ip_key	, sizeof(ip_key));
	INIT_DBT(val, &range	, sizeof(range ));
	while (0 < fgets(buf, 512, fip)) {
		buf[511] = 0;
		if (0 < sscanf(buf, "%15s - %15s , %3s , %*s", ip_von, ip_bis, ip_val)) {
			range.von = (atol(ip_von+0)<<24)+(atol(ip_von+4)<<16)+(atol(ip_von+8)<< 8)+atol(ip_von+12);
			range.bis = (atol(ip_bis+0)<<24)+(atol(ip_bis+4)<<16)+(atol(ip_bis+8)<< 8)+atol(ip_bis+12);
			range.val = atol(ip_val);
			ip_key	  = 0xFFFFFFFF - range.von; // DB sortiert 0.Byte 1.Byte (set range >= wir wollen aber <=)
			DB_put2 (ip_block, key, val);
			}
		}
	fclose(fip);
	}}}

void IP_block(struct in_addr ip) {
	int err = 0;
	struct sIP_range range;
	uint32_t KEY = 0xFFFFFFFF - htonl(ip.s_addr);
	range.von = htonl(ip.s_addr);
	range.bis = htonl(ip.s_addr);
	range.val = 0;
	INIT_DBT(key, &KEY  , sizeof(KEY  ));
	INIT_DBT(val, &range, sizeof(range));
	DB_put2 (ip_block, key, val);
	}

bool IP_is_OK(struct in_addr ip) {{{
static	unsigned count_all = 0;
static	unsigned count_bad = 0;
	uint32_t IP = htonl (ip.s_addr);
	if (0x00000000 == (IP & 0x0000FFFF)) return false;	// aaa.bbb.0.0
	if (0x00000000 == (IP & 0xFF000000)) return false;
	if (0x7F000000 == (IP & 0xFF000000)) return false;
	if (0xE0000000 == (IP & 0xF0000000)) return false;	// Spezial USE never valid
	if (0xF0000000 == (IP & 0xF0000000)) return false;	// MCAST nevervalid
	if (ip.s_addr == cSocket::getLocalExternalIP().s_addr) return false;  // context should be taken into account
	if (ip.s_addr == cSocket::getLocalInternalIP().s_addr) return false;
	if (ip_block == NULL) return true;
	count_all ++;
	int err  = 0;
	if (ip_block_cur == NULL)
		DB_cursor2 (ip_block, ip_block_cur);
	unsigned KEY = 0;
	KEY = 0xFFFFFFFF - IP;
	struct sIP_range range;
	INIT_DBT(key, &KEY  , sizeof(KEY  ));
	INIT_DBT(val, &range, sizeof(range));
	if (0 != (err = ip_block_cur->c_get (ip_block_cur, &key, &val, DB_SET_RANGE))) {{{
		if (err != DB_NOTFOUND) DB_error();
		//printf("DEBUG 0: ip not in block %15s\n", inet_ntoa(ip));
		return true;
		}}}
	if (IP < range.von || IP > range.bis) {{{
		printf("DEBUG 1: ip not in block %15s\n", inet_ntoa(ip));
		KEY = 0xFFFFFFFF - IP;
		range.von = htonl(ip.s_addr);
		range.bis = htonl(ip.s_addr);
		range.von &= 0xFFFFFF00;
		range.bis &= 0xFFFFFF00;
		range.bis += 255;
		range.val = pref.ip_trash + 1;
		DB_put2 (ip_block, key, val);
		return true;
		}}}
	if (range.val > pref.ip_trash) return true;
	count_bad ++;
	if (range.val == 127) return false;	// Private netspace
	if (range.val ==  11) return false;	// IANA reserved
	printf("DEBUG: ip alert %15s is mark as bad.level %u (%u/%u)\n", inet_ntoa(ip), range.val, count_all, count_bad);
	return false;
	}}}
void ende_IP_list(void) {{{
	int err = 0;
	if(ip_block_cur != NULL) {
		DB_c_close(ip_block_cur);
		ip_block_cur = NULL;
		}
	DB_close2 (ip_block);
	}}}
// }}}

// ---------------------------- IP CHECK ---------------------------
int cmp_20 (DB *, const DBT *A, const DBT *B) {{{
	return memcmp(A->data, B->data, 20);
	}}}
int key_src_time(DB *, const DBT *, const DBT *pdata, DBT *skey) {{{
	bzero (skey, sizeof(DBT));
	skey->data=(     & (reinterpret_cast<struct eSources *>(pdata->data)->next_time));
	skey->size=(sizeof(((reinterpret_cast<struct eSources *>(pdata->data)->next_time))));
	return 0;
	}}}
int key_dat_ip  (DB *, const DBT *, const DBT *pdata, DBT *skey) {{{
	bzero (skey, sizeof(DBT));
	skey->data=(     & (reinterpret_cast<struct eFiles   *>(pdata->data)->ip));
	skey->size=(sizeof((reinterpret_cast<struct eFiles   *>(pdata->data)->ip)));
	return 0;
	}}}
int key_dat_hash(DB *, const DBT *, const DBT *pdata, DBT *skey) {{{
	bzero (skey, sizeof(DBT));
	skey->data=(     & (reinterpret_cast<struct eFiles*>(pdata->data))->hash);
	skey->size=(sizeof((reinterpret_cast<struct eFiles*>(pdata->data))->hash));
	return 0;
	}}}

// B+Tree IP suche {IDX,IP} IDX => Entry in one Table
void db_warn  (const char *file, int line, int err) {{{
	printf("\n\n%s %i DB: %5i %s\n\n", file, line, err, db_strerror(err));
	}}}
void db_error (const char *file, int line, int err) {{{
	printf("\n\n%s %i DB: %5i %s\n\n", file, line, err, db_strerror(err));
	fflush(stdout);
	abort();
	}}}
DB  *src_ip	= NULL;
DB  *src_time	= NULL;
DB  *srv_ip	= NULL;
DB  *srv_next	= NULL;
DB  *srv_ping	= NULL;
DB  *dat_idx	= NULL;
DB  *dat_hash	= NULL;
DB  *dat_ip	= NULL;
DBC *c0		= NULL;
DBC *c1		= NULL;
DBC *c2		= NULL;
void dbSync (void) {{{
	if (src_ip == NULL) return;
static time_t next = 0;
	if(next >= currentTime) return;
	next = currentTime + 60;
	if (src_ip != NULL) src_ip->sync(src_ip, 0);
	}}}
int cursor_count = 0;

void  open_Sources (void) {{{
	int err = 0;
	if (src_ip != NULL) return;
	DB_create(src_ip  ); // Open Primary   (IP)
	DB_create(src_time); // Open secondary (TIME)
	DB_create(dat_idx ); // Open Primary   (IP)
	DB_create(dat_hash); // Open Primary   (IP)
	DB_create(dat_ip  ); // Open secondary (TIME)
	DB_set_alloc(src_ip  );
	DB_set_alloc(src_time);
	DB_set_alloc(dat_idx );
	DB_set_alloc(dat_hash);
	DB_set_alloc(dat_ip  );
	src_ip  ->set_errfile(src_ip  , stdout);
	src_time->set_errfile(src_time, stdout);
	DB_set_bt_compare2 (src_time ,cmp_u32);
	DB_set_bt_compare2 (dat_idx  ,cmp_20 );
	DB_set_flags2 (dat_hash, DB_DUP | DB_DUPSORT);
	DB_set_flags2 (dat_ip  , DB_DUP | DB_DUPSORT);
	DB_set_flags2 (src_time, DB_DUP | DB_DUPSORT);
	DB_open2 (src_ip  , db_client  , "source_lst", DB_BTREE, DB_CREATE, 0600);
	printf("DB_client %s\n", db_client);
	DB_open2 (dat_idx , NULL, NULL, DB_BTREE, DB_CREATE, 0);
	DB_open2 (dat_hash, NULL, NULL, DB_BTREE, DB_CREATE, 0);
	DB_open2 (dat_ip  , NULL, NULL, DB_BTREE, DB_CREATE, 0);
	DB_open2 (src_time, NULL, NULL, DB_BTREE, DB_CREATE, 0);
	// Associate secondary indexes with primary index
	DB_associate2 (src_ip , src_time, key_src_time);
	DB_associate2 (dat_idx, dat_hash, key_dat_hash);
	DB_associate2 (dat_idx, dat_ip  , key_dat_ip  );
	uint32_t ip = 0;
#define	BUFFER_LENGTH	(5 * 1024 * 1024)
	char *buffer = reinterpret_cast<char*>(MALLOC (BUFFER_LENGTH));
	INIT_DBT(key, &ip    , sizeof(ip));
	INIT_DBT(val,  buffer, BUFFER_LENGTH);
	DBC *cur = NULL;
	DB_cursor2 (src_ip, cur);
	while (0 == cur->c_get (cur, &key, &val, DB_MULTIPLE_KEY|DB_NEXT)) {{{
		void *p = NULL;
		struct eSources *tmp = NULL;
		DBT m_key;
		DBT m_val;
		DB_MULTIPLE_INIT(p, &val);
		for(;;) {
			DB_MULTIPLE_KEY_NEXT(p, &val, m_key.data, m_key.size, m_val.data, m_val.size);
			if (p == NULL) break;
			tmp = reinterpret_cast<struct eSources*>(m_val.data);
			tmp->aktive = 0;
			INIT_DBT(key_set, &tmp->ip, sizeof(tmp->ip));
			INIT_DBT(val_set,  tmp    , sizeof(struct eSources));
			DB_put2 (src_ip, key_set, val_set);
			}
		}}}
	DB_c_close(cur);
	Free2 (buffer);
	}}}
void close_Sources (void) {{{
	if (src_ip == NULL) return;
	int err = 0;
	if (c0 != NULL) DB_c_close(c0);
	if (c2 != NULL) DB_c_close(c2);
	if (dat_idx  != NULL) DB_close2 (dat_idx ); dat_idx  = NULL;
	if (src_ip   != NULL) DB_close2 (src_ip  ); src_ip   = NULL;
	if (dat_hash != NULL) DB_close2 (dat_hash); dat_hash = NULL;
	if (dat_ip   != NULL) DB_close2 (dat_ip  ); dat_ip   = NULL;
	if (src_time != NULL) DB_close2 (src_time); src_time = NULL;
	c0 = NULL;
	}}}

unsigned countBtree  (DB *db) {{{
	if (db == NULL) return 0;
	int err;
	DB_BTREE_STAT *statistik;
	DB_stat(db, statistik);
	uint32_t ret = statistik->bt_nkeys;
	free (statistik);
	return ret;
	}}}

// { {{ Source  Liste
void     Update_src        (struct eSources *s) {{{
	if (src_ip == NULL) return;
	in_addr ip = s->ip;
	int err = 0;
	INIT_DBT(key, &ip, sizeof(ip));
	INIT_DBT(val,  s , sizeof(struct eSources));
	DB_put2 (src_ip, key, val);
	}}}

void     Delete_src        (struct in_addr  IP) {{{
	if (src_ip == NULL) return;
	int err = 0;
	INIT_DBT(key, &IP, sizeof(IP));
	DB_del2 (src_ip, key);
	}}}
eSources *Source_ip        (struct in_addr  IP) {{{
	if (src_ip == NULL) {
		if (running) printf("WARNING src_ip = NULL\n");
		return NULL;
		}
	struct in_addr ip = IP;
static  struct eSources tmp;
	bzero (&tmp, sizeof(tmp));
	INIT_DBT(key, &ip , sizeof(ip ));
	INIT_DBT(val, &tmp, sizeof(tmp));
	int err = 0;
	if (0 != (err = DB_get (src_ip, key, val))) {
		if (DB_NOTFOUND != err) DB_error();
		return NULL;
		}
	return &tmp;
	}}}
time_t   addSource         (struct in_addr  IP, uint16_t PORT, unsigned VER, bool usefull) {{{
	if (usefull)
	ASSERT(usefull);
	if (htonl(IP.s_addr) <= 0x00FFFFFF) {
		printf("REJECT(0) IP %s\n", inet_ntoa(IP));
		return currentTime + 3600;
		}
	if ((IP.s_addr & 0x000000FF) == 0 ) {
		printf("REJECT(1) IP %s\n", inet_ntoa(IP));
		return currentTime + 3600;
		}
	if ((IP.s_addr & 0xFF000000) == 0 ) {
		return currentTime + 3600;
		printf("REJECT(2) IP %s\n", inet_ntoa(IP));
		}
	if (!IP_is_OK(IP)) {
		printf("REJECT(3) IP %s\n", inet_ntoa(IP));
		return currentTime + 3600;
		}
	struct eSources tmp;
	struct eSources *s = Source_ip (IP);
	if (s == NULL) {
		s = &tmp;
		bzero (s, sizeof(struct eSources));
		s->ip 	     = IP;
		s->next_time = 0;
		s->prio      = 1;
		s->ratio     = 0;
		s->aktive    = 0;
		s->version   = 0;
		s->udp_port  = 0;
		log (2,"ADD source %15s\n", inet_ntoa(IP));
		}
	else {
		memcpy(&tmp, s, sizeof(tmp));
		s = &tmp;
		}
	s->port    = PORT;
	s->version = VER;
	Update_src (s);
	dbSync ();
	return s->next_time;
	}}}
void     delSource         (struct in_addr  IP, int err) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	time_t zeit = 0;
	switch (err) {
	case    0 : zeit = currentTime + 60 * 10; break;
	case  101 : zeit = currentTime + 60 * 60; break;	// net unreachable
	case  104 : zeit = currentTime + 60 *  3; break;	// reset by peer
	case  110 : zeit = currentTime + 60 * 10; break; 	// timeout
	case  111 : zeit = currentTime + 60 * 10; break; 	// refused
	case  113 : zeit = currentTime + 60 * 30; break; 	// no Route
	case   -3 : zeit = currentTime + 60 * 30; break; 	// protocol error
	default   : zeit = currentTime + 60 * 60;
		    printf("DELETE source ground %4i\n", err);
		    break;
		   }
	s->next_time = zeit;
	ASSERT(0);
	Update_src (s);
	}}}
bool     getSource         (in_addr *ip, unsigned short *port) {{{ //  DB_DBT_USERMEM;
static  time_t next = 0;	
static  int fresh = 0;
	if (next >= currentTime) return false;
	bzero (ip, sizeof(in_addr));
	*port = 0;
	fresh++;
	if ((fresh % 1000) == 0) {
		printf("SOURCE: 1000 refresh pool\n");
		return false;
		}
	int err = 0;
	struct eSources tmp;
	bzero (&tmp, sizeof(tmp));
	time_t zeit = 0;
	INIT_DBT(key, &zeit, sizeof(zeit));
	INIT_DBT(val, &tmp , sizeof(tmp	));
	DBC *c3 = NULL;
	DB_cursor2 (src_time, c3);
	while (0 == (err = c3->c_get (c3, &key, &val, DB_NEXT))) {{{ // search next source for connect
 		ASSERT(sizeof(tmp ) == val.size);
 		ASSERT(sizeof(zeit) == key.size);
		uint16_t s_port = tmp.port;
		if (tmp.next_time >= currentTime) { // sortiert !!!
			next = tmp.next_time;
			DB_c_close(c3);
			char zeit_tmp[101];
			strftime(zeit_tmp, 100, "%m-%d %H:%M.%S", gmtime(&tmp.next_time));
			printf("SOURCE: no current timeouted source %15s => %s\n", inet_ntoa(tmp.ip), zeit_tmp);
			return false;
			}
		if (s_port == 0      ) { Delete_src (tmp.ip); continue; }
		if (!IP_is_OK(tmp.ip)) { Delete_src (tmp.ip); continue; }
		if (tmp.aktive > 0) continue;
		if (tmp.ratio < -PARTSIZE) continue;
		
		bool use = false;
		{ FILE_LIST {
			if (akt->status == ALL_DONE || akt->Pause() ) continue;
			if (NULL == File_idx (akt->hash, tmp.ip)) continue;
			use = true;
			} }
		if (!use) {
			tmp.next_time = currentTime + 600;
			Update_src(&tmp);
			continue;
			}
		
		*ip   = tmp.ip;
		*port = s_port;
		log (2,"GET source %15s cur: %s t: %s", inet_ntoa(*ip), currentTime_str, ctime(&tmp.next_time));
		tmp.next_time = currentTime + 600;
		Update_src(&tmp);
		DB_c_close(c3);
		log (1, "getSource_from_File (%s,%u) OK nextTime %s\n",inet_ntoa(*ip),*port, currentTime_str);
		if (*port == 0) {
			printf("SOURCE: 0 port in pool\n");
			return false;
			}
		printf("SOURCE: using %15s\n", inet_ntoa(*ip));
		return true;
		}}}
	if (err != DB_NOTFOUND) DB_error();
	DB_c_close(c3);
	// printf("SOURCE: none used from pool\n");
	return false;
	}}}
unsigned cntSource         (void) {{{
	int err = 0;
	DB_BTREE_STAT *statistik;
	DB_stat(src_ip, statistik);
	uint32_t ret = statistik->bt_nkeys;
	free (statistik);
	return ret;
	}}}
void     setSource_timeout (struct in_addr  IP, time_t zeit) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	s->next_time = zeit;
	Update_src (s);
	}}}
/* Does not Change the order */
void     chgSources_ratio  (struct in_addr  IP, long ratio   ) {{{
	printf("Change Source Ratio ");
	struct eSources *s = Source_ip (IP);
	if (s == NULL) {
		printf("Source not found\n");
		return;
		}
	printf("Change done\n");
	s->ratio += ratio;
	ASSERT(0);
	Update_src (s);
	return;
	}}}
void     setSources_aktive (struct in_addr  IP) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	s->aktive++;
	Update_src (s);
	}}}
void     setSources_version(struct in_addr  IP, int      version) {{{
	struct eSources *s = Source_ip (IP);
	struct eSources tmp;
	if (s == NULL) {
		s = &tmp;
		bzero (s, sizeof(struct eSources));
		s->ip 	     = IP;
		s->prio      = 1;
		s->ratio     = 0;
		s->aktive    = 0;
		}
	s->version   = version;
	Update_src (s);
	return;
	}}}
void     setSources_udp    (struct in_addr  IP, uint16_t u) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	s->udp_port = u;
	Update_src (s);
	}}}
void     setSources_hash_dl(struct in_addr  IP, uint8_t *h) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	memcpy(s->hash_dl, h, 16);
	Update_src (s);
	}}}
void     setSources_hash   (struct in_addr  IP, uint8_t *h) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	memcpy(s->hash, h, 16);
	Update_src (s);
	}}}
int      cntSources_aktive (struct in_addr  IP) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return 0;
	return s->aktive;
	}}}
void     endSources_aktive (struct in_addr  IP) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return;
	if (s->aktive <= 0) return;
	s->aktive--;
	Update_src (s);
	}}}
long     getSources_ratio  (struct in_addr  IP) {{{
	struct eSources *s = Source_ip (IP);
	if (s == NULL) return 0;
	return s->ratio;
	}}}
// } }}

// {{{ Server liste
static void Update_srv (const struct eServers *s) {{{
	struct eServers u;
	memcpy (&u, s, sizeof(struct eServers));
	struct in_addr ip = u.ip;
	int err = 0;
	INIT_DBT(key, &ip, sizeof(ip));
	INIT_DBT(val, &u , sizeof(u ));
	DB_put2 (srv_ip, key, val);
	dbSync ();
	}}}

void cServer_list::Save(void) {{{
	size_t count = countBtree(srv_ip);
	if (count == 0) return;
	size_t len, LEN = len = count * 400;
	unsigned char *buf, *BUF = buf = reinterpret_cast<unsigned char*>(MALLOC(LEN));
	ADD_U1 (&buf, &len, 0xE0);
	ADD_U4 (&buf, &len, count);
	struct eServers tmp;
	time_t zeit = 0;
	int err = 0;
	INIT_DBT(key, &zeit, sizeof(time_t));
	INIT_DBT(val, &tmp , sizeof(struct eServers));
	DBC *c = NULL;
	DB_cursor2 (srv_next, c);
	while (0 == (err = c->c_get (c, &key, &val, DB_NEXT))) {
		count--;
		ADD_U4 (&buf, &len, tmp.ip.s_addr);
		ADD_U2 (&buf, &len, tmp.port);
		unsigned char   *cnt_idx = buf;
		size_t  cnt_len = len;
		size_t  tag_count = 0;
		ADD_U4 (&buf, &len, 0);
		if (0 != strlen(tmp.name)) { add_strTag(&buf, &len, ST_SERVERNAME , tmp.name	 ); tag_count++; }
		if (0 != strlen(tmp.desc)) { add_strTag(&buf, &len, ST_DESCRIPTION, tmp.desc	 ); tag_count++; }
					     add_intTag(&buf, &len, ST_LASTPING   , tmp.last_ping); tag_count++;
					     add_intTag(&buf, &len, ST_PREFERENCE , tmp.prio     ); tag_count++;
					     add_intTag(&buf, &len, ST_FAIL       , tmp.ping_fail); tag_count++;
					     add_intTag(&buf, &len, ST_LONG_USERS , tmp.user	 ); tag_count++;
					     add_intTag(&buf, &len, ST_LONG_FILES , tmp.file	 ); tag_count++;
					     add_intTag(&buf, &len, ST_MAXUSERS   , tmp.user_max ); tag_count++;
		ADD_U4 (&cnt_idx, &cnt_len, tag_count);
		}
	DB_c_close(c);
	if (count != 0) printf("Wrong count %u\n", count);
	int fd = FD_create ("server.met.new", O_WRONLY|O_BINARY|O_TRUNC|O_CREAT,0600);
	if (fd == -1) {{{
		printf ("server.met.new not open\n");
		free (BUF);
		return;
		}}}
	len = LEN - len;
	buf = BUF;
	FD_WRITE2 (fd, buf, len);
	free(BUF);
	if (close (fd)) printf("close server.met failed\n");
	remove("server.met.old");
	if(rename("server.met", "server.met.old")) printf("old server.met was not saved\n");
	if(rename("server.met.new", "server.met")) printf("new server.met was not saved\n");
	}}}
void cServer_list::Load(void) {{{
	size_t len, LEN = len = 0;
	unsigned char *buf, *BUF = buf = file("server.met", &LEN);
	len = LEN;
	if (BUF == NULL) return;
	uint8_t version = GET_U1(&buf, &len);
	if (version != 0xE0) {
		free(BUF);
		return;
		}
	struct eServers server;
	server.id 	= 0;
	server.name[31] = 0;
	server.desc[63] = 0;
	server.padding  = 0;
	for (size_t count = GET_U4 (&buf, &len); count > 0; count--) {
		server.ip.s_addr = GET_U4 (&buf, &len);
		server.port      = GET_U2 (&buf, &len);
		size_t cnt = len;
		class sTag *tag = parse_tag (buf, &cnt);
		buf += cnt;
		len -= cnt;
		server.name[0] = 0;
		server.desc[0] = 0;
		if (tag->name != NULL) strncpy(server.name, tag->name, 31);
		if (tag->desc != NULL) strncpy(server.desc, tag->desc, 63);
		server.user 	   = tag->users;
		server.file 	   = tag->files;
		server.user_max    = tag->users_max;
		server.next_time   = 0;
		server.last_action = 0;
		server.prio	   = tag->pref;
		server.last_ping   = tag->last_ping;
		server.ping_fail   = tag->fail;
		Update_srv (&server);
		delete tag;
		}
	free (BUF);
	}}}

unsigned cntServer (void) {{{
	int err;
	DB_BTREE_STAT *statistik;
	DB_stat(srv_ip, statistik);
	uint32_t ret = statistik->bt_nkeys;
	free (statistik);
	return ret;
	}}}
int key_srv_next(DB *, const DBT *, const DBT *pdata, DBT *skey) {{{
	bzero (skey, sizeof(DBT));
	skey->data=(     & (reinterpret_cast<struct eServers *>(pdata->data))->next_time);
	skey->size=(sizeof((reinterpret_cast<struct eServers *>(pdata->data))->next_time));
	return 0;
	}}}
int key_srv_ping(DB *, const DBT *, const DBT *pdata, DBT *skey) {{{
	bzero (skey, sizeof(DBT));
	skey->data=(     & (reinterpret_cast<struct eServers *>(pdata->data))->last_ping);
	skey->size=(sizeof((reinterpret_cast<struct eServers *>(pdata->data))->last_ping));
	return 0;
	}}}
void  open_Servers (void) {{{
	int err = 0;
	if (srv_ip != NULL) return;
	DB_create(srv_ip  ); // Open Primary   (IP)
	DB_create(srv_next); // Open secondary (TIME)
	DB_create(srv_ping); // Open secondary (PING)
	if (0 != (err = srv_next->set_flags(srv_next, DB_DUP | DB_DUPSORT))) DB_error();
	if (0 != (err = srv_ping->set_flags(srv_ping, DB_DUP | DB_DUPSORT))) DB_error();
	DB_set_alloc(srv_ip);
	DB_set_alloc(srv_next);
	DB_set_alloc(srv_ping);
	DB_set_bt_compare2 (srv_next ,cmp_u32);
	DB_set_bt_compare2 (srv_ping ,cmp_u32);
	DB_open2 (srv_ip  , NULL, NULL, DB_BTREE, DB_CREATE, 0);
	DB_open2 (srv_next, NULL, NULL, DB_BTREE, DB_CREATE, 0);
	DB_open2 (srv_ping, NULL, NULL, DB_BTREE, DB_CREATE, 0);
	// Associate secondary indexes with primary index
	DB_associate2 (srv_ip, srv_next, key_srv_next);
	DB_associate2 (srv_ip, srv_ping, key_srv_ping);
	}}}
void close_Servers (void) {{{
	cServer_list::Save ();
	if (srv_ip == NULL) return;
	int err = 0;
	if (c1 != NULL) DB_c_close(c1);
	c1 = NULL;
	if (srv_ip   != NULL) DB_close2 (srv_ip  ); srv_ip   = NULL;
	if (srv_next != NULL) DB_close2 (srv_next); srv_next = NULL;
	if (srv_ping != NULL) DB_close2 (srv_ping); srv_ping = NULL;
	}}}
void Delete_srv (in_addr ip) {{{
	int err = 0;
	log (2, "final del server %15s\n", inet_ntoa(ip) );
	INIT_DBT(key, &ip, sizeof(ip));
	DB_del2 (srv_ip, key);
	dbSync ();
	}}}
struct eServers *Server_ip (struct in_addr IP) {{{
	if (!running) return NULL;
	if (srv_ip == NULL) {
		printf("WARNING srv_ip = NULL\n");
		return NULL;
		}
	struct in_addr ip = IP;
static  struct eServers tmp;
	bzero (&tmp, sizeof(tmp));
	INIT_DBT(val, &tmp        , sizeof(tmp          ));
	INIT_DBT(key, &ip         , sizeof(ip           ));
	int err = 0;
	if (0 != (err = DB_get (srv_ip, key, val))) {
		if (DB_NOTFOUND != err) DB_error();
		return NULL;
		}
	return reinterpret_cast<struct eServers *>(val.data);
	}}}
void delServer       (in_addr IP, uint16_t , int err) {{{ // NEW
	struct eServers *s = Server_ip (IP);
	time_t zeit = currentTime + 90;
	if (s == NULL) return;
	switch (err) {
	case 104 : zeit = currentTime + 180; break;	// connection reset by peer
	case 111 : zeit = currentTime + 180; break;	// connection refused by peer
	case -4  : zeit = currentTime + 600;
		   strncpy(s->desc, "unwanted", 64);
		   s->desc[63] = 0;
		   break;
	case -7  : Delete_srv(IP); return;
	default  : printf("del server %15s cause %4i\n", inet_ntoa(IP), err);
		   break;
		}
	s->ping_fail++;
	s->next_time = zeit;
	Update_srv (s);
	return;
	}}}
// true: valid ip/port found , else false
bool nextServer	     (struct in_addr *ip, uint16_t *port) {{{
	ip->s_addr = 0;
	*port = 0;
	int err = 0;
	struct eServers tmp;
	time_t zeit = 0;
	INIT_DBT(key, &zeit, sizeof(time_t));
	INIT_DBT(val, &tmp , sizeof(struct eServers));
	DBC *c = NULL;
	DB_cursor2 (srv_next, c);
	while (0 == (err = c->c_get (c, &key, &val, DB_NEXT))) {{{ // find the server we next ry to connect
		ASSERT(val.size == sizeof(struct eServers));
		uint16_t s_port = tmp.port;
		// Delete server if we got 10 errors : adjust variable ?
		if (tmp.ping_fail > 10) { Delete_srv (tmp.ip); continue; }
		if (s_port        == 0) { Delete_srv (tmp.ip); continue; }
		if (0 == strcmp (tmp.desc, "unwanted")) continue;
		DB_c_close(c);
		if (tmp.next_time >= currentTime) {
			log (1, "nextServer: All servers exhausted.  Trying next one in %li seconds.\n", tmp.next_time-currentTime );
			return false; // sortiert !!!
			}
		*ip   = tmp.ip;
		*port = s_port;
		tmp.next_time = currentTime + 60;
		tmp.ping_fail ++;
		Update_srv(&tmp);
		log (1, "1 USE %15s t: %10lu \n", inet_ntoa(*ip), (tmp.next_time - currentTime) );
		return true;
		}}}
	if (err != DB_NOTFOUND) DB_error();
	DB_c_close(c);
	return false;
	}}}
const struct eServers *nextServers(bool first) {{{
	int err;
static  struct eServers tmp;
static  uint32_t ip;
	INIT_DBT(val, &tmp, sizeof(tmp));
	INIT_DBT(key, &ip , sizeof(ip ));
	if (c1 == NULL) {
		if (!first) return NULL;
		DB_cursor2 (srv_ip, c1);
		bzero (&tmp, sizeof(tmp));
		ip = 0;
//		RESET_DBT(key, &ip , sizeof(ip ));
		if (0 != (err = c1->c_get (c1, &key, &val, DB_FIRST))) {
			if (err != DB_NOTFOUND) DB_error();
			return NULL;
			}
		return reinterpret_cast<const struct eServers *>(val.data);
		}
	if (0 != (err = c1->c_get (c1, &key, &val, (first)?DB_FIRST:DB_NEXT))) {
		if (err != DB_NOTFOUND) DB_error();
		DB_c_close(c1);
		c1 = NULL;
		return NULL;
		}
	return reinterpret_cast<const struct eServers *>(val.data);
	}}}
bool saveServer      (struct in_addr ip, uint16_t port,unsigned int files,unsigned int user, bool gui) {{{ // SQL-Enabled
	if ((ip.s_addr & 0xFF000000) == 0) return false;
	if ((ip.s_addr & 0x000000FF) == 0) return false;
	if (!IP_is_OK (ip)) return false;
	if (port == 4665) port = 4661;
	ASSERT (port != 4665);
	struct eServers tmp;
	struct eServers *s = Server_ip (ip);
	bool	neu = false;
	if (s == NULL || gui) {{{
		s = &tmp;
		bzero (s, sizeof(struct eServers));
		s->id		= 0;		// Currently unused
		s->ip		= ip;
		s->port 	= port;
		s->next_time 	= currentTime - 1; // server is available to be tested.
		s->name[0] 	= 0;
		s->desc[0] 	= 0;
		s->user 	= user;
		s->file 	= files;
		s->user_max 	= user;
		s->file_max 	= files;
		s->prio 	= 1;
	// 	s->last_action	IMMER
		s->ping_fail	= 0;
		s->last_ping 	= 0;
		neu = true;
		}}}
	else	{
		if (files>0 || user>0) {
			s->user 	= user;
			s->file 	= files;
			if (s->user_max < user ) s->user_max = user ;
			if (s->file_max < files) s->file_max = files;
			}
		if (port != 0) s->port = port; 
		}
	s->last_action	= currentTime - 120;
	Update_srv (s);
	return true;
	}}}
bool actionServer    (struct in_addr ip, uint16_t) {{{
	struct eServers *s = Server_ip (ip);
	if (s == NULL) return false;
	s->ping_fail 	= 0;
	s->next_time    = currentTime + 600;
	s->last_ping    = currentTime + 60;
	s->last_action  = currentTime;
	Update_srv (s);
	return true;
	}}}
bool setServer_name  (struct in_addr ip, uint16_t, const char *name) {{{
	struct eServers *s = Server_ip (ip);
	if (s == NULL) return false;
	s->last_ping    = currentTime + 60;
	s->next_time    = currentTime + 60;
	s->ping_fail    = 0;
	s->last_action  = currentTime;
	strncpy (s->name, name, 32);
	s->name[31] = 0;
	Update_srv (s);
	return true;
	}}}
bool setServer_desc  (struct in_addr ip, uint16_t, const char *desc) {{{
	struct eServers *s = Server_ip (ip);
	if (s == NULL) return false;
	s->last_ping    = currentTime + 60;
	s->next_time    = currentTime + 60;
	s->last_action  = currentTime;
	s->ping_fail    = 0;
	strncpy (s->desc, desc, 64);
	s->desc[63] = 0;
	Update_srv (s);
	return true;
	}}}
bool setServer_prio  (struct in_addr ip, uint16_t, uint8_t prio) {{{
	struct eServers *s = Server_ip (ip);
	if (s == NULL) return false;
	s->prio = prio;
	Update_srv (s);
	return true;
	}}}
bool pongServer      (struct in_addr ip) {{{
	struct eServers *s = Server_ip (ip);
	if (s == NULL) return false;
	s->last_action  = currentTime;
	s->ping_fail 	= 0;
	s->last_action 	= currentTime;
	s->last_ping   	= currentTime + 120;
	if (s->next_time > currentTime - 120) s->next_time = currentTime - 120;
	Update_srv (s);
	return true;
	}}}
bool nextPing        (struct in_addr *ip, uint16_t *port, uint32_t *challange) {{{ // DB_DBT_USERMEM
	ASSERT(ip != NULL);
	*challange = currentTime;
	ip->s_addr = 0;
	*port = 0;
	if (srv_ping == NULL) return false;
	int err = 0;
	struct eServers tmp;
	bzero (&tmp, sizeof(tmp));
	time_t zeit = currentTime;
	INIT_DBT(key, &zeit, sizeof(zeit));
	INIT_DBT(val, &tmp , sizeof(tmp));
	DBC *c = NULL;
	DB_cursor2 (srv_ping, c);
	while (0 == (err = c->c_get (c, &key, &val, DB_NEXT))) {
		struct eServers *s = reinterpret_cast<struct eServers *>(val.data);
		if (s->last_action + 180 >= currentTime) continue;
		// only ping when needed
		if (s->last_ping >= currentTime) { // sortiert !!!
			DB_c_close(c);
			return false;
			}
		if (0 == (s->ip.s_addr & 0xFF000000) || 0 == (s->ip.s_addr & 0x000000FF)) {
			Delete_srv (s->ip);
			continue;
			}
		if (!IP_is_OK(s->ip)) {
			Delete_srv (s->ip);
			continue;
			}
		*ip   = s->ip;
		*port = s->port;
		log (1, "PING %15s ping/action: %10lu/%10lu c:%s\n",
					inet_ntoa(*ip), currentTime- s->last_ping,currentTime- s->last_action, currentTime_str);
		s->ping_fail++;
		s->last_ping = currentTime + s->ping_fail * 10;
		Update_srv(s);
		DB_c_close(c);
		return true;
		}
	if (err != DB_NOTFOUND) DB_error();
	DB_c_close(c);
	return false;
	}}}
// }}}
// {{{ Files  liste
void     insertFiles(tHash &hash, in_addr ip, size_t info_len, const unsigned char *info) {{{
	int err = 0;
	size_t len = info_len + sizeof(eFiles) - 1;
	struct eFiles *i = reinterpret_cast<struct eFiles*>(MALLOC (len));
	memcpy(i->hash, hash, sizeof(tHash));
	char idx[20];
	memcpy(idx +  0, hash, 16);
	memcpy(idx + 16, &ip ,  4);

	i->ip.s_addr = ip.s_addr;
	i->info_len  = info_len;
	i->since     = currentTime;
	if (info_len > 0) {
		ASSERT(info != NULL);
		memcpy(i->info, info, info_len);
		}
	INIT_DBT(key, idx, 20);
	INIT_DBT(val, i, len);
	DB_put2 (dat_idx, key, val);
	Free2 (i);
	}}}
void     deleteFiles_ip  (in_addr ip) {{{
	int err = 0;
	INIT_DBT(key, &ip, sizeof(ip));
	DB_del2 (dat_ip, key);
	}}}
void     deleteFiles_hash(char *hash) {{{
	int err = 0;
	INIT_DBT(key, hash, 16);
	DB_del2 (dat_hash, key);
	}}}
unsigned    cntFiles (void) {{{
	int err;
	DB_BTREE_STAT *statistik;
	DB_stat(dat_idx, statistik);
	uint32_t ret = statistik->bt_nkeys;
	free (statistik);
	return ret;
	}}}
const struct eFiles *nextFile (const tHash &hash, bool first) {{{
static  DBT key;	// DB_DBT_USERMEM
static  DBT val;	// should be handeld
static  char Hash[16];
static  bool first_run = true;
	if (first_run) {
		first_run = false;
		bzero (&val, sizeof(val));
		}
	int err;
	if (first) {{{
		if (c0 != NULL) DB_c_close (c0);
		DB_cursor2 (dat_hash, c0);
		memcpy (Hash, hash, 16);
		RESET_DBT(key, Hash, 16);
		if (0 != (err = c0->c_get (c0, &key, &val, DB_SET_RANGE))) {{{
			cursor_count = 0;
			if (err != DB_NOTFOUND) DB_error();
			return NULL;
			}}}
		db_recno_t rec_count;
		c0->c_count (c0, &rec_count, 0);
		cursor_count = rec_count;
		return reinterpret_cast<const struct eFiles *>(val.data);
		}}}
	if (c0 == NULL) return NULL;
	if (0 != (err = c0->c_get (c0, &key, &val, DB_NEXT_DUP))) {{{
		if (err != DB_NOTFOUND) DB_error();
		DB_c_close (c0);
		c0 = NULL;
		return NULL;
		}}}
	return reinterpret_cast<const struct eFiles *>(val.data);
	}}}
struct eFiles *File_idx (tHash &hash, struct in_addr ip) {{{
static  bool first_run = true;
static  DBT val;
	if (first_run) {
		first_run = false;
		bzero (&val, sizeof(val));
		}
	int err = 0;
	char idx[20];
	memcpy(idx +  0, hash, 16);
	memcpy(idx + 16, &ip ,  4);
	INIT_DBT(key, idx, 20);
	if (0 != (err = DB_get (dat_idx, key, val))) {
		if (DB_NOTFOUND != err && DB_PAGE_NOTFOUND != err) DB_error();
		return NULL;
		}
	return reinterpret_cast<struct eFiles *>(val.data);
	}}}
// }}}

void misc_init(void) {{{
	read_IP_list ("ip.prefix");
	open_Sources ();
	open_Servers ();
	}}}

#define CHAR64(c)  (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
static char basis_64[] =
	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
	"????????????????????????????????????????????????????????????????"
	"????????????????????????????????????????????????????????????????"
	"???????????";
static char index_64[128] = { 
	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
	-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
	52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
	-1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
	15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
	-1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
	41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
	};

bool encode64(const char *_in, size_t inlen, char *_out, size_t   outmax, size_t *outlen) {{{
	const unsigned char *in  = reinterpret_cast<const unsigned char *>(_in);
	unsigned char       *out = reinterpret_cast<unsigned char *>(_out);
	size_t olen = (inlen + 2) / 3 * 4; // Will it fit?
	if (outlen != NULL) *outlen = olen;
	if (outmax < olen) return false;
	// Do the work...
	while (inlen >= 3) {	// user provided max buffer size; make sure we don't go over it
		*out++ = basis_64[in[0] >> 2];
		*out++ = basis_64[((in[0] << 4) & 0x30) | (in[1] >> 4)];
		*out++ = basis_64[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
		*out++ = basis_64[in[2] & 0x3f];
		in += 3; 
		inlen -= 3;
		}
	if (inlen > 0) {	// user provided max buffer size; make sure we don't go over it
		unsigned char oval = (in[0] << 4) & 0x30;
		*out++ = basis_64[in[0] >> 2];
		if (inlen > 1) oval |= in[1] >> 4;
		*out++ = basis_64[oval];
		*out++ = (inlen < 2) ? '=' : basis_64[(in[1] << 2) & 0x3c];
		*out++ = '=';
		}
	if (olen < outmax) *out = '\0';
	return true;
	}}}
bool decode64(const char * in, size_t inlen, char * out, size_t *outlen) {{{
	size_t len = 0,lup;
	int v1, v2, c3, c4;
	if (out == NULL) return false;	// check parameters
	if (in[0] == '+' && in[1] == ' ') in += 2;	// xxx these necessary?
	if (*in == '\r') return false;
	for (lup=0;lup<inlen/4;lup++) {
		v1 = in[0];
		if (CHAR64(v1) == -1) return false;
		v2 = in[1];
		if (CHAR64(v2) == -1) return false;
		c3 = in[2];
		if (c3 != '=' && CHAR64(c3) == -1) return false;
		c4 = in[3];
		if (c4 != '=' && CHAR64(c4) == -1) return false;
		in += 4;
		*out++ = (CHAR64(v1) << 2) | (CHAR64(v2) >> 4);
		++len;
		if (c3 != '=') {
			*out++ = ((CHAR64(v2) << 4) & 0xf0) | (CHAR64(c3) >> 2);
			++len;
			if (c4 != '=') {
				*out++ = ((CHAR64(c3) << 6) & 0xc0) | CHAR64(c4);
				++len;
				}
			}
		}
	*out=0; // terminate string
	*outlen=len;
	return true;
	}}}

