/*
 * 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 "md5crypt/md5crypt.h"
#include "byteswap.h"

#include "common.h"
#include "cc.h"
#include "system-common.h"
#include "network.h"
#include "data.h"
#include "misc.h"
#include "parse.h"

#include <openssl/des.h>
#include "openssl-compat.h"

#define CWS_NETMSGSIZE 240

#define DEBUG_NEWCAMD

#ifdef DEBUG_NEWCAMD
#define dn(x) { (x); }
#else
#define dn(x) ;
#endif

// -- cTripleDes ---------------------------------------------------------------

class cTripleDes {
private:
	DES_key_schedule ks1,ks2;
	//
	void SetOddParity(unsigned char *key); // key must be 16 bytes!
protected:
	unsigned char desKey[16];
	//
	void ScheduleKey(void);
	int PadMessage(unsigned char *data, int len);
	void Expand(unsigned char *expanded, const unsigned char *normal); // 14 byte key input, 16 byte expanded output
	void Decrypt(unsigned char *data, int len);
	const unsigned char *Encrypt(const unsigned char *data, int len, unsigned char *crypt);
};

void cTripleDes::SetOddParity(unsigned char *key)
{
	DES_set_odd_parity((DES_cblock *)&key[0]); // set odd parity on both keys
	DES_set_odd_parity((DES_cblock *)&key[8]); // 
}

void cTripleDes::ScheduleKey(void)
{
	DES_KEY_SCHED((DES_cblock *)&desKey[0], ks1);
	DES_KEY_SCHED((DES_cblock *)&desKey[8], ks2);
}

void cTripleDes::Expand(unsigned char *expand, const unsigned char *normal)
{
	expand[0]  =   normal[0] & 0xfe;
	expand[1]  = ((normal[0] << 7) | (normal[1] >> 1)) & 0xfe;
	expand[2]  = ((normal[1] << 6) | (normal[2] >> 2)) & 0xfe;
	expand[3]  = ((normal[2] << 5) | (normal[3] >> 3)) & 0xfe;
	expand[4]  = ((normal[3] << 4) | (normal[4] >> 4)) & 0xfe;
	expand[5]  = ((normal[4] << 3) | (normal[5] >> 5)) & 0xfe;
	expand[6]  = ((normal[5] << 2) | (normal[6] >> 6)) & 0xfe;
	expand[7]  =   normal[6] << 1;
	expand[8]  =   normal[7] & 0xfe;
	expand[9]  = ((normal[7] << 7)  | (normal[8] >> 1)) & 0xfe;
	expand[10] = ((normal[8] << 6)  | (normal[9] >> 2)) & 0xfe;
	expand[11] = ((normal[9] << 5)  | (normal[10] >> 3)) & 0xfe;
	expand[12] = ((normal[10] << 4) | (normal[11] >> 4)) & 0xfe;
	expand[13] = ((normal[11] << 3) | (normal[12] >> 5)) & 0xfe;
	expand[14] = ((normal[12] << 2) | (normal[13] >> 6)) & 0xfe;
	expand[15] =   normal[13] << 1;
	SetOddParity(expand);
}

int cTripleDes::PadMessage(unsigned char *data, int len)
{
	DES_cblock padBytes;
	unsigned char noPadBytes;

	noPadBytes = (8 - ((len - 1) % 8)) % 8;
	if(len+noPadBytes+1 >= CWS_NETMSGSIZE-8) {
		dn(printf("cc-newcamd: message overflow in cTripleDes::PadMessage\n"))
		return -1;
	}

	srand(time(NULL)); // make sure the random generator is initialized
	DES_random_key((DES_cblock *)padBytes);
	memcpy(data+len,padBytes,noPadBytes); len+=noPadBytes;
	data[len]=XorSum(data+2,len-2);
	return len+1;
}

const unsigned char *cTripleDes::Encrypt(const unsigned char *data, int len, unsigned char *crypt)
{
	DES_cblock ivec;
	DES_random_key((DES_cblock *)ivec);
	memcpy(crypt+len,ivec,sizeof(ivec));
	DES_EDE2_CBC_ENCRYPT(data+2,crypt+2,len-2,ks1,ks2,(DES_cblock *)ivec,DES_ENCRYPT);
	return crypt;
}

void cTripleDes::Decrypt(unsigned char *data, int len)
{
	if((len-2) % 8 || (len-2)<16) {
		dc(printf("cc-newcamd: warning, the encrypted data size mismatch\n"))
		return;
	}
	DES_cblock ivec;
	len-=sizeof(ivec); memcpy(ivec, data+len, sizeof(ivec));
	DES_EDE2_CBC_ENCRYPT(data+2,data+2,len-2,ks1,ks2,(DES_cblock *)ivec,DES_DECRYPT);
}

// -- cNewCamdClient -----------------------------------------------------------

#define USERLEN        32
#define PASSWDLEN      32
#define CWS_FIRSTCMDNO 0xe0

typedef enum {
	MSG_CLIENT_2_SERVER_LOGIN = CWS_FIRSTCMDNO,
	MSG_CLIENT_2_SERVER_LOGIN_ACK,
	MSG_CLIENT_2_SERVER_LOGIN_NAK,
	MSG_CARD_DATA_REQ,
	MSG_CARD_DATA,
	MSG_SERVER_2_CLIENT_NAME,
	MSG_SERVER_2_CLIENT_NAME_ACK,
	MSG_SERVER_2_CLIENT_NAME_NAK,
	MSG_SERVER_2_CLIENT_LOGIN,
	MSG_SERVER_2_CLIENT_LOGIN_ACK,
	MSG_SERVER_2_CLIENT_LOGIN_NAK,
	MSG_ADMIN,
	MSG_ADMIN_ACK,
	MSG_ADMIN_LOGIN,
	MSG_ADMIN_LOGIN_ACK,
	MSG_ADMIN_LOGIN_NAK,
	MSG_ADMIN_COMMAND,
	MSG_ADMIN_COMMAND_ACK,
	MSG_ADMIN_COMMAND_NAK
} net_msg_type_t;

typedef enum {
	COMMTYPE_CLIENT,
	COMMTYPE_SERVER
} comm_type_t;

struct CustomData {
	union {
		struct {
			unsigned short prgId; // Big-Endian
			unsigned char data[6];
		} V525;
		struct {
			unsigned int prgId;  // Big-Endian
		} V520;
	};
};

class cCardClientNewCamd : public cCardClient, private cTripleDes, private cIdSet {
private:
	cNetSocket so;
	unsigned char configKey[14];
	unsigned short netMsgId;
	int caId, protoVers, cdLen;
	bool emmProcessing;
	char username[USERLEN], password[PASSWDLEN];
	//
	void InitVars(void);
	void InitProtoVers(int vers);
	bool NextProto(void);
	void InitCustomData(struct CustomData *cd, const unsigned short PrgId, const unsigned char *data);
	void PrepareLoginKey(unsigned char *deskey, const unsigned char *rkey, const unsigned char *ckey);
protected:
	virtual bool Login(void);
public:
	cCardClientNewCamd(const char *Name);
	// Client Helper functions
	bool SendMessage(cNetSocket *so, const unsigned char *data, int len, bool UseMsgId, const struct CustomData *cd=0, comm_type_t commType=COMMTYPE_CLIENT);
	int ReceiveMessage(cNetSocket *so, unsigned char *data, bool UseMsgId, struct CustomData *cd=0, comm_type_t commType=COMMTYPE_CLIENT);
	bool CmdSend(cNetSocket *so, net_msg_type_t cmd,  comm_type_t commType=COMMTYPE_CLIENT);
	int CmdReceive(cNetSocket *so, comm_type_t commType=COMMTYPE_CLIENT);
	//
	virtual bool Init(const char *CfgDir);
	virtual bool CanHandle(unsigned short SysId);
	virtual bool ProcessECM(const cEcmInfo *ecm, const unsigned char *data, unsigned char *Cw);
};

static cCardClientLinkReg<cCardClientNewCamd> __ncd("Newcamd");

cCardClientNewCamd::cCardClientNewCamd(const char *Name)
:cCardClient(Name)
,so(DEFAULT_CONNECT_TIMEOUT,20,DEFAULT_IDLE_TIMEOUT)
{
	memset(username,0,sizeof(username));
	memset(password,0,sizeof(password));
	InitVars();
	InitProtoVers(525);
}

void cCardClientNewCamd::InitVars(void)
{
	netMsgId=0; caId=-1; emmProcessing=false;
	ResetIdSet();
}

void cCardClientNewCamd::InitProtoVers(int vers)
{
	switch(vers) {
		case 525: protoVers=525; cdLen=8; break;
		default:  protoVers=520; cdLen=4; break;
	}
	dc(printf("cc-newcamd: now using protocol version %d (cdLen=%d)\n",protoVers,cdLen))
}

bool cCardClientNewCamd::NextProto(void)
{
	switch(protoVers) {
		case 525: InitProtoVers(520); break;
		default:  return false;
	}
	return true;
}

void cCardClientNewCamd::InitCustomData(struct CustomData *cd, const unsigned short PrgId, const unsigned char *data)
{
	if(cd) {
		switch(protoVers) {
			case 525:
				cd->V525.prgId=bswap_16(PrgId);
				if(data) memcpy(cd->V525.data,data,sizeof(cd->V525.data));
				else memset(cd->V525.data,0,sizeof(cd->V525.data));
				break;
			default:
				cd->V520.prgId=bswap_32((unsigned int)PrgId);
				break;
		}
	}
}

void cCardClientNewCamd::PrepareLoginKey(unsigned char *deskey, const unsigned char *rkey, const unsigned char *ckey)
{
	unsigned char tmpkey[14];
	for (int i=0; i<(int)sizeof(tmpkey); i++) { tmpkey[i]=rkey[i]^ckey[i]; }
	Expand(deskey, tmpkey);
}

bool cCardClientNewCamd::SendMessage(cNetSocket *so, const unsigned char *data, int len, bool UseMsgId, const struct CustomData *cd, comm_type_t commType)
{
	if(len<3||len+cdLen+4>CWS_NETMSGSIZE) {
		dn(printf("cc-newcamd: bad message size %d in SendMessage\n",len))
		return false;
	}
	unsigned char netbuf[CWS_NETMSGSIZE];
	memset(&netbuf[2],0,cdLen+2);
	memcpy(&netbuf[cdLen+4],data,len);
	netbuf[cdLen+4+1]=(data[1]&0xf0)|(((len-3)>>8)&0x0f);
	netbuf[cdLen+4+2]=(len-3)&0xff;
	len+=4;
	if(cd) memcpy(&netbuf[4],cd,cdLen);
	len+=cdLen;
	if(UseMsgId) {
		if(commType==COMMTYPE_CLIENT) netMsgId++;
		netbuf[2]=netMsgId>>8;
		netbuf[3]=netMsgId&0xff;
	}
	if((len=cTripleDes::PadMessage(netbuf,len))<0) {
		dn(printf("cc-newcamd: PadMessage failed\n"))
		return false;
	}
	if((data=cTripleDes::Encrypt(netbuf,len,netbuf))==0) {
		dn(printf("cc-newcamd: Encrypt failed\n"))
		return false;
	}
	len+=sizeof(DES_cblock);
	netbuf[0]=(len-2)>>8;
	netbuf[1]=(len-2)&0xff;
	return cCardClient::SendMsg(so,netbuf,len);
}

int cCardClientNewCamd::ReceiveMessage(cNetSocket *so, unsigned char *data, bool UseMsgId, struct CustomData *cd, comm_type_t commType)
{
	unsigned char netbuf[CWS_NETMSGSIZE];
	int len=cCardClient::RecvMsg(so,netbuf,2);
	if(len!=2) {
		dn(printf("cc-newcamd: bad length %d != 2 on message length read\n",len))
		return 0;
	}
	const int mlen=WORD(netbuf,0,0xFFFF);
	if(mlen>CWS_NETMSGSIZE-2) {
		dc(printf("cc-newcamd: error: buffer overflow\n"))
		return 0;
	}
	len=cCardClient::RecvMsg(so,netbuf+2,mlen);
	if(len!=mlen) {
		dn(printf("cc-newcamd: bad length %d != %d on message read\n",len,mlen))
		return 0;
	}
	len+=2;
	cTripleDes::Decrypt(netbuf,len); len-=sizeof(DES_cblock);
	if(XorSum(netbuf+2, len-2)) {
		dn(printf("cc-newcamd: checksum error\n"))
		return 0;
	}

	int returnLen=WORD(netbuf,5+cdLen,0x0FFF)+3;
	if(cd) memcpy(cd,&netbuf[4],cdLen);
	if(UseMsgId) {
		switch(commType) {
			case COMMTYPE_SERVER:
				netMsgId=WORD(netbuf,2,0xFFFF);
				break;
			case COMMTYPE_CLIENT:
				if(netMsgId!=WORD(netbuf,2,0xFFFF)) {
					dn(printf("cc-newcamd: bad msgid %04x != %04x \n",netMsgId,WORD(netbuf,2,0xFFFF)))
					return -1;
				}
				break;
			default:
				dn(printf("cc-newcamd: unknown commType %x\n",commType))
				return -1;
		}
	}
	memcpy(data,netbuf+4+cdLen,returnLen);
	return returnLen;
}

bool cCardClientNewCamd::CmdSend(cNetSocket *so, net_msg_type_t cmd, comm_type_t commType)
{
	unsigned char buffer[3];
	buffer[0] = cmd; buffer[1] = buffer[2] = 0;
	return SendMessage(so,buffer,sizeof(buffer),false,0,commType);
}

int cCardClientNewCamd::CmdReceive(cNetSocket *so, comm_type_t commType)
{
	unsigned char buffer[CWS_NETMSGSIZE];
	if(ReceiveMessage(so,buffer,false,0,commType)!=3) return -1;
	return buffer[0];
}

bool cCardClientNewCamd::CanHandle(unsigned short SysId)
{
	return (caId>=0 && SysId==caId) || cCardClient::CanHandle(SysId);
}

bool cCardClientNewCamd::Init(const char *config)
{
	cMutexLock lock(this);
	int num=0;
	char key[29];
	const char *tmp=key;
	if(!ParseStdConfig(config,&num)
		|| sscanf(&config[num],":%31[^:]:%31[^:]:%28[^:]",username,password,key)!=3
		|| GetHex(tmp,configKey,sizeof(configKey),false)!=14) return false;
#ifdef DEBUG_CC
	char str[32];
	printf("cc-newcamd: username=%s password=%s key=%s\n",username,password,HexStr(str,configKey,14));
#endif
	return Immediate() ? Login() : true;
}

bool cCardClientNewCamd::Login(void)
{
	so.Disconnect();
	if(!so.Connect(hostname,port)) return false;

	InitVars();
	unsigned char randData[14];
	if(so.Read(randData,sizeof(randData))!=14) {
		dc(printf("cc-newcamd: no connect answer from %s:%d\n",hostname,port))
		so.Disconnect();
		return false;
	}

	char *crPasswd=md5_crypt(password,"$1$abcdefgh$");
	unsigned char buffer[CWS_NETMSGSIZE];
	const int userLen=strlen(username)+1;
	const int passLen=strlen(crPasswd)+1;
  
	// prepare the initial login message
	buffer[0] = MSG_CLIENT_2_SERVER_LOGIN;
	buffer[1] = 0;
	buffer[2] = userLen+passLen;
	memcpy(&buffer[3],username,userLen);
	memcpy(&buffer[3]+userLen,crPasswd,passLen);

	// XOR configKey with randData and expand the 14 byte result -> 16 byte
	PrepareLoginKey(desKey,randData,configKey);
	cTripleDes::ScheduleKey();

	if(!SendMessage(&so,buffer,buffer[2]+3,true) || CmdReceive(&so)!=MSG_CLIENT_2_SERVER_LOGIN_ACK) {
		d(printf("cc-newcamd: failed to login to cardserver for username %s (proto %d)\n",username,protoVers))
		if(NextProto()) return Login();
		return false;
	}

	// create the session key (for usage later)
	unsigned char tmpkey[14];
	memcpy(tmpkey, configKey, sizeof(tmpkey));
	const int passStrLen=strlen(crPasswd);
	for(int i=0; i<passStrLen; ++i) tmpkey[i%14]^=crPasswd[i];

	cTripleDes::Expand(desKey,tmpkey); // expand 14 byte key -> 16 byte
	cTripleDes::ScheduleKey();

	if(!CmdSend(&so,MSG_CARD_DATA_REQ) || ReceiveMessage(&so,buffer,false)<=0) return false;
	if(buffer[0] == MSG_CARD_DATA) {
		caId=(buffer[4]<<8)+buffer[5];
#ifdef DEBUG_CC
		char str[32], str2[32];
		printf("cc-newcamd: CaID=%04x admin=%d srvUA=%s",caId,buffer[3]==1,HexStr(str,&buffer[6],8));
#endif
		if(!CheckNull(&buffer[6],8)) {
			emmProcessing=true;
			switch(caId>>8) {
				case 0x17:
				case 0x06: SetCard(new cCardIrdeto(buffer[6+4],&buffer[6+5])); break;
				case 0x01: SetCard(new cCardSeca(&buffer[6+2])); break;
				case 0x0b: SetCard(new cCardConax(&buffer[6+1])); break;
				case 0x09: SetCard(new cCardNDS(&buffer[6+4])); break;
				case 0x05: SetCard(new cCardViaccess(&buffer[6+3])); break;
				case 0x0d: SetCard(new cCardCryptoworks(&buffer[6+3])); break;
				case 0x18: if(caId==0x1801) {
						SetCard(new cCardNagra2(&buffer[6+4]));
						break;
					}
					// fall through to default
				default:
					dc(printf(" (unhandled)"))
					break;
			}
		}
		dc(printf(" provider"))
		for(int i=(buffer[14]-1)*11; i>=0; i-=11) {
			dc(printf(" %s/%s",HexStr(str2,&buffer[15+i],3),HexStr(str,&buffer[18+i],8)))
			if(!CheckNull(&buffer[18+i],8)) {
				switch(caId>>8) {
					case 0x17:
					case 0x06: AddProv(new cProviderIrdeto(buffer[18+i+4],&buffer[18+i+5])); break;
					case 0x01: AddProv(new cProviderSeca(&buffer[15+i+1],&buffer[18+i+5])); break;
					case 0x0b: AddProv(new cProviderConax(&buffer[18+i+1])); break;
					case 0x09: AddProv(new cProviderNDS(&buffer[18+i+4])); break;
					case 0x05: AddProv(new cProviderViaccess(&buffer[15+i],&buffer[18+i+4])); break;
					case 0x0d: AddProv(new cProviderCryptoworks(&buffer[18+i+3])); break;
					default:
						dc(printf(" <unhandled>"))
						break;
				}
			}
		}
		dc(printf("\n"))
		if(emmProcessing && !emmAllowed) printf("cc-newcamd: EMM disabled from config\n");
	}
	return true;
}

bool cCardClientNewCamd::ProcessECM(const cEcmInfo *ecm, const unsigned char *data, unsigned char *cw)
{
	cMutexLock lock(this);
	if((!so.Connected() && !Login()) || !CanHandle(ecm->caId)) return false;
//	so.Flush(); //no idea how to flush under win atm :P

	struct CustomData cd;
	InitCustomData(&cd,(unsigned short)ecm->prgId,0);
	if(!SendMessage(&so,data,SCT_LEN(data),true,&cd)) return false;
	unsigned char buffer[CWS_NETMSGSIZE];
	switch(ReceiveMessage(&so,buffer,true)) {
		case 19: // ecm was decoded
			// check for zero cw, as newcs doesn't send both cw's every time
			if(!CheckNull(buffer+3+0,8)) memcpy(cw+0,buffer+3+0,8);
			if(!CheckNull(buffer+3+8,8)) memcpy(cw+8,buffer+3+8,8);
			return true;
		case 3:
			dc(printf("cc-newcamd: card was not able to decode the channel\n"))
			break;
		default:
			dc(printf("cc-newcamd: warning an unexpected error occurred\n"))
			break;
	}
	return false;
}