/* vim:set sw=4 ts=8 fileencoding=cp1251::Кодировка:WINDOWS-1251[АБЁЪЯабёъя] */
/*
 * Copyright(C) 2013-2014 Проект ИОК
 *
 * Этот файл содержит информацию, являющуюся
 * собственностью компании Крипто Про.
 *
 * Разрешается повторное распространение и использование как в виде 
 * исходного кода, так и в двоичной форме, с изменениями или без, 
 * при соблюдении следующих условий: 
 *   1. При повторном распространении исходного кода должно оставаться 
 *      указанное выше уведомление об авторском праве, этот список 
 *      условий и последующий отказ от гарантий. 
 *   2. При повторном распространении двоичного кода должно сохраняться 
 *      указанная выше информация об авторском праве, этот список 
 *      условий и последующий отказ от гарантий в документации и/или 
 *      в других материалах, поставляемых при распространении. 
 * 
 * ЭТА ПРОГРАММА ПРЕДОСТАВЛЕНА БЕСПЛАТНО ВЛАДЕЛЬЦАМИ АВТОРСКИХ ПРАВ 
 * И/ИЛИ ДРУГИМИ СТОРОНАМИ "КАК ОНА ЕСТЬ" БЕЗ КАКОГО-ЛИБО ВИДА ГАРАНТИЙ, 
 * ВЫРАЖЕННЫХ ЯВНО ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ, 
 * ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ КОММЕРЧЕСКОЙ ЦЕННОСТИ И ПРИГОДНОСТИ ДЛЯ 
 * КОНКРЕТНОЙ ЦЕЛИ. НИ В КОЕМ СЛУЧАЕ, ЕСЛИ НЕ ТРЕБУЕТСЯ СООТВЕТСТВУЮЩИМ 
 * ЗАКОНОМ, ИЛИ НЕ УСТАНОВЛЕНО В УСТНОЙ ФОРМЕ, НИ ОДИН ВЛАДЕЛЕЦ АВТОРСКИХ 
 * ПРАВ И НИ ОДНО ДРУГОЕ ЛИЦО, КОТОРОЕ МОЖЕТ ИЗМЕНЯТЬ И/ИЛИ ПОВТОРНО 
 * РАСПРОСТРАНЯТЬ ПРОГРАММУ, КАК БЫЛО СКАЗАНО ВЫШЕ, НЕ НЕСЁТ 
 * ОТВЕТСТВЕННОСТИ, ВКЛЮЧАЯ ЛЮБЫЕ ОБЩИЕ, СЛУЧАЙНЫЕ, СПЕЦИАЛЬНЫЕ ИЛИ 
 * ПОСЛЕДОВАВШИЕ УБЫТКИ, В СЛЕДСТВИИ ИСПОЛЬЗОВАНИЯ ИЛИ НЕВОЗМОЖНОСТИ 
 * ИСПОЛЬЗОВАНИЯ ПРОГРАММЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ПОТЕРЕЙ ДАННЫХ, 
 * ИЛИ ДАННЫМИ, СТАВШИМИ НЕПРАВИЛЬНЫМИ, ИЛИ ПОТЕРЯМИ ПРИНЕСЕННЫМИ ИЗ-ЗА 
 * ВАС ИЛИ ТРЕТЬИХ ЛИЦ, ИЛИ ОТКАЗОМ ПРОГРАММЫ РАБОТАТЬ СОВМЕСТНО С 
 * ДРУГИМИ ПРОГРАММАМИ), ДАЖЕ ЕСЛИ ТАКОЙ ВЛАДЕЛЕЦ ИЛИ ДРУГОЕ ЛИЦО БЫЛИ 
 * ИЗВЕЩЕНЫ О ВОЗМОЖНОСТИ ТАКИХ УБЫТКОВ
 *
 * Программный код, содержащийся в этом файле, предназначен
 * исключительно для целей обучения и не может быть использован
 * для защиты информации.
 *
 * Компания Крипто-Про не несет никакой
 * ответственности за функционирование этого кода.
 */

/*!
 * \file $RCSfile$
 * \version $Revision: 59519 $
 * \date $Date: 2010-01-13 15:36:15 +0300 (Wed, 13 Jan 2010) $
 * \author $Author: dedal $
 *
 * \brief Реализация тестового драйвера для загружаемого модуля 
 * "КриптоПро KTLS" под Unix
 *
 */


#define USE_STD_MM 
#if defined(__x86_64__) || defined(__AMD64__) || defined(__powerpc64__) || defined(__aarch64__)
#    define DRV_LONG long
#elif defined(__i386__) || defined(__mips__) || defined (__arm__)
#    define DRV_LONG int
#else
#    error UNKNOWN PLATFORM
#endif
#include <linux/version.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,18)
#    error Kernels below 2.4.18 are not supported
#endif
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uio.h>
#include <linux/socket.h>
#include <linux/net.h>
#include <linux/delay.h>
#include <linux/file.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
    # include <linux/init.h>
    # ifndef module_param
    #  include <linux/moduleparam.h>
    # endif
#else
    #ifndef __user
    # define __user
    #endif
    # include <linux/devfs_fs_kernel.h> // devfs_register_chrdev
    # include <linux/slab.h>
    # if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
    #   define MODVERSIONS
    #   include <linux/modversions.h>
    # endif
    # define MODULE_VERSION "1.0.0"
    # include <linux/kernel.h>
    # include <linux/major.h>
    # include <linux/errno.h>
    EXPORT_NO_SYMBOLS;
#endif	/* Linux-2.5 */

#include <asm/atomic.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0)
#include <linux/uaccess.h>
#else  /* ломается сборка для 2.6.9 */
#include <asm/uaccess.h>
#endif	/* Linux-4.12 */

#include <linux/byteorder/generic.h>
#include <linux/stddef.h>
#include <linux/nls.h>
#include <linux/vmalloc.h>
#include "drtktls_srv_io.h"
#include "reader/tchar.h"

MODULE_LICENSE("FreeBSD");
MODULE_AUTHOR("Alexey A. Godin CryptoPro Ltd. <dedal@cryptopro.ru>");
MODULE_DESCRIPTION("drtktls_srv for drvssp.o");

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
    // MODULE_INFO(vermagic, VERMAGIC_STRING);
#endif

#define MIN_DEBUG_LEVEL 0
#define MAX_DEBUG_LEVEL 9
		    
#define MAXCONTEXTS 100 /* Fill in something more reasonable if you wish */
PDRTKTLS_DRV pDrv=NULL;

int CPCAPI trans(unsigned char *data, size_t len, int dir, void *arg)
{
  int error;
  char **buf=(char**)arg;

  if(dir==FROM_KERNEL)
	error=copy_to_user(*buf, data, len);
  else
	error=copy_from_user(data, *buf, len);
  if(!error) *buf+=len;

  return error;
}

static PDRTKTLS get_drtktls(struct file *file)
{
  PDRTKTLS pdrtktls=(PDRTKTLS)file->private_data;
  if (!pdrtktls)
	printk(KERN_ALERT MODNAME ": %s(): !pdrtktls, file=0x%lx\n", __func__,
	       (unsigned long)file);
  return pdrtktls;
}

typedef INT_PTR intptr_t; // Для SUP_STOP, не ясно :) TODO: разобраться бы

atomic_t dtkt_mm_cnt = ATOMIC_INIT(0);
atomic_t dtkt_mm_totcnt = ATOMIC_INIT(0);
atomic_t dtkt_mm_totmem = ATOMIC_INIT(0);
atomic_t dtkt_mm_afail = ATOMIC_INIT(0);
atomic_t dtkt_mm_ffail = ATOMIC_INIT(0);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
    static __inline__ int atomic_dec_return(atomic_t *v)
    {
	    // Для целей контроля важна собственно атомарность 
	    // увеличения и уменьшения.
	atomic_dec(v);
	    // Инвариант: 0 <= atomic_read(&dtkt_mm_cnt),
	    // должен выполнятся в любой момент времени.
	return atomic_read(v);
    }
#endif
typedef union
{
    struct
    {
	DWORD magic;
	DWORD size;
    } s;
    unsigned char x[16];
} memblk;

#define MEM_MAGIC 0xDEADC0DE
#define BAD_MAGIC 0xBAD0BAD1
/*!
 * \ingroup MemoryManager
 * \brief Выделение памяти c обнулением буфера
 */
static DWORD CPCAPI
stdAllocMemory(LPCPC_MEMORY_ARENA pArena, SIZE_T dwSize, DWORD dwMemPoolId,
		   DWORD dwThreadId, LPVOID *pRes)
{
  
  memblk *ptr;

  UNUSED(dwThreadId);
  UNUSED(dwMemPoolId);

  ptr = (memblk *)vmalloc(dwSize+16); /* DMA мы не занимаемся --> kmalloc() нам не нужен */
  if(!ptr){
	atomic_inc(&dtkt_mm_afail);
	printk(KERN_ALERT MODNAME ": %s: %d: Too big arg for vmalloc() - %x\n",
	       __func__, __LINE__, (unsigned)dwSize);
	return NTE_NO_MEMORY;
  } 
  atomic_inc(&dtkt_mm_cnt);
  atomic_inc(&dtkt_mm_totcnt);
  atomic_add((int)dwSize, &dtkt_mm_totmem);
  memset (ptr, 0, dwSize+16);
  ptr->s.magic=MEM_MAGIC;
  ptr->s.size=dwSize;

  *pRes = ptr+1;

  return S_OK;
}
/*!
 * \ingroup MemoryManager
 * \brief Освобождение памяти
 */
static DWORD CPCAPI
stdFreeMemory(LPCPC_MEMORY_ARENA pArena, VOID *pMem, DWORD dwMemPoolId)
{
  memblk * ptr=(memblk *)pMem - 1;
  UNUSED(dwMemPoolId);
  if(!pMem || ptr->s.magic != MEM_MAGIC){
	if (ptr->s.magic == BAD_MAGIC)
	    printk(KERN_ALERT MODNAME ": double free:%p\n",pMem);
	atomic_inc(&dtkt_mm_ffail);
	printk(KERN_ALERT MODNAME ": %s: %d: Bad args\n", __func__, __LINE__);
	return NTE_FAIL;
  }
  if(0 > atomic_dec_return(&dtkt_mm_cnt)) {
        printk(KERN_ERR MODNAME ":%s:%d: Error: Assert failed: "
			"0 > dtkt_mm_cnt\n",
			__func__, __LINE__);
	SUP_STOP;
  }
  atomic_sub((int)ptr->s.size,&dtkt_mm_totmem);
  ptr->s.magic = BAD_MAGIC;
  vfree (ptr);

  return 0;
}

static void CPCAPI
stdValidateMemory(LPCPC_MEMORY_ARENA pArena)
{
}

static void CPCAPI
stdStatMemory (LPCPC_MEMORY_ARENA pArena, LPCPC_MEMORY_STATS pStats,
		   DWORD dwMemPoolId)
{
	UNUSED (pArena);
	UNUSED (pStats);
	UNUSED (dwMemPoolId);
}

static void CPCAPI
stdDoneMemory(LPCPC_MEMORY_ARENA pArena)
{
}

DWORD CPCAPI stdInitMemory(LPCPC_MEMORY_ARENA *pArena, LONG *PoolSizes, DWORD nPools)
{
  static CPC_MEMORY_ARENA Config;
  memset (&Config, 0, sizeof (Config));
  Config.pValidateMemory = stdValidateMemory;
  Config.pDoneMemory = stdDoneMemory;
  Config.pAllocMemory = stdAllocMemory;
  Config.pFreeMemory = stdFreeMemory;
  Config.pStatMemory = stdStatMemory;
  *pArena = &Config;
  return S_OK;
}
#define NATT 10
ssize_t emulate_recv(int fd,
		void *buffer,   // buffer in kernel space
		size_t length,
		int flags)
{
	struct socket *sock;
	struct kvec kiov;
	struct msghdr msg;
	int ret = -1;
	int natt = 0;

	sock = sockfd_lookup(fd, &ret);
	if (!sock)
	    goto err;

	do
	{
	    memset(&msg,0,sizeof(msg));
	    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		kiov.iov_base = buffer;
		kiov.iov_len = length;
		ret = kernel_recvmsg(sock, &msg, &kiov, 1, length, flags);
	    #else
		#error "Don't implement for version early 2.5.0"
	    #endif
	    if (ret != -EINTR && ret != -EAGAIN)
		break;
 	    if ( ++natt == NATT )		
	    {
		printk(KERN_ERR "drtktls_srv:recv:fail_retry\n");
		break;
	    }
	    msleep(1<<natt);	
	} 
	while (1);    

	fput(sock->file);
err:
	return ret;
}


ssize_t emulate_send(int fd,
		const void *buffer,   // buffer in kernel space
		size_t length,
		int flags)
{
	struct socket *sock;
	struct kvec kiov;
	struct msghdr msg;
	int ret = -1;
	int natt = 0;

	sock = sockfd_lookup(fd, &ret);
	if (!sock)
	{
	    ret=-EBADF;
	    goto err;
	}

	do
	{
	    memset(&msg,0,sizeof(msg));
	    #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,5,0)
		kiov.iov_base = (void *)buffer; // const - на доверии
		kiov.iov_len = length;
		ret = kernel_sendmsg(sock, &msg, &kiov, 1, length);
	    #else
		#error "Don't implement for version early 2.5.0"
	    #endif
	    if (ret != -EINTR && ret != -EAGAIN)
		break;
	    if ( ++natt == NATT )		
	    {
		printk(KERN_ERR "drtktls_srv:send:fail_retry\n");
		break;
	    }
	    msleep(1<<natt);	
	} 
	while(1);
	fput(sock->file);
err:
	return ret;
}

static DWORD CPCAPI 
dts_lnx_rwlock_init(LPCPC_RWLOCK pSection, DWORD cbSection, LPVOID lpArg)
{
  char cp_c_version_static_assert[(sizeof(struct semaphore) <= sizeof(*pSection))?1:-1];
  UNUSED(cp_c_version_static_assert);
  UNUSED(lpArg);
  if (cbSection<sizeof(struct semaphore))
    return NTE_BAD_DATA;
  sema_init((struct semaphore *)pSection, 1);

  return S_OK;
}

static VOID CPCAPI
dts_lnx_rwlock_destroy(LPCPC_RWLOCK pSection)
{
  UNUSED(pSection);
}

static VOID CPCAPI
dts_lnx_rwlock_wrlock(LPCPC_RWLOCK pSection)
{
  down((struct semaphore *)pSection);
}

static VOID CPCAPI
dts_lnx_rwlock_rdlock(LPCPC_RWLOCK pSection)
{
  down((struct semaphore *)pSection);
}

static VOID CPCAPI
dts_lnx_rwlock_unlock(LPCPC_RWLOCK pSection)
{
  up((struct semaphore *)pSection);
}
void CPCAPI CPCSetLocks(LPCPC_CONFIG pConfig)
{
    pConfig->lockFuncs.rwlock_init = dts_lnx_rwlock_init;
    pConfig->lockFuncs.rwlock_destroy = dts_lnx_rwlock_destroy;
    pConfig->lockFuncs.rwlock_rdlock = dts_lnx_rwlock_rdlock;
    pConfig->lockFuncs.rwlock_wrlock = dts_lnx_rwlock_wrlock;
    pConfig->lockFuncs.rwlock_unlock = dts_lnx_rwlock_unlock;
}

static int
drtktls_open(struct inode *inode, struct file *file)
{
  PDRTKTLS pdrtktls;
  unsigned cbSize=0;
  pdrtktls=(PDRTKTLS)vmalloc(sizeof(DRTKTLS));
  if (!pdrtktls)
    return -ENOMEM;
  pdrtktls->size = sizeof(*pdrtktls);
  pdrtktls->state |= DRTKTLS_ATTACH;
  file->private_data=pdrtktls;
  if (kinit_ctx(pDrv,NULL,&cbSize))
    return -EPERM;
  pdrtktls->pCtx=vmalloc(cbSize);  
  if (!pdrtktls->pCtx)
  {
    vfree(pdrtktls);
    file->private_data = NULL;
    return -ENOMEM;
  }
  if (kinit_ctx(pDrv,pdrtktls->pCtx,&cbSize))
  {
    vfree(pdrtktls->pCtx);
    vfree(pdrtktls);
    file->private_data = NULL;
    return -EFAULT;
  }
  
  //  MOD_INC_USE_COUNT;
  
  return 0;
}

static int
drtktls_release( struct inode *inode, struct file *file)
{
  PDRTKTLS pdrtktls;
  int ret=0;

  //  MOD_DEC_USE_COUNT;

  if(!(pdrtktls=get_drtktls(file))) return -ENXIO;
  
  if (pdrtktls->pCtx)
  {
      if(kdone_ctx(pdrtktls->pCtx)) 
	    ret=-EFAULT;
      vfree(pdrtktls->pCtx);  
  }
  vfree(pdrtktls);  
  file->private_data=NULL;
  return ret;
}

static DRV_LONG
drtktls_read(struct file *file, char *buf, size_t length, loff_t *off)
{
  //PDRTKTLS pdrtktls;
  // int resid=length;
  // char *tmp=buf;
  
  return -ENXIO;
}

static DRV_LONG
drtktls_write(struct file *file, const char *buf, size_t length, loff_t *off)
{
  return -ENXIO;
}

/* dim XXX раскопок я не затевал, поэтому точно не знаю, когда тип стал long. В 3.3.1 уже long */
#if LINUX_VERSION_CODE < KERNEL_VERSION(3,3,0)
#   define IOCTL_TYPE int
#else
#   define IOCTL_TYPE long
#endif	/* < 3.3.x */
#define COPY_CONTENTS 1
#define NO_COPY_CONTENTS 0

static int is32ioctl(unsigned int cmd)
{
#if SIZEOF_VOID_P == 8
    switch(cmd)
    {
	case DRTKTLS_RNG32:
	case DRTKTLS_EXPORT_SA32:
	case DRTKTLS_IMPORT_SA32:
	case DRTKTLS_EXPORT_CONTEXT32:
	case DRTKTLS_IMPORT_CONTEXT32:
	case DRTKTLS_PROCESS_DATA32:
	    return 1;
    }
#endif
    return 0;
}
#if SIZEOF_VOID_P == 8
#define FromUserPtr(ptr,is32) ((is32)?compat_ptr(ptr):((void __user *)(ptr)))
#define ToUserPtr(ptr,is32) ((is32)?ptr_to_compat(ptr):((void __user *)(ptr)))
#define CASE(ioc) case ioc: case ioc##32
#else
#define ToUserPtr(ptr,is32) ((void __user *)(ptr))
#define FromUserPtr(ptr,is32) ((void __user *)(ptr))
#define CASE(ioc) case ioc
#endif

static unsigned long blob_from_user(PDRTKTLS_EXCHANGE pDst,unsigned long arg, int copy_contents, int is32)
{
    unsigned long ret;
    void * ptr;
#if SIZEOF_VOID_P == 8 
    COMPAT_DRTKTLS_EXCHANGE CDex;
    if (is32)
    {
	ret = copy_from_user(&CDex,FromUserPtr(arg,is32),sizeof(COMPAT_DRTKTLS_EXCHANGE));
	pDst->cbBuffer=CDex.cbBuffer;
	pDst->pvBuffer=compat_ptr(CDex.cpvBuffer);
    }
    else
    {
	ret = copy_from_user(pDst,FromUserPtr(arg,is32),sizeof(DRTKTLS_EXCHANGE));
    }
#else	
    ret = copy_from_user(pDst,FromUserPtr(arg,is32),sizeof(DRTKTLS_EXCHANGE));
#endif    
    if (ret)
	return EFAULT;
    if (copy_contents == NO_COPY_CONTENTS)
	return 0;
    ptr=vmalloc(pDst->cbBuffer);	
    if (!ptr)
	return ENOMEM;
    ret = copy_from_user(ptr,pDst->pvBuffer,pDst->cbBuffer);	
    if (ret)
    {
	vfree(ptr);
	return EFAULT;
    }
    pDst->pvBuffer=ptr;
    return 0;
}

static unsigned long blob_to_user(unsigned long arg,const DRTKTLS_EXCHANGE * pSrc,int copy_contents,int is32)
{
    DRTKTLS_EXCHANGE Dst;
    unsigned long ret;
    unsigned old_cbBuffer;
#if SIZEOF_VOID_P == 8    
    COMPAT_DRTKTLS_EXCHANGE CDex;
    if (is32)
    {
	ret = copy_from_user(&CDex,compat_ptr(arg),sizeof(CDex));
	Dst.cbBuffer=CDex.cbBuffer;
    }
    else	
	ret = copy_from_user(&Dst,(void __user *)arg,sizeof(Dst));
#else    
    ret = blob_from_user(&Dst,arg,NO_COPY_CONTENTS,is32);
#endif    
    if (ret)
	return EFAULT;
    old_cbBuffer = Dst.cbBuffer;	
    Dst.cbBuffer = pSrc->cbBuffer;
#if SIZEOF_VOID_P == 8
    if (is32)
    {
	CDex.cbBuffer=Dst.cbBuffer;
	ret=copy_to_user(compat_ptr(arg),&CDex,sizeof(CDex));    
	if (ret)
	    return EFAULT;
	Dst.pvBuffer=compat_ptr(CDex.cpvBuffer);
    }
    else
	ret=copy_to_user((void __user *)arg,&Dst,sizeof(Dst));    
#else
    ret=copy_to_user((void __user *)arg,&Dst,sizeof(Dst));    
#endif    
    if (ret)
	return EFAULT;
    if (copy_contents == COPY_CONTENTS)
    {
	if (Dst.pvBuffer == 0)
	    return EFAULT;

	if (old_cbBuffer < pSrc->cbBuffer)
	    return ENOMEM;

	ret=copy_to_user(Dst.pvBuffer,pSrc->pvBuffer,pSrc->cbBuffer);    
    	if (ret)
	    return EFAULT;
    }
    return 0;
}

static unsigned long DRTKP_from_user(PDRTKTLS_PROCESS pPr, unsigned long arg, int is32)
{
    void * pvExtra = NULL;
    unsigned long long ret;
#if SIZEOF_VOID_P == 8
    if (is32)
    {
	COMPAT_DRTKTLS_PROCESS CPr;
	ret = copy_from_user(&CPr,compat_ptr(arg),sizeof(CPr));
	if (ret)
	    return EFAULT;
	pPr->Socket=CPr.Socket;
	pPr->Extra.cbBuffer=CPr.Extra.cbBuffer;
	pPr->Extra.pvBuffer=compat_ptr(CPr.Extra.cpvBuffer);
    }
    else
	ret = copy_from_user(pPr,(void __user *)arg,sizeof(DRTKTLS_PROCESS));
#else
    ret = copy_from_user(pPr,(void __user *)arg,sizeof(DRTKTLS_PROCESS));
#endif
    if (ret)
	return EFAULT;
    pvExtra = vmalloc(pPr->Extra.cbBuffer);	
    if (!pvExtra)
	return ENOMEM;
    ret = copy_from_user(pvExtra,pPr->Extra.pvBuffer,pPr->Extra.cbBuffer);
    if (ret)
    {
	vfree(pvExtra);
	return EFAULT;
    }
    pPr->Extra.pvBuffer=pvExtra;
    return 0;
}

static IOCTL_TYPE
drtktls_ioctl(
	#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) // TODO: 2.6.11 
	    struct inode *inop,
	#endif
	struct file *file, unsigned int cmd, unsigned long arg)
{
  PDRTKTLS pdrtktls;
  DRTKTLS_EXCHANGE Dex;
  int         error = 0;
  int is32;

  Dex.cbBuffer=0;
  Dex.pvBuffer=0;
  is32 = is32ioctl(cmd);

  printk(KERN_ERR "CMD=%d,is32=%d\n",cmd,is32);
  if(!(pdrtktls=get_drtktls(file))) return -ENXIO;
  if (cmd == DRTKTLS_PROCESS_DATA
#if SIZEOF_VOID_P == 8
    || cmd == DRTKTLS_PROCESS_DATA32
#endif    
  )
  {
	DRTKTLS_PROCESS Pr;
	unsigned long ret;
	ret = DRTKP_from_user(&Pr,arg,is32);
	if (ret)
	{
	    error=-ret;
	    goto end;
	}
	switch(kwritefile(pdrtktls->pCtx,Pr.Socket,Pr.Extra.pvBuffer,Pr.Extra.cbBuffer))
	{
	    case 0: error=0;
		break;
#if 0		
	    case SEC_I_RENEGOTIATE:	
		error=-EAGAIN;
		break;
#endif		
	    default:	
		error=-ENXIO;
		vfree(Pr.Extra.pvBuffer);
		goto end;
	}	
	vfree(Pr.Extra.pvBuffer);
	goto end;	
  }
  else if (cmd == DRTKTLS_CREATE_SA)
  {
	if (kcreate_SA(pDrv))
	    error=-ENXIO;
	goto end;
  }
  else if (cmd == DRTKTLS_RNG || cmd == DRTKTLS_IMPORT_SA || cmd == DRTKTLS_IMPORT_CONTEXT
#if SIZEOF_VOID_P == 8
|| cmd == DRTKTLS_RNG32 || cmd == DRTKTLS_IMPORT_SA32 || cmd == DRTKTLS_IMPORT_CONTEXT32
#endif
  )

  {
	unsigned long ret;
	SECURITY_STATUS scRet=0;
	ret = blob_from_user(&Dex,arg,COPY_CONTENTS,is32);
	printk("BFE__: cbBuffer=%d, pvBuffer=%p\n",Dex.cbBuffer,Dex.pvBuffer);
	if (ret)
	{
	    error=-ret;
	    goto end;
	}
	switch (cmd)
	{
	    CASE(DRTKTLS_RNG):
		scRet=kset_rng(pDrv,Dex.pvBuffer,Dex.cbBuffer); 
		break;
	    CASE(DRTKTLS_IMPORT_SA):
		scRet=kimport_SA(pdrtktls->pCtx,Dex.pvBuffer,Dex.cbBuffer); 
		break;
	    CASE(DRTKTLS_IMPORT_CONTEXT):	
		scRet=kimport_keys(pdrtktls->pCtx,Dex.pvBuffer,Dex.cbBuffer); 
		break;
	}
	if (scRet)
	    error = -EFAULT;
	vfree(Dex.pvBuffer);    
  }
  else if ( cmd ==  DRTKTLS_EXPORT_SA || cmd == DRTKTLS_EXPORT_CONTEXT
#if SIZEOF_VOID_P == 8  
|| cmd ==  DRTKTLS_EXPORT_SA32 || cmd == DRTKTLS_EXPORT_CONTEXT32
#endif
  )
  {
	unsigned long ret;
	SECURITY_STATUS scRet=0;
	unsigned cbData;
	ret = blob_from_user(&Dex,arg,NO_COPY_CONTENTS,is32);
	if (ret)
	{
	    error=-ret;
	    goto end;
	}
	cbData=Dex.cbBuffer;
	switch (cmd)
	{
	    CASE(DRTKTLS_EXPORT_SA):
		scRet=kexport_SA(pDrv,NULL,&cbData);
		break;
	    CASE(DRTKTLS_EXPORT_CONTEXT):
		scRet=kexport_keys(pdrtktls->pCtx,NULL,&cbData);
		break;
	}

	if (scRet)
	{
	    error=-EFAULT;
	    goto end;
	}
	if (Dex.pvBuffer == 0)
	{
	    Dex.cbBuffer=cbData;
	    ret=blob_to_user(arg,&Dex,NO_COPY_CONTENTS,is32);
	    error=-ret;
	    goto end;
	}
	if (Dex.cbBuffer < cbData)
	{
	    Dex.cbBuffer=cbData;
	    ret=blob_to_user(arg,&Dex,NO_COPY_CONTENTS,is32);
	    if (ret)
		error=-ret;
	    else
		error=-ENOMEM;
	    goto end;    
	}
	Dex.pvBuffer=vmalloc(cbData);
	if (Dex.pvBuffer == 0)
	{
	    error=-ENOMEM;
	    goto end;
	}

	switch (cmd)
	{
	    CASE(DRTKTLS_EXPORT_SA):
		scRet=kexport_SA(pDrv,Dex.pvBuffer,&cbData);
		break;
	    CASE(DRTKTLS_EXPORT_CONTEXT):
		scRet=kexport_keys(pdrtktls->pCtx,Dex.pvBuffer,&cbData);
		break;
	}

	if (scRet)
	{
	    error=-ENXIO;
	    vfree(Dex.pvBuffer);
	    goto end;
	}
	Dex.cbBuffer=cbData;
	ret = blob_to_user(arg,&Dex,COPY_CONTENTS,is32);
	vfree(Dex.pvBuffer);
	error=-ret;
  }
  else
  {
	error=-ENOTTY;
  }
end:  
  return error;
}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) // TODO: 2.6.11
static long compat_drtktls_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    return drtktls_ioctl(
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) // TODO: 2.6.11 
    0,
#endif    
    file,cmd,arg);
}
#endif


static struct file_operations fops = {
  .owner =	THIS_MODULE,
  .open =	drtktls_open,
  .release =	drtktls_release,
  .read =	drtktls_read,
  .write =	drtktls_write,
  #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,36) // TODO: 2.6.11
    .ioctl = drtktls_ioctl,
  #else
    .unlocked_ioctl = drtktls_ioctl,
  #endif
  #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) // TODO: 2.6.11
    .compat_ioctl = compat_drtktls_ioctl
  #endif  
};

static int Major;
#ifdef CONFIG_DEVFS_FS
    static devfs_handle_t handles[DRTKTLS_MAX_CHANNEL];
#endif

static int __init
drtktls_init(void)
{
  unsigned cbSize=0;
  #ifdef CONFIG_DEVFS_FS
    char nodename[sizeof(MODNAME)+2]; /* /dev/drtktls0 */
  #endif

  #ifdef CONFIG_DEVFS_FS
    for(i=0; i<DRTKTLS_MAX_CHANNEL; i++){
      sprintf(nodename, "%s%1d", MODNAME, i);
      handles[i]=devfs_register(NULL, MODNAME, DEVFS_FL_AUTO_DEVNUM, 0, 0,
			      S_IFCHR|S_IRWXUGO, &fops, NULL);
      if(!Major&&handles[i]) devfs_get_maj_min(handles[i], &Major, NULL);
      printk(KERN_INFO "%s() %d %lx", __func__, i, (unsigned long)handles[i]);
    }
  #else
    Major=register_chrdev(0, MODNAME, &fops);
    if(Major<0) {
      printk(KERN_ERR"%s() Failed to register: error %d.\n", __func__, Major);
      return Major;
    }
  #endif /* CONFIG_DEVFS_FS */
  printk(KERN_INFO"%s() Registered at Major %d.\n", __func__, Major);
  if (kinit_gost(NULL,&cbSize))
    return -EPERM;
  pDrv=vmalloc(cbSize);  
  if (!pDrv)
  {
    return -ENOMEM;
  }
  printk(KERN_INFO"malloced %d bytes at %p\n",cbSize,pDrv);
  if (kinit_gost(pDrv,&cbSize))
  {
    vfree(pDrv);
    return -EFAULT;
  }
  printk(KERN_INFO"pdrv: cbSize:%d pDrv:%p\n",cbSize,pDrv);
    
  return 0;
}

static __exit void
drtktls_exit(void)
{
  #ifdef CONFIG_DEVFS_FS
    int i;
    for(i=0;i<DRTKTLS_MAX_CHANNEL;i++) if(handles[i]) devfs_unregister(handles[i]);
    printk(KERN_INFO"%s() Unregistered.\n", __func__);
  #else
    unregister_chrdev( Major, MODNAME);
    printk(KERN_INFO"%s() Unregistered Major %d.\n", __func__, Major);
  #endif
  kdone_gost(pDrv);
  vfree(pDrv);
  printk(KERN_ALERT MODNAME ": dtkt_mm stats: cnt=%d totcnt=%d totmem=%d afail=%d ffail=%d\n",
		atomic_read(&dtkt_mm_cnt), 
		atomic_read(&dtkt_mm_totcnt), 
		atomic_read(&dtkt_mm_totmem), 
		atomic_read(&dtkt_mm_afail),
		atomic_read(&dtkt_mm_ffail));
}

module_init(drtktls_init);
module_exit(drtktls_exit);

struct drtktls_control_data_t {
  char s[10];
};

int debug_level = MIN_DEBUG_LEVEL;
