//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// This program 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 3 of the License, or
// (at your option) any later version.
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/un.h>
#include <termios.h>
#include <unistd.h>

#ifdef USE_BLUEZ
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#endif

#ifdef USE_BT_FBSD
#include <bluetooth.h>
#include <sdp.h>
#include <err.h>
#endif

#include "pr_btspp.h"
#include "common.h"
#include "utils.h"
#include "sys_util.h"
#include "conf.h"
#include "lib_wrapper.h"
#include "dispatcher.h"
#include "security.h"

extern char tmp[MAXMAXLEN];
extern boolean_t stillRun;

typedef struct _BtsppConnection_ {
    int           fileDescriptor;
    int           serverFileDescriptor;
    #ifdef USE_BLUEZ
    sdp_session_t *session;
    sdp_record_t  *record;
    #endif
    #ifdef USE_BT_FBSD
    void      *session;
    uint32_t      record;
    #endif
} _BtsppConnection;

//
// Support SDP
//

#ifdef USE_BLUEZ

static boolean_t sdpRegister(ConnectInfo* connInfo)
{
    _BtsppConnection* cn = (_BtsppConnection*) connInfo->connectionData;
    if (!cn) {
        return BOOL_NO;
    }
    boolean_t ret = BOOL_YES;
    
    uint8_t svc_uuid_int[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xab, 0xcd };
    uint8_t rfcomm_channel = connInfo->port;
    const char *svc_dsc = "Bluetooth remote control";
    const char *service_prov = "anyRemote";

    uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid,
           svc_class_uuid;
    sdp_list_t *l2cap_list = 0,
                *rfcomm_list = 0,
                 *root_list = 0,
                  *proto_list = 0,
                   *access_proto_list = 0,
                    *svc_class_list = 0,
                     *profile_list = 0;
    sdp_data_t *channel = 0;
    sdp_profile_desc_t profile;
    cn->record = sdp_record_alloc();

    // set the general service ID
    sdp_uuid128_create( &svc_uuid, &svc_uuid_int );
    sdp_set_service_id( cn->record, svc_uuid );

    // set the service class
    sdp_uuid16_create(&svc_class_uuid, SERIAL_PORT_SVCLASS_ID);
    svc_class_list = sdp_list_append(0, &svc_class_uuid);
    sdp_set_service_classes(cn->record, svc_class_list);

    // set the Bluetooth profile information
    sdp_uuid16_create(&profile.uuid, SERIAL_PORT_PROFILE_ID);
    profile.version = 0x0100;
    profile_list = sdp_list_append(0, &profile);
    sdp_set_profile_descs(cn->record, profile_list);

    // make the service record publicly browsable
    sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
    root_list = sdp_list_append(0, &root_uuid);
    sdp_set_browse_groups(cn->record, root_list );

    // set l2cap information
    sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
    l2cap_list = sdp_list_append( 0, &l2cap_uuid );
    proto_list = sdp_list_append( 0, l2cap_list );

    // register the RFCOMM channel for RFCOMM sockets
    sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
    channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
    rfcomm_list = sdp_list_append( 0, &rfcomm_uuid );
    sdp_list_append( rfcomm_list, channel );
    sdp_list_append( proto_list, rfcomm_list );

    access_proto_list = sdp_list_append( 0, proto_list );
    sdp_set_access_protos( cn->record, access_proto_list );

    // set the name, provider, and description
    char *sn = getServiceName();
    sdp_set_info_attr(cn->record, sn, service_prov, svc_dsc);
    free(sn);

    // connect to the local SDP server, register the service record,
    // and disconnect
    cn->session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY);
    if (!cn->session || !cn->record) {
        ERROR2("can not connect to SDP server %s (%d)", 
	        strerror(sdp_get_error(cn->session)), sdp_get_error(cn->session));
	ret = BOOL_NO;
    }
    if (cn->session && cn->record && 
        sdp_record_register(cn->session, cn->record, 0) == -1) {
        ERROR2("can not register SDP service %s (%d)", 
	        strerror(sdp_get_error(cn->session)), sdp_get_error(cn->session));
	ret = BOOL_NO;
    }

    // cleanup
    sdp_data_free( channel );
    sdp_list_free( l2cap_list, 0 );
    sdp_list_free( proto_list, 0 );
    sdp_list_free( rfcomm_list, 0 );
    sdp_list_free( root_list, 0 );
    sdp_list_free( access_proto_list, 0 );
    sdp_list_free( svc_class_list, 0 );
    sdp_list_free( profile_list, 0 );
    
    return ret;
}
#endif

#ifdef USE_BT_FBSD
static boolean_t sdpRegister(ConnectInfo* connInfo)
{
    _BtsppConnection* cn = (_BtsppConnection*) connInfo->connectionData;
    if (!cn) {
        return BOOL_NO;
    }
    boolean_t ret = BOOL_YES;
    
    int channel,service;
    bdaddr_t         bt_addr_any;
    sdp_lan_profile_t     lan;

    channel = connInfo->port;
    service = SDP_SERVICE_CLASS_SERIAL_PORT;

    cn->session = sdp_open_local(NULL);
    if (cn->session == NULL) {
        errx(1, "Unable to create local SDP session");
	ret = BOOL_NO;
    }
    if (sdp_error(cn->session) != 0) {
        errx(1, "Unable to open local SDP session. %s (%d)",
             strerror(sdp_error(cn->session)), sdp_error(cn->session));
	ret = BOOL_NO;
    }
    memset(&lan, 0, sizeof(lan));
    lan.server_channel = channel;

    memcpy(&bt_addr_any, NG_HCI_BDADDR_ANY, sizeof(bt_addr_any));
    
    if (sdp_register_service(cn->session, service, &bt_addr_any,
                             (void *)&lan, sizeof(lan), &(cn->record)) != 0) {
        errx(1, "Unable to register LAN service with "
             "local SDP daemon. %s (%d)",
             strerror(sdp_error(cn->session)), sdp_error(cn->session));
	ret = BOOL_NO;
    }
    
    return ret;
}
#endif

static void sdpDeregister(_BtsppConnection* cn)
{
    if (!cn) return;
    
    logger(L_DBG, "Deregister SDP service");
    #ifdef USE_BLUEZ
    if (cn->session != NULL) {
        sdp_record_unregister(cn->session, cn->record);
        sdp_close(cn->session);
        cn->session = NULL;
        //sdp_record_free(cn->record);
    }
    #endif
    #ifdef USE_BT_FBSD
    if (cn->session != NULL) {
        sdp_unregister_service(cn->session, cn->record);
        sdp_close(cn->session);
        sdp_close(cn->session);
        cn->session = NULL;
    }
    #endif
}

int btsppFD(ConnectInfo* conn)
{
    _BtsppConnection* cn = (_BtsppConnection*) conn->connectionData;
    if (!cn) {
        return -1;
    }
    return (conn->state == PEER_WAIT_ACCEPT || 
            conn->state == PEER_WAIT_LISTEN ? cn->serverFileDescriptor : cn->fileDescriptor);
}

static int btsppOpenInternal(ConnectInfo* connInfo)
{
    #ifdef USE_BLUEZ
    struct sockaddr_rc bt_addr = { 0 };
    #endif
    #ifdef USE_BT_FBSD
    struct sockaddr_rfcomm bt_addr;
    #endif

    struct sockaddr*   socketaddr = NULL;

    int addFamily = AF_UNSPEC;
    int proto     = 0;
    size_t sz;
    
    if (connInfo->connectionData && ((_BtsppConnection*) connInfo->connectionData)->serverFileDescriptor > 0) {
        logger(L_ERR, "BTSPP socket was already opened");
        return 1;
    }
    
    if (connInfo->connectionData) {
        free(connInfo->connectionData);
    }
    
    connInfo->connectionData = (_BtsppConnection*) malloc(sizeof(_BtsppConnection));
    _BtsppConnection* cn = (_BtsppConnection*) connInfo->connectionData;
    
    cn->serverFileDescriptor = -1;
    cn->fileDescriptor       = -1;

    #ifdef USE_BLUEZ
    cn->session              = NULL;
    cn->record               = NULL;
    
    addFamily = AF_BLUETOOTH;
    proto     = BTPROTO_RFCOMM;
    #endif
    
    #ifdef USE_BT_FBSD
    cn->session              = NULL;
    cn->record               = 0;
    
    addFamily = PF_BLUETOOTH;
    proto     = BLUETOOTH_PROTO_RFCOMM;
    #endif

    if (addFamily == AF_UNSPEC) {  // no BT support
        return -1;
    }

    if ((cn->serverFileDescriptor = socket(addFamily, SOCK_STREAM|SOCK_CLOEXEC, proto)) < 0) {
        logger(L_ERR, "opening BT/RFCOMM socket");
        errnoDebug("opening BT/RFCOMM socket",errno);  // testing debug
        
        printf("ERROR: opening BT/RFCOMM socket\n");
        cn->serverFileDescriptor = -1;
        return -1;
    }
    DEBUG2("[DS]: btsppOpenInternal srv descriptor %d", cn->serverFileDescriptor);

    int optval = 1;
    setsockopt(cn->serverFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    /*
    // Set non-blocking mode
    if (-1 == (oldflags = fcntl(sportfd, F_GETFL, 0))) {
        oldflags = 0;
    }
    fcntl(sportfd, F_SETFL, oldflags | O_NONBLOCK);
    */


    #ifdef USE_BLUEZ

    //memset((void *) &bt_addr, 0, sizeof(bt_addr));
    sz = sizeof(bt_addr);

    // bind socket to the specified port of the first available local bluetooth adapter
    bt_addr.rc_family = AF_BLUETOOTH;
    bt_addr.rc_bdaddr = *BDADDR_ANY;
    bt_addr.rc_channel = (uint8_t) connInfo->port;

    // try to do the same manually
    //char tmpstring[512];
    //sprintf(tmpstring, "sdptool add --channel=%i SP", connInfo->port);
    //sprintf(tmpstring, "sdptool add --channel=%i SP;sdptool setattr `sdptool search --bdaddr local SP|grep \"Service RecHandle\"|tail -1|cut -f 3 -d \" \"` 0x100 anyRemote", connInfo->port);
    //sprintf(tmpstring, "bash -c \'A=`sdptool search --bdaddr local SP|grep \"Service Name\"|grep anyRemote|wc -l`; if [ \"x$A\" == \"x0\" ]; then sdptool add --channel=%i SP;sdptool setattr `sdptool search --bdaddr local SP|grep \"Service RecHandle\"|tail -1|cut -f 3 -d \" \"` 0x100 anyRemote; fi\'", connInfo->port);
    //system(tmpstring);
    
    if (sdpRegister(connInfo) == BOOL_YES) {
    	sprintf(tmp, "[DS]: registered SP for channel %i", connInfo->port);
    	logger(L_INF, tmp);
    } else {
	
	size_t sz;
	char* btDaemon = executeCommandPipe("ps -eo args|grep bluetoothd|grep -v grep", &sz);
	if (btDaemon) {
	     if (!strstr(btDaemon,"bluetoothd")) {
	         ERROR2("[DS]: ERROR: bluetoothd daemon is not started");
		 writeToFrontEnd("Bluetooth connection will not work. bluetoothd daemon is not started");
		 printf("ERROR: bluetoothd daemon is not started\n");
	     } else if (!strstr(btDaemon,"-C")) {
	         ERROR2("[DS]: It needs to run bluetoothd daemon with -C option");
		 writeToFrontEnd("Bluetooth connection will not work. It needs to run bluetoothd daemon with -C option");
	         printf("ERROR: Bluetooth connection will not work. It needs to run bluetoothd daemon with -C option\n");
             } else {
 		 ERROR2("[DS]: Not enough permissions to register SDP service");
		 writeToFrontEnd("Not enough permissions to register SDP service");
		 printf("ERROR: Not enough permissions to register SDP service\n");
	     }
	} else {
	     ERROR2("[DS]: ERROR: bluetoothd daemon is not started");
	     writeToFrontEnd("Bluetooth connection will not work. bluetoothd daemon is not started");
	     printf("ERROR: bluetoothd daemon is not started\n");
	}
	
	
	/*     } else {
	         char* sdp = executeCommandPipe("sdptool search --bdaddr local SP", &sz);
		 if (sdp) {
		     if (strstr(sdp,"Permission denied")) {
		         ERROR2("[DS]: Not enough permissions to run sdptool search command");
			 writeToFrontEnd("Not enough permissions to run sdptool search command");
		     } else {
			 printf("TEST %s", sdp);
	        	 char* ptrptr = NULL;
    	        	 char* tok  = strtok_r(sdp,"\\n", &ptrptr); 

			 while (tok) {
	        	     printf("TEST %s", tok);
			     tok  = strtok_r(NULL,"\\n", &ptrptr); 

			 }
		     }
		 }
	     }
	}*/
    }
    
    socketaddr=(struct sockaddr *)&bt_addr;
    #endif

    #ifdef USE_BT_FBSD
    memset(&bt_addr, 0, sizeof(bt_addr));
    sz = sizeof(bt_addr);

    bt_addr.rfcomm_len = sizeof(bt_addr);
    bt_addr.rfcomm_family = AF_BLUETOOTH;
    bt_addr.rfcomm_channel =  (uint8_t) connInfo->port;
    if (sdpRegister(connInfo) == BOOL_YES) {
        sprintf(tmp, "registered SP for channel %i", connInfo->port);
        logger(L_INF, tmp);
    }

    socketaddr=(struct sockaddr *)&bt_addr;
    #endif
 
    if (bind(cn->serverFileDescriptor, (struct sockaddr *) socketaddr, sz) < 0) {
        logger(L_ERR, "on binding");
        printf("ERROR: on binding %d->%s\n", errno, strerror(errno));
        return -1;
    }
    
    return 1;
}


int btsppOpen(ConnectInfo* connInfo)
{
    DEBUG2("[DS]: btsppOpen %d", connInfo->mode);
    if (connInfo->mode != SERVER_BT) {
        DEBUG2("[DS]: btsppOpen wrong mode");
    }
    if (btsppOpenInternal(connInfo) < 0) {
        return EXIT_NOK;
    }
    connInfo->state = PEER_WAIT_LISTEN;
    
    // make work with bluez 5.x
    btsppListen(connInfo);
    
    return EXIT_OK;
}

void btsppClose(ConnectInfo* connInfo, int final)
{
    if (final) {
        logger(L_INF, "btsppClose");
    }
    
    _BtsppConnection* cn = (_BtsppConnection*) connInfo->connectionData;
    if (!cn) return;

    if (cn->fileDescriptor >= 0) {
        if (final) {
            logger(L_INF, "btsppClose close socket");
        }
        close(cn->fileDescriptor);
        cn->fileDescriptor = -1;
    }
    if (cn->serverFileDescriptor >= 0) {
        if (final) {
        logger(L_INF, "closeSocketPort close server socket");
    }
        close(cn->serverFileDescriptor);
        cn->serverFileDescriptor = -1;
    }
    
    if (final) {
        sdpDeregister(cn);
    }
    
    free(cn);
    connInfo->connectionData = NULL;
    connInfo->state = PEER_DISCONNECTED;
}

void btsppReset(ConnectInfo* conn)
{
    _BtsppConnection* cn = (_BtsppConnection*) conn->connectionData;
    if (cn) {
        if (cn->fileDescriptor >= 0) {
            close(cn->fileDescriptor);
            cn->fileDescriptor = -1;
        }
        conn->state = PEER_WAIT_ACCEPT;
    } else {
        conn->state = PEER_DISCONNECTED;  // should not happens
    }
}

//
// Setup peer
//
int btsppListen(ConnectInfo* conn)
{
    logger(L_INF, "[DS]: btsppListen");
    
    _BtsppConnection* cn = (_BtsppConnection*) conn->connectionData;
    if (!cn) {
        return -1;
    }
    INFO2("[DS]: btsppListen: fd %d", cn->serverFileDescriptor);
    int ret = listen(cn->serverFileDescriptor,0);
    if (ret >= 0) {
        conn->state = PEER_WAIT_ACCEPT;
    }
    INFO2("[DS]: btsppListen ret=%d",ret);
    return (ret < 0 ? -1 : 1);
}

//
// Wait for incoming connection
//
int btsppAccept(ConnectInfo* connInfo)
{
    logger(L_INF, "[DS]: btsppAccept");
    
    _BtsppConnection* cn = (_BtsppConnection*) connInfo->connectionData;
    if (!cn) return -1;
    
    logger(L_INF, "[DS]: BTSPP server mode: Waiting connection");
    int cnt;
    socklen_t sz;

    struct sockaddr* socketaddr = NULL;

    #ifdef USE_BLUEZ
    struct sockaddr_rc bt_addr;
    bdaddr_t ba;
    #endif
    #ifdef USE_BT_FBSD
    struct sockaddr_rfcomm bt_addr;
    #endif

    logger(L_INF, "btsppAccept");
    cnt = 0;

    #if defined(USE_BLUEZ) || defined(USE_BT_FBSD)
    socketaddr=(struct sockaddr *)&bt_addr;
    sz = sizeof(bt_addr);
    #endif

    while (stillRun) {

        INFO2("btsppAccept: fd %d", cn->serverFileDescriptor);

        cn->fileDescriptor = accept(cn->serverFileDescriptor, (struct sockaddr *) socketaddr, &sz);

        if (cn->fileDescriptor < 0 && errno == EAGAIN) {

            if (cnt >= 60) {    // Print to log every minute
                logger(L_INF, "btsppAccept: waiting for connection");
                //printf(".");
                cnt = 0;
            }
            fflush(stdout);

            sleep(1);
            cnt++;

            continue;
        }

        if (cn->fileDescriptor < 0) {
            logger(L_ERR, "btsppAccept: on accept");
            return -1;
        }
	INFO2("[DS]: btsppAccept fd=%d",cn->fileDescriptor);
        
	char* btAddress = NULL;
        #ifdef USE_BLUEZ
        baswap(&ba, &bt_addr.rc_bdaddr);
        btAddress = batostr(&ba);
        #endif
        #ifdef USE_BT_FBSD
        btAddress = strdup(bt_ntoa(&bt_addr.rfcomm_bdaddr, NULL));
        #endif
    
        if (!isAllowed(btAddress)) {
            INFO2("btsppAccept: host %s is not in the list of accepted host, close connection", (btAddress ? btAddress : "NULL"));
            write(cn->fileDescriptor,CMD_STR_DISCONNECT,strlen(CMD_STR_DISCONNECT));
        
            close(cn->fileDescriptor);
            cn->fileDescriptor = -1;
            connInfo->state = PEER_DISCONNECTED;
        
            free(btAddress);
        
            return -1;
        }
    
        if (getUsePassword() && !getBemused()) {
            logger(L_DBG,"[DS]: btsppAccept: Do password verification");

            int ret = EXIT_OK;

            int i=0;
            for ( ; i<3; i++) {

                ret = verifyPassword(cn->fileDescriptor);

                if (ret == EXIT_OK) {    // got it
                   break;

                }
            }

            if (ret != EXIT_OK) {

                if (ret == EXIT_NOK) {
                    write(cn->fileDescriptor,CMD_STR_DISCONNECT,strlen(CMD_STR_DISCONNECT));
                }

                close(cn->fileDescriptor);
                cn->fileDescriptor = -1;
                connInfo->state = PEER_DISCONNECTED;

                free(btAddress);

                return -1;
            }
            logger(L_DBG,"[DS]: socketAccept: Password verification OK");
        }

        logger(L_INF, "btsppAccept: accepted");
        connInfo->state = PEER_CONNECTED;

        freeBtAddress();
    
        sprintf(tmp, "btsppAccept: remote BT address is %s", (btAddress ? btAddress : "NULL"));
        setBtAddress(btAddress);
        logger(L_INF, tmp);
        
        // force to detect cover size. need to do that before (Connect) or syncPeer() handling
        getClientSize(connInfo->id, cn->fileDescriptor);  

        break;
    }
    return 1;
}

int btsppWrite(ConnectInfo* connInfo, dMessage* msg)
{
    _BtsppConnection* cn = (_BtsppConnection*) connInfo->connectionData;
    if (!cn) {
        logger(L_DBG,"[DS]: btsppWrite() no connection data");
        return EXIT_NOK;
    }
   
    const char* command = msg->value; 
    int count = msg->size; 
    
    //logger(L_DBG, "btsppWrite");
    if (!command || count <= 0) {
        return EXIT_OK;
    }
    
    if (strcmp("End();",command) == 0) {  // used only for WEB/CMXML
        return EXIT_OK;
    }

    // send command
    if (cn->fileDescriptor >= 0 && count > 0) {

        memset(tmp, 0, MAXMAXLEN);
        strcat(tmp, "btsppWrite ");

        int logSz = (count > 256 ? 255 : count);

        // it is possible to get binary data here
        memcpy(tmp, command, logSz); // Do not dump long commands
        tmp[logSz] = '\0';
        logger(L_DBG, tmp);

        sprintf(tmp, "btsppWrite %d bytes", count);
        logger(L_INF, tmp);

        int n = write(cn->fileDescriptor,command,count);
        if (n < 0) {
            logger(L_ERR, "error writing to BTSPP socket");
            return EXIT_NOK;
        }
        return EXIT_OK;
    } else {
        logger(L_ERR, "error writing to socket: already closed");
    }
    return EXIT_NOK;
}
