/*
 * Softcam plugin to VDR (C++)
 *
 * This code 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 code 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.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 */

#include "stdafx.h"

#include <string.h>
#include "regex.h"

#include "common.h"
#include "sc.h"
#include "system.h"
#include "system-common.h"
#include "data.h"
#include "misc.h"
#include "crypto.h"
#include "parse.h"
#include "network.h"
#include "opts.h"
#include "win32lacks.h"

#define SYSTEM_VIACCESS      0x0500

#define SYSTEM_NAME          "Viaccess"
#define SYSTEM_PRI           -10
#define SYSTEM_CAN_HANDLE(x) ((x)==SYSTEM_VIACCESS)

static char AESKeyHost[30]="213.251.166.40";
static int AESKeyPort=80;
static char AESKeyPath[30]="~aes/aes/";

//#define DEBUG_LOG

#ifdef DEBUG_LOG
#define dl(x) { (x); }
#else
#define dl(x) ; 
#endif

// -- cPlainKeyVia -------------------------------------------------------------

#define VIA1_KEYLEN 8
#define VIA2_KEYLEN 16
#define VIATPS_KEYLEN 16

class cPlainKeyVia : public cPlainKeyStd {
protected:
	virtual cString PrintKeyNr(void);
public:
	cPlainKeyVia(bool Super);
	virtual bool Parse(const char *line);
	virtual bool SetKey(void *Key, int Keylen);
};

static cPlainKeyTypeReg<cPlainKeyVia,'V',false> KeyReg;

cPlainKeyVia::cPlainKeyVia(bool Super)
:cPlainKeyStd(Super)
{}

bool cPlainKeyVia::SetKey(void *Key, int Keylen)
{
	if(keynr==MBC3('T','P','S')) SetSupersede(false);
	return cPlainKeyStd::SetKey(Key,Keylen);
}

bool cPlainKeyVia::Parse(const char *line)
{
	unsigned char sid[3], skeynr, skey[VIA2_KEYLEN];
#ifdef DEBUG
	const char *sline=line;
#endif
	int len;
	if(GetChar(line,&type,1) && (len=GetHex(line,sid,3,false))) {
		type=toupper(type); id=Bin2Int(sid,len);
		line=skipspace(line);
		bool ok=false;
		if(!strncasecmp(line,"TPS",3)) {
			line+=3;
			keynr=MBC3('T','P','S');
			ok=(len=GetHex(line,skey,VIATPS_KEYLEN));
		}
		else if(GetHex(line,&skeynr,1)) {
			keynr=skeynr;
			ok=((len=GetHex(line,skey,VIA2_KEYLEN,false)) && (len==VIA1_KEYLEN || len==VIA2_KEYLEN));
		}
		if(ok) {
			SetKey(skey,len);
			return true;
		}
	}
	d(printf("plainkeyvia: bad key format '%s'\n",sline))
	return false;
}

cString cPlainKeyVia::PrintKeyNr(void)
{
	return cString::sprintf((keynr==MBC3('T','P','S'))?"TPS":"%02X",keynr);
}

// -- cViaccessCardInfo --------------------------------------------------------

class cViaccessCardInfo : public cSimpleItem {
public:
	unsigned char ID[3], UA[5], SA[4], KEYNO[1], KEY[8];
	//
	bool Parse(const char *line);
	bool Save(FILE *f) { return true; }
	bool IsUpdated(void) { return false; }
	void Updated(void) {}
	bool Cmp(cViaccessCardInfo *ci) { return false; }
};

bool cViaccessCardInfo::Parse(const char *line)
{
	return	GetHex(line,ID,sizeof(ID)) && 
			GetHex(line,UA,sizeof(UA)) &&
			GetHex(line,SA,sizeof(SA)) &&
			GetHex(line,KEYNO,sizeof(KEYNO)) && 
			GetHex(line,KEY,sizeof(KEY));
}

// -- cViaccessCardInfos -------------------------------------------------------

class cViaccessCardInfos : public cCardInfos<cViaccessCardInfo> {
public:
	cViaccessCardInfos(void):cCardInfos<cViaccessCardInfo>(SYSTEM_NAME) {}
};

static cViaccessCardInfos Vcards;

// -- cViaccess ----------------------------------------------------------------

class cViaccess : protected cDes {
private:
	unsigned char v2key[8];
	bool v2mode;
	//
	int HashNanos(const unsigned char *data, int len);
	void Via2Mod(const unsigned char *key2, unsigned char *data);
protected:
	unsigned char hbuff[8], hkey[8];
	int pH;
	//
	void SetV2Mode(const unsigned char *key2);
	void SetHashKey(const unsigned char *key);
	void HashByte(unsigned char c);
	void HashClear(void);
	void Hash(void);
	//
	void Decode(unsigned char *data, const unsigned char *key);
	bool Decrypt(const unsigned char *work_key, const unsigned char *data, int len, unsigned char *des_data1, unsigned char *des_data2);
	//
	virtual unsigned int Mod(unsigned int R, unsigned int key7) const;
public:
	cViaccess(void);
};

cViaccess::cViaccess(void)
:cDes()
{
	v2mode=false;
}

/* viaccess DES modification */

unsigned int cViaccess::Mod(unsigned int R, unsigned int key7) const
{
	if(key7!=0) {
		const unsigned int key5=(R>>24)&0xff;
		unsigned int al=key7*key5 + key7 + key5;
		al=(al&0xff)-((al>>8)&0xff);
		if(al&0x100) al++;
		R=(R&0x00ffffffL) + (al<<24);
	}
	return R;
}

/* viaccess2 modification. Extracted from "Russian wafer" card.
   A lot of thanks to it's author :) */

void cViaccess::Via2Mod(const unsigned char *key2, unsigned char *data)
{
	int kb, db;
	for(db=7; db>=0; db--) {
		for(kb=7; kb>3; kb--) {
			int a0=kb^db;
			int pos=7;
			if(a0&4) { a0^=7; pos^=7; }
			a0=(a0^(kb&3)) + (kb&3);
			if(!(a0&4)) data[db]^=(key2[kb] ^ ((data[kb^pos]*key2[kb^4]) & 0xFF));
		}
	}
	for(db=0; db<8; db++) {
		for(kb=0; kb<4; kb++) {
			int a0=kb^db;
			int pos=7;
			if(a0&4) { a0^=7; pos^=7; }
			a0=(a0^(kb&3)) + (kb&3);
			if(!(a0&4)) data[db]^=(key2[kb] ^ ((data[kb^pos]*key2[kb^4]) & 0xFF));
		}
    }
}

void cViaccess::Decode(unsigned char *data, const unsigned char *key)
{
	if(v2mode) Via2Mod(v2key,data);
	Des(data,key,VIA_DES);
	if(v2mode) Via2Mod(v2key,data);
}

void cViaccess::SetV2Mode(const unsigned char *key2)
{
	if(key2) {
		memcpy(v2key,key2,sizeof(v2key));
		v2mode=true;
    }
	else v2mode=false;
}


void cViaccess::SetHashKey(const unsigned char *key)
{
	memcpy(hkey,key,sizeof(hkey));
}

void cViaccess::HashByte(unsigned char c)
{
	hbuff[pH++]^=c;
	if(pH==8) { pH=0; Hash(); }
}

void cViaccess::HashClear(void)
{
	memset(hbuff,0,sizeof(hbuff));
	pH=0;
}

void cViaccess::Hash(void)
{
	if(v2mode) Via2Mod(v2key,hbuff);
	Des(hbuff,hkey,VIA_DES_HASH);
	if(v2mode) Via2Mod(v2key,hbuff);
}

int cViaccess::HashNanos(const unsigned char *data, int len)
{
	int i=0;
	pH=0;
	if(data[0]==0x9f) {
		HashByte(data[i++]);
		HashByte(data[i++]);
		for(int j=0; j<data[1]; j++) HashByte(data[i++]);
		while(pH!=0) HashByte(0);
    }
	for(; i<len; i++) HashByte(data[i]);
	return i;
}

bool cViaccess::Decrypt(const unsigned char *work_key, const unsigned char *data, int len, unsigned char *des_data1, unsigned char *des_data2)
{
	int pos=0, encStart=0;
	unsigned char signatur[8];
	while(pos<len) {
		switch(data[pos]) {
		case 0xea:                 // encrypted bytes
			encStart = pos + 2;
			memcpy(des_data1,&data[pos+2],8);
			memcpy(des_data2,&data[pos+2+8],8);
			break;
		case 0xf0:                 // signature
			memcpy(signatur,&data[pos+2],8);
			break;
		}
		pos += data[pos+1]+2;
    }
	HashClear();
	SetHashKey(work_key);
	// key preparation
	unsigned char prepared_key[8];
	if(work_key[7]==0) {
		// 8th key-byte = 0 then like Eurocrypt-M but with viaccess mods
		HashNanos(data,encStart+16);
		memcpy(prepared_key,work_key,sizeof(prepared_key));
    }
	else { // key8 not zero
		// rotate the key 2x left
		prepared_key[0]=work_key[2];
		prepared_key[1]=work_key[3];
		prepared_key[2]=work_key[4];
		prepared_key[3]=work_key[5];
		prepared_key[4]=work_key[6];
		prepared_key[5]=work_key[0];
		prepared_key[6]=work_key[1];
		prepared_key[7]=work_key[7];
		// test if key8 odd
		if(work_key[7]&1) {
			HashNanos(data,encStart);
			// test if low nibble zero
			unsigned char k = ((work_key[7] & 0xf0) == 0) ? 0x5a : 0xa5;
			for(int i=0; i<8; i++) {
				unsigned char tmp=des_data1[i];
				des_data1[i]=(k & hbuff[pH]) ^ tmp;
				HashByte(tmp);
			}
			for(int i=0; i<8; i++) {
				unsigned char tmp=des_data2[i];
				des_data2[i]=(k & hbuff[pH]) ^ tmp;
				HashByte(tmp);
			}
		}
		else {
			HashNanos(data,encStart+16);
		}
	}
	Decode(des_data1,prepared_key);
	Decode(des_data2,prepared_key);
	Hash();
	return (memcmp(signatur,hbuff,8)==0);
}

// -- cTPS ---------------------------------------------------------------------

class cTPS : private cAES {
private:
	bool keySet;
	static const unsigned char T1[], T2[];
protected:
	void SetV2Key(const unsigned char *key);
	int Decrypt(unsigned char *data, int len);
public:
	cTPS(void);
};

const unsigned char cTPS::T1[] = {
  0x4F,0xB4,0xFC,0x9B,0x4A,0x7F,0x44,0xFB,0x05,0xFF,0xBD,0xBB,0x16,0x2D,0x6C,0xC8,
  0xD8,0x96,0xF9,0xFE,0x3F,0xFF,0x36,0x24,0xB6,0xBF,0x49,0xC9,0x2D,0x36,0x5E,0xD0,
  0x1F,0x09,0x7E,0xA9,0x7F,0xFF,0x64,0xB6,0x5B,0x7E,0xF8,0xFC,0x6E,0x3F,0x7F,0xBF,
  0xDD,0x36,0x12,0xE9,0x05,0xFE,0xB4,0x6C,0x6F,0xFE,0x7E,0xC8,0x25,0x90,0x6D,0x90
  };

const unsigned char cTPS::T2[] = {
  0x7E,0x6D,0x7E,0x12,0x76,0xFD,0x2F,0xFE,0x6D,0xFE,0xDA,0x3F,0xDA,0x6D,0xBD,0x97,
  0xD0,0x6D,0xD8,0x9F,0x69,0xFD,0xB6,0x37,0xFE,0x7F,0x36,0x92,0xBD,0x52,0x16,0xDF,
  0xFC,0x96,0xFF,0x92,0xFD,0x6D,0x7F,0xB5,0xFB,0x4C,0xB6,0xB7,0x7E,0xD9,0xFE,0x9B,
  0xFD,0xF4,0x6D,0x9B,0xB9,0x36,0xBF,0x7F,0xD2,0x2D,0xDF,0xB7,0xD9,0xFE,0x69,0xBF
  };

cTPS::cTPS(void)
{
	keySet=false;
}

void cTPS::SetV2Key(const unsigned char *key)
{
	cAES::SetKey(key); keySet=true;
}

int cTPS::Decrypt(unsigned char *data, int len)
{
	if(data[0]!=0xD2 || data[1]!=0x01 || data[2]!=0x01) return 0;
	data+=3; len-=3;
	if(data[0]==0x40) { // TPS crypt v2 (AES)
		if(!keySet) return -1;
		data[0]=0x90;
		for(int i=0; i<len; i+=data[i+1]+2)
			if(data[i]==0xEA) {
				cAES::Decrypt(&data[i+2],16);
				break;
			}
		return 2;
	}
	else {              // TPS crypt v1 (XOR)
		const unsigned char * const table=(data[0]==0xDF) ? T1 : T2;
		for(int i=len-1 ; i>=0 ; i--) data[i]^=table[i&63];
		return 1;
    }
}

// -- cSystemViaccess ----------------------------------------------------------

struct ViaccessSOIDData {
	unsigned char ident[3];
	unsigned char keyno;
};

#define MAX_NEW_KEYS 5

class cSystemViaccess : public cSystem, private cViaccess, private cTPS {
private:
	struct ViaccessSOIDData sd;
	unsigned char emmBuff[256];
	int emmLen;
	bool haveSOID;
	//
	bool DownloadAES(unsigned char *key);
	bool ParseAES(const regex_t *re, regmatch_t *pm, const char *buf, unsigned char *key);
public:
	cSystemViaccess(void);
	virtual bool ProcessECM(const cEcmInfo *ecm, unsigned char *data);
	virtual void ProcessEMM(int pid, int caid, unsigned char *buffer);
};

cSystemViaccess::cSystemViaccess(void)
:cSystem(SYSTEM_NAME,SYSTEM_PRI)
{
  haveSOID=false; emmLen=0;
  hasLogger=true;
}

bool cSystemViaccess::ParseAES(const regex_t *re, regmatch_t *pm, const char *buf, unsigned char *key)
{
	int i, j;
	unsigned char val;

	if (regexec(re, buf, 1, pm, 0))
		return false;

	for (i = 0, j = 0; i < 23; ++i)
		if (i % 3 == 0)
			val = (buf[pm->rm_so + i] < 'A' ? buf[pm->rm_so + i] - '0' : buf[pm->rm_so + i] - 'A' + 10) * 16;
		else if (i % 3 == 1)
			key[j++] = val + (buf[pm->rm_so + i] < 'A' ? buf[pm->rm_so + i] - '0' : buf[pm->rm_so + i] - 'A' + 10);

	return (j == 8);
}

bool cSystemViaccess::DownloadAES(unsigned char *key)
{
	char buf[4096], *ptr;
	regex_t re;
	regmatch_t pm;

	cNetSocket so(DEFAULT_CONNECT_TIMEOUT, DEFAULT_READWRITE_TIMEOUT, DEFAULT_IDLE_TIMEOUT, false);
	if (!so.Connect(AESKeyHost, AESKeyPort))
		return false;

	memset(buf, 0x00, sizeof(buf));
	sprintf(buf, "GET /%s HTTP/1.1\r\nHost: %s\r\nUser-Agent: Mozilla\r\nReferer: http://%s/%s\r\n\r\n", AESKeyPath, AESKeyHost, AESKeyHost, AESKeyPath);
	so.Write((unsigned char *) buf, strlen(buf));
	memset(buf, 0x00, sizeof(buf));
	so.Read((unsigned char *) buf, sizeof(buf) - 1);

	if (regcomp(&re, "[0-9A-F]{2}( [0-9A-F]{2}){7}", REG_EXTENDED))
		return false;
	if (!ParseAES(&re, &pm, buf, key))
		return false;
	if (!ParseAES(&re, &pm, buf + pm.rm_eo, key + 8))
		return false;

	return true;
}

bool cSystemViaccess::ProcessECM(const cEcmInfo *ecm, unsigned char *data)
{
	int len=SCT_LEN(data);
	unsigned char tmp[0x100];
	cPlainKey *tpsPk=0;
	cKeySnoop *ks=0;
	while(1) {
		memcpy(tmp,data,len);
		unsigned char *nanos=(unsigned char *)cParseViaccess::NanoStart(tmp);
		int nlen=len-(nanos-tmp);

		int tps=cTPS::Decrypt(nanos,nlen);
		if(tps>0) { nanos+=3; nlen-=3; }

		unsigned char key[16];
		if(tps>=0 && cParseViaccess::CheckNano90FromNano(nanos)) {
			int keynr=cParseViaccess::KeyNrFromNano(nanos);
			if(!ks) ks=new cKeySnoop(this,'V',ecm->provId,keynr);
			cPlainKey *pk=0;
			while((pk=keys.FindKey('V',ecm->provId,keynr,-1,pk))) {
				pk->Get(key);
				SetV2Mode(pk->Size()==VIA2_KEYLEN ? &key[VIA1_KEYLEN] : 0);
				if(cViaccess::Decrypt(key,&nanos[5],nlen-5,&cw[0],&cw[8])) {
					ks->OK(pk); delete ks;
					return true;
				}
			}
		}

		if(tps==1 || tps==0) {
			delete ks;
			return false;
		}

		tpsPk=keys.FindKey('V',ecm->provId,MBC3('T','P','S'),sizeof(key),tpsPk);
		if(!tpsPk) {
			unsigned char newKey[16];
/*			if (DownloadAES(newKey)) {
				cSoftCAM::FoundKey();
				if (keys.NewKey('V',ecm->provId,MBC3('T','P','S'),newKey,16)) {
					cSoftCAM::NewKey();
					cSoftCAM::Save();
					tpsPk=keys.FindKey('V',ecm->provId,MBC3('T','P','S'),16,tpsPk);
				}
			}*/
			if(!tpsPk) {
				d(printf("system-viaccess: missing/invalid %.4x TPS or OP key\n",ecm->provId))
				delete ks;
				return false;
			}
		}
		tpsPk->Get(key);
		cTPS::SetV2Key(key);
	}
}

void cSystemViaccess::ProcessEMM(int pid, int caid, unsigned char *buffer)
{
	unsigned char ua[5], sa[3], signature[8], addressdata[34];
	unsigned char *scan=0;
	unsigned int updtype=0, scanlen=0;

	bool haveData=false;
	const unsigned int msgLen=buffer[2];
	switch(buffer[0]) {
		case 0x8C: // shared update message
		case 0x8D:
			if(msgLen<=sizeof(emmBuff)) {
				scan=&buffer[3]; // Skip SA
			}
		case 0x8E: // shared update data/message
			{

				static const unsigned char addr_nano[] = { 0x9E,0x20 };

				memcpy(sa,&buffer[3],sizeof(sa));
			}
		case 0x88:
			{
				if(msgLen>=0xB4) {
					return;
				}
			}
	}
}

// -- cSystemLinkViaccess ------------------------------------------------------

class cSystemLinkViaccess : public cSystemLink {
public:
  cSystemLinkViaccess(void);
  virtual bool CanHandle(unsigned short SysId);
  virtual cSystem *Create(void) { return new cSystemViaccess; }
  virtual bool Init(const char *cfgdir);
  };

static cSystemLinkViaccess staticInit;

cSystemLinkViaccess::cSystemLinkViaccess(void)
:cSystemLink(SYSTEM_NAME,SYSTEM_PRI)
{
	opts=new cOpts(SYSTEM_NAME,3);
	opts->Add(new cOptStr("AESKeyHost","Viaccess: AES Key website host",AESKeyHost,sizeof(AESKeyHost),"0123456789abcdefghijklmnopqrstuvwxyz.-"));
	opts->Add(new cOptInt("AESKeyPort","Viaccess: AES Key website port",&AESKeyPort,1,65535));
	opts->Add(new cOptStr("AESKeyPath","Viaccess: AES Key website path",AESKeyPath,sizeof(AESKeyPath),"0123456789abcdefghijklmnopqrstuvwxyz.-_/~"));
	Feature.NeedsKeyFile();
}

bool cSystemLinkViaccess::CanHandle(unsigned short SysId)
{
  SysId&=SYSTEM_MASK;
  return SYSTEM_CAN_HANDLE(SysId);
}

bool cSystemLinkViaccess::Init(const char *cfgdir)
{
  Vcards.Load(cfgdir,SYSTEM_NAME,"Viaccess.KID");
  return true;
}