/*
 * Copyright (c) 2005 Novell, Inc.
 * All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * 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, contact Novell, Inc.
 *
 * To contact Novell about this file by physical or electronic mail,
 * you may find current contact information at www.novell.com 
 *
 * Author		: Rohit Kumar
 * Email ID	: rokumar@novell.com
 * Date		: 25th August 2005
 */


#include <rfb/rfb.h>
#include "rfbtightproto.h"
#include "handlefiletransferrequest.h"

/*
 * Get my data!
 *
 * This gets the extension specific data from the client structure. If
 * the data is not found, the client connection is closed, a complaint
 * is logged, and NULL is returned.
 */

extern rfbProtocolExtension tightVncFileTransferExtension;

rfbTightClientPtr 
rfbGetTightClientData(rfbClientPtr cl)
{
    rfbTightClientPtr rtcp = (rfbTightClientPtr) 
				rfbGetExtensionClientData(cl, 
				&tightVncFileTransferExtension);
    if(rtcp == NULL) {
        rfbLog("Extension client data is null, closing the connection !\n");
	rfbCloseClient(cl);
    }

    return rtcp;
}

/*
 * Send the authentication challenge.
 */

static void
rfbVncAuthSendChallenge(rfbClientPtr cl)
{
	
    rfbLog("tightvnc-filetransfer/rfbVncAuthSendChallenge\n");
    /* 4 byte header is alreay sent. Which is rfbSecTypeVncAuth (same as rfbVncAuth). Just send the challenge. */
    rfbRandomBytes(cl->authChallenge);
    if (rfbWriteExact(cl, (char *)cl->authChallenge, CHALLENGESIZE) < 0) {
        rfbLogPerror("rfbAuthNewClient: write");
        rfbCloseClient(cl);
        return;
    }
    
    /* Dispatch client input to rfbVncAuthProcessResponse. */
   /* This methos is defined in auth.c file */
    rfbAuthProcessClientMessage(cl);

}

/*
 * LibVNCServer has a bug WRT Tight SecurityType and RFB 3.8
 * It should send auth result even for rfbAuthNone.
 * See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=517422
 * For testing set USE_SECTYPE_TIGHT_FOR_RFB_3_8 when compiling
 * or set it here.
 */
#define SECTYPE_TIGHT_FOR_RFB_3_8 \
	if (cl->protocolMajorVersion==3 && cl->protocolMinorVersion > 7) { \
		uint32_t authResult; \
		rfbLog("rfbProcessClientSecurityType: returning securityResult for client rfb version >= 3.8\n"); \
		authResult = Swap32IfLE(rfbVncAuthOK); \
		if (rfbWriteExact(cl, (char *)&authResult, 4) < 0) { \
			rfbLogPerror("rfbAuthProcessClientMessage: write"); \
			rfbCloseClient(cl); \
			return; \
		} \
	}
/*
 * Read client's preferred authentication type (protocol 3.7t).
 */

void
rfbProcessClientAuthType(rfbClientPtr cl)
{
    uint32_t auth_type;
    int n, i;
    rfbTightClientPtr rtcp = rfbGetTightClientData(cl);

    rfbLog("tightvnc-filetransfer/rfbProcessClientAuthType\n");

    if(rtcp == NULL)
	return;

    /* Read authentication type selected by the client. */
    n = rfbReadExact(cl, (char *)&auth_type, sizeof(auth_type));
    if (n <= 0) {
	if (n == 0)
	    rfbLog("rfbProcessClientAuthType: client gone\n");
	else
	    rfbLogPerror("rfbProcessClientAuthType: read");
	rfbCloseClient(cl);
	return;
    }
    auth_type = Swap32IfLE(auth_type);

    /* Make sure it was present in the list sent by the server. */
    for (i = 0; i < rtcp->nAuthCaps; i++) {
	if (auth_type == rtcp->authCaps[i])
	    break;
    }
    if (i >= rtcp->nAuthCaps) {
	rfbLog("rfbProcessClientAuthType: "
	       "wrong authentication type requested\n");
	rfbCloseClient(cl);
	return;
    }

    switch (auth_type) {
    case rfbAuthNone:
	/* Dispatch client input to rfbProcessClientInitMessage. */
#ifdef USE_SECTYPE_TIGHT_FOR_RFB_3_8
	SECTYPE_TIGHT_FOR_RFB_3_8
#endif
	cl->state = RFB_INITIALISATION;
	break;
    case rfbAuthVNC:
	rfbVncAuthSendChallenge(cl);
	break;
    default:
	rfbLog("rfbProcessClientAuthType: unknown authentication scheme\n");
	rfbCloseClient(cl);
    }
}


/*
 * Read tunneling type requested by the client (protocol 3.7t).
 * NOTE: Currently, we don't support tunneling, and this function
 *       can never be called.
 */

void
rfbProcessClientTunnelingType(rfbClientPtr cl)
{
    /* If we were called, then something's really wrong. */
    rfbLog("rfbProcessClientTunnelingType: not implemented\n");
    rfbCloseClient(cl);
    return;
}


/*
 * Send the list of our authentication capabilities to the client
 * (protocol 3.7t).
 */

static void
rfbSendAuthCaps(rfbClientPtr cl)
{
    rfbAuthenticationCapsMsg caps;
    rfbCapabilityInfo caplist[MAX_AUTH_CAPS];
    int count = 0;
    rfbTightClientPtr rtcp = rfbGetTightClientData(cl);

    rfbLog("tightvnc-filetransfer/rfbSendAuthCaps\n");

    if(rtcp == NULL)
	return;

    if (cl->screen->authPasswdData && !cl->reverseConnection) {
	/* chk if this condition is valid or not. */
	    SetCapInfo(&caplist[count], rfbAuthVNC, rfbStandardVendor);
	    rtcp->authCaps[count++] = rfbAuthVNC;
    }

    rtcp->nAuthCaps = count;
    caps.nAuthTypes = Swap32IfLE((uint32_t)count);
    if (rfbWriteExact(cl, (char *)&caps, sz_rfbAuthenticationCapsMsg) < 0) {
	rfbLogPerror("rfbSendAuthCaps: write");
	rfbCloseClient(cl);
	return;
    }

    if (count) {
	if (rfbWriteExact(cl, (char *)&caplist[0],
		       count * sz_rfbCapabilityInfo) < 0) {
	    rfbLogPerror("rfbSendAuthCaps: write");
	    rfbCloseClient(cl);
	    return;
	}
	/* Dispatch client input to rfbProcessClientAuthType. */
	/* Call the function for authentication from here */
	rfbProcessClientAuthType(cl);
    } else {
#ifdef USE_SECTYPE_TIGHT_FOR_RFB_3_8
	SECTYPE_TIGHT_FOR_RFB_3_8
#endif
	/* Dispatch client input to rfbProcessClientInitMessage. */
	cl->state = RFB_INITIALISATION;
    }
}


/*
 * Send the list of our tunneling capabilities (protocol 3.7t).
 */

static void
rfbSendTunnelingCaps(rfbClientPtr cl)
{
    rfbTunnelingCapsMsg caps;
    uint32_t nTypes = 0;		/* we don't support tunneling yet */

    rfbLog("tightvnc-filetransfer/rfbSendTunnelingCaps\n");

    caps.nTunnelTypes = Swap32IfLE(nTypes);
    if (rfbWriteExact(cl, (char *)&caps, sz_rfbTunnelingCapsMsg) < 0) {
	rfbLogPerror("rfbSendTunnelingCaps: write");
	rfbCloseClient(cl);
	return;
    }

    if (nTypes) {
	/* Dispatch client input to rfbProcessClientTunnelingType(). */
	/* The flow should not reach here as tunneling is not implemented. */
	rfbProcessClientTunnelingType(cl);
    } else {
	rfbSendAuthCaps(cl);
    }
}



/*
 * rfbSendInteractionCaps is called after sending the server
 * initialisation message, only if TightVNC protocol extensions were
 * enabled (protocol 3.7t). In this function, we send the lists of
 * supported protocol messages and encodings.
 */

/* Update these constants on changing capability lists below! */
/* Values updated for FTP */ 
#define N_SMSG_CAPS  4
#define N_CMSG_CAPS  6
#define N_ENC_CAPS  12

void
rfbSendInteractionCaps(rfbClientPtr cl)
{
    rfbInteractionCapsMsg intr_caps;
    rfbCapabilityInfo smsg_list[N_SMSG_CAPS];
    rfbCapabilityInfo cmsg_list[N_CMSG_CAPS];
    rfbCapabilityInfo enc_list[N_ENC_CAPS];
    int i, n_enc_caps = N_ENC_CAPS;

    /* Fill in the header structure sent prior to capability lists. */
    intr_caps.nServerMessageTypes = Swap16IfLE(N_SMSG_CAPS);
    intr_caps.nClientMessageTypes = Swap16IfLE(N_CMSG_CAPS);
    intr_caps.nEncodingTypes = Swap16IfLE(N_ENC_CAPS);
    intr_caps.pad = 0;

    rfbLog("tightvnc-filetransfer/rfbSendInteractionCaps\n");

    /* Supported server->client message types. */
    /* For file transfer support: */
    i = 0;
	if((IsFileTransferEnabled() == TRUE) && ( cl->viewOnly == FALSE)) {
	    SetCapInfo(&smsg_list[i++], rfbFileListData,           rfbTightVncVendor);
	    SetCapInfo(&smsg_list[i++], rfbFileDownloadData,       rfbTightVncVendor);
	    SetCapInfo(&smsg_list[i++], rfbFileUploadCancel,       rfbTightVncVendor);
	    SetCapInfo(&smsg_list[i++], rfbFileDownloadFailed,     rfbTightVncVendor);
	    if (i != N_SMSG_CAPS) {
			rfbLog("rfbSendInteractionCaps: assertion failed, i != N_SMSG_CAPS\n");
			rfbCloseClient(cl);
			return;
	    }
	}

    /* Supported client->server message types. */
    /* For file transfer support: */
    i = 0;
	if((IsFileTransferEnabled() == TRUE) && ( cl->viewOnly == FALSE)) {
	    SetCapInfo(&cmsg_list[i++], rfbFileListRequest,        rfbTightVncVendor);
	    SetCapInfo(&cmsg_list[i++], rfbFileDownloadRequest,    rfbTightVncVendor);
	    SetCapInfo(&cmsg_list[i++], rfbFileUploadRequest,      rfbTightVncVendor);
	    SetCapInfo(&cmsg_list[i++], rfbFileUploadData,         rfbTightVncVendor);
	    SetCapInfo(&cmsg_list[i++], rfbFileDownloadCancel,     rfbTightVncVendor);
	    SetCapInfo(&cmsg_list[i++], rfbFileUploadFailed,       rfbTightVncVendor);
	    if (i != N_CMSG_CAPS) {
			rfbLog("rfbSendInteractionCaps: assertion failed, i != N_CMSG_CAPS\n");
			rfbCloseClient(cl);
			return;
	    }		
	}
	
    /* Encoding types. */
    i = 0;
    SetCapInfo(&enc_list[i++],  rfbEncodingCopyRect,       rfbStandardVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingRRE,            rfbStandardVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingCoRRE,          rfbStandardVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingHextile,        rfbStandardVendor);
#ifdef LIBVNCSERVER_HAVE_LIBZ
    SetCapInfo(&enc_list[i++],  rfbEncodingZlib,           rfbTridiaVncVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingTight,          rfbTightVncVendor);
#else
    n_enc_caps -= 2;
#endif
    SetCapInfo(&enc_list[i++],  rfbEncodingCompressLevel0, rfbTightVncVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingQualityLevel0,  rfbTightVncVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingXCursor,        rfbTightVncVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingRichCursor,     rfbTightVncVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingPointerPos,     rfbTightVncVendor);
    SetCapInfo(&enc_list[i++],  rfbEncodingLastRect,       rfbTightVncVendor);
    if (i != n_enc_caps) {
	rfbLog("rfbSendInteractionCaps: assertion failed, i != N_ENC_CAPS\n");
	rfbCloseClient(cl);
	return;
    }

    /* Send header and capability lists */
    if (rfbWriteExact(cl, (char *)&intr_caps,
		   sz_rfbInteractionCapsMsg) < 0 ||
	rfbWriteExact(cl, (char *)&smsg_list[0],
		   sz_rfbCapabilityInfo * N_SMSG_CAPS) < 0 || 
	rfbWriteExact(cl, (char *)&cmsg_list[0],
		   sz_rfbCapabilityInfo * N_CMSG_CAPS) < 0  ||
	rfbWriteExact(cl, (char *)&enc_list[0],
		   sz_rfbCapabilityInfo * N_ENC_CAPS) < 0) {
	rfbLogPerror("rfbSendInteractionCaps: write");
	rfbCloseClient(cl);	
	return;
    }

    /* Dispatch client input to rfbProcessClientNormalMessage(). */
    cl->state = RFB_NORMAL;
}



rfbBool
rfbTightExtensionInit(rfbClientPtr cl, void** data)
{

   rfbSendInteractionCaps(cl);

    return TRUE;
}

static rfbBool
handleMessage(rfbClientPtr cl,
	const char* messageName,
	void (*handler)(rfbClientPtr cl, rfbTightClientPtr data))
{
	rfbTightClientPtr data;

	rfbLog("tightvnc-filetransfer: %s message received\n", messageName);

	if((IsFileTransferEnabled() == FALSE) || ( cl->viewOnly == TRUE)) {
		rfbCloseClient(cl);
		return FALSE;	
	}

	data = rfbGetTightClientData(cl);
	if(data == NULL)
		return FALSE;

	handler(cl, data);
	return TRUE;
}

rfbBool
rfbTightExtensionMsgHandler(struct _rfbClientRec* cl, void* data,
				const rfbClientToServerMsg* msg)
{
    switch (msg->type) {
		
	case rfbFileListRequest:

	return handleMessage(cl, "rfbFileListRequest", HandleFileListRequest);

	case rfbFileDownloadRequest:

	return handleMessage(cl, "rfbFileDownloadRequest", HandleFileDownloadRequest);

	case rfbFileUploadRequest:	
	
	return handleMessage(cl, "rfbFileUploadRequest", HandleFileUploadRequest);

	case rfbFileUploadData:

	return handleMessage(cl, "rfbFileUploadDataRequest", HandleFileUploadDataRequest);

	case rfbFileDownloadCancel:

	return handleMessage(cl, "rfbFileDownloadCancelRequest", HandleFileDownloadCancelRequest);

	case rfbFileUploadFailed:

	return handleMessage(cl, "rfbFileUploadFailedRequest", HandleFileUploadFailedRequest);

	case rfbFileCreateDirRequest:

	return handleMessage(cl, "rfbFileCreateDirRequest", HandleFileCreateDirRequest);
	
    default:

	rfbLog("rfbProcessClientNormalMessage: unknown message type %d\n",
		msg->type);

	/*

	We shouldn't close the connection here for unhandled msg, 
	it should be left to libvncserver.
	rfbLog(" ... closing connection\n");
	rfbCloseClient(cl);

	*/
	
	return FALSE;

    }
}


void
rfbTightExtensionClientClose(rfbClientPtr cl, void* data) {

	if(data != NULL)
		free(data);

}

void
rfbTightUsage(void) {
    fprintf(stderr, "\nlibvncserver-tight-extension options:\n");
    fprintf(stderr, "-disablefiletransfer   disable file transfer\n");
    fprintf(stderr, "-ftproot string        set ftp root\n");
    fprintf(stderr,"\n");
}

int
rfbTightProcessArg(int argc, char *argv[]) {

    rfbLog("tightvnc-filetransfer/rfbTightProcessArg\n");

    InitFileTransfer();

    if(argc<1)
	return 0;

    if (strcmp(argv[0], "-ftproot") == 0) { /* -ftproot string */
	if (2 > argc) {
	    return 0;
	}
	rfbLog("ftproot is set to <%s>\n", argv[1]);
	if(SetFtpRoot(argv[1]) == FALSE) {
	    rfbLog("ERROR:: Path specified for ftproot in invalid\n");
	    return 0;
	}
	return 2;
    } else if (strcmp(argv[0], "-disablefiletransfer") == 0) {
	EnableFileTransfer(FALSE);
	return 1;
    }
    return 0;
}

/*
  * This method should be registered to libvncserver to handle rfbSecTypeTight  security type.
  */
void
rfbHandleSecTypeTight(rfbClientPtr cl) {

    rfbTightClientPtr rtcp = (rfbTightClientPtr) malloc(sizeof(rfbTightClientRec));

    rfbLog("tightvnc-filetransfer/rfbHandleSecTypeTight\n");

    if(rtcp == NULL) {
        /* Error condition close socket */
        rfbLog("Memory error has occured while handling "
		"Tight security type... closing connection.\n");
	 rfbCloseClient(cl);
	 return;
    }

    memset(rtcp, 0, sizeof(rfbTightClientRec));
    rtcp->rcft.rcfd.downloadFD = -1;
    rtcp->rcft.rcfu.uploadFD = -1;
    rfbEnableExtension(cl, &tightVncFileTransferExtension, rtcp);

    rfbSendTunnelingCaps(cl);

}

rfbProtocolExtension tightVncFileTransferExtension = {
	NULL,
	rfbTightExtensionInit,
	NULL,
	NULL,
	rfbTightExtensionMsgHandler,
	rfbTightExtensionClientClose,
	rfbTightUsage,
	rfbTightProcessArg,
	NULL
};

static rfbSecurityHandler tightVncSecurityHandler = {
	rfbSecTypeTight,
	rfbHandleSecTypeTight,
	NULL
};

void rfbRegisterTightVNCFileTransferExtension() {
	rfbRegisterProtocolExtension(&tightVncFileTransferExtension);
	rfbRegisterSecurityHandler(&tightVncSecurityHandler);
}

void 
rfbUnregisterTightVNCFileTransferExtension() {
	rfbUnregisterProtocolExtension(&tightVncFileTransferExtension);
	rfbUnregisterSecurityHandler(&tightVncSecurityHandler);
}

