// ///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002 Ultr@VNC Team Members. All Rights Reserved.
//
// 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 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// Program is based on the
// http://www.imasy.or.jp/~gotoh/ssh/connect.c
// Written By Shun-ichi GOTO <gotoh@taiyo.co.jp>
//
// If the source code for the program is not available from the place
// from
// which you received this file, check
// http://ultravnc.sourceforge.net/
//
// Linux port (C) 2005 Jari Korhonen, jarit1.korhonen@dnainternet.net
// ///////////////////////////////////////////////////////////////////////////

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <memory.h>
#include <errno.h>
#include <assert.h>
#include <stdarg.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <netdb.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "repeaterproc.h"

#define REPEATERVERSION "0.07"

#define rfbProtocolVersionFormat "RFB %03d.%03d\n"
#define rfbProtocolMajorVersion 0
#define rfbProtocolMinorVersion 0
#define SIZE_rfbProtocolVersionMsg 12

#define MAX_IDLE_CONNECTION_TIME 600    //Seconds
#define MAX_HOST_NAME_LEN 250
#define MAX_IP_LEN 20
#define MAX_SESSIONS 100                //Maximum active repeater sessions
#define UNKNOWN_REPINFOIND 999          //Notice: This should always be bigger than MAX_SESSIONS

//Use safer openbsd string functions:
//strlcpy instead of strcpy
extern size_t strlcpy(char *dst, const char *src, size_t siz);

//strlcat instead of strcat
extern size_t strlcat(char *dst, const char *src, size_t siz);

typedef char rfbProtocolVersionMsg[SIZE_rfbProtocolVersionMsg+1]; /* allow extra byte for null */

typedef struct _repeaterinfo {
    int socket;
    long code;
    unsigned long timestamp;
    char peerip[MAX_IP_LEN];        //Ip address of the other end

    //There are 3 connection levels (using variables "code" and "active"):
    //A. code==0,active==false: fully idle, no connection attempt detected
    //B. code==non-zero,active==false: server/viewer has connected, waiting for other end to connect
    //C. code==non-zero,active=true: do_repeater() running on viewer/server connection, fully active
    //-after viewer/server disconnects or some error in do_repeater, returns both to level A
    //(and closes respective sockets)
    //This logic means, that when one end disconnect, BOTH ends need to reconnect. This is not a bug, it is feature
    //(this repeater will anyway be running in internet-visible machine and server/viewer behind a corporate firewall,
    //so connections have to be outwards from server/viewer to repeater)
    bool active;
} repeaterinfo;
static repeaterinfo Servers[MAX_SESSIONS];
static repeaterinfo Viewers[MAX_SESSIONS];


//These tables are used in function UpdateServerViewerInfo to compare against current Servers/Viewers
//and update differences
static repeaterinfo OldServers[MAX_SESSIONS];
static repeaterinfo OldViewers[MAX_SESSIONS];

//This structure (and RepeaterProcs[] table) is used for keeping track of child processes running do_repeater
//and cleaning up after they exit
typedef struct _repeaterprocinfo
{
    long code;
    pid_t pid;
} repeaterprocinfo;
static repeaterprocinfo RepeaterProcs[MAX_SESSIONS];

//This structure keeps information of ports/socket used when
//route_connections() listens for new incoming connections
typedef struct _listenportinfo {
    int socket;
    int port;
} listenportinfo;

//stopped==true means that user wants program to stop (has pressed ctrl+c)
static bool stopped;

int ReadExact(int sock, char *buf, int len);
int Find_viewer_list(long code);

void debug(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stderr, "UltraVnc> ");
    vfprintf(stderr, fmt, args);
    va_end(args);
}

void Clean_repeaterproc_list(void)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        RepeaterProcs[i].code = 0;
        RepeaterProcs[i].pid = 0;
    }
}

void Add_repeaterproc_list(long code, pid_t pid)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (RepeaterProcs[i].code == 0) {
            debug("Add_repeaterproc_list(): Added proc to index %d, pid=%d, code=%ld\n", i, pid, code);
            RepeaterProcs[i].code = code;
            RepeaterProcs[i].pid = pid;
            return;
        }
    }
    debug("Add_repeaterproc_list(): Error, no free process slots found\n");
}

void Remove_repeaterproc_list(pid_t pid)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (RepeaterProcs[i].pid == pid) {
            debug("Remove_repeaterproc_list(): Removing proc from index %d, pid=%d\n", i, pid);
            RepeaterProcs[i].code = 0;
            RepeaterProcs[i].pid = 0;
            return;
        }
    }
    debug("Remove_repeaterproc_list(): Error, did not find any process to remove\n");

}

int Find_repeaterproc_list(pid_t pid)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (RepeaterProcs[i].pid == pid) {
            debug("Find_repeaterproc_list(): proc found at %d, pid=%d, code = %ld\n", i, pid, RepeaterProcs[i].code);
            return i;
        }
    }

    return UNKNOWN_REPINFOIND;
}


void Clean_server_List()
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        Servers[i].code = 0;
        Servers[i].active = false;
        OldServers[i].code = 0;
        OldServers[i].active = false;
    }
}

void Add_server_list(int socket, long code, char *peerip)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Servers[i].code == code) {
            debug("Add_server_list(): similar server already there (reconnect ?)\n");
            return;
        }
    }

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Servers[i].code == 0) {
            debug("Add_server_list(): Server added to list %ld\n", code);
            Servers[i].code = code;
            Servers[i].socket = socket;
            strlcpy(Servers[i].peerip, peerip, MAX_IP_LEN);
            Servers[i].timestamp = time(NULL);  /* 1 second accuracy is enough ? */
            Servers[i].active = false;
            return;
        }
    }
}

void Remove_server_list(long code)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Servers[i].code == code) {
            debug("Remove_server_list(): Server Removed from list %ld\n", code);
            Servers[i].code = 0;
            Servers[i].active = false;
            return;
        }
    }
}

void Set_server_active(long code)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Servers[i].code == code) {
            Servers[i].active = true;
            debug("Set_server_active(): activated server at %d, code = %ld\n", i, Servers[i].code);
            return;
        }
    }
}


int Find_server_list(long code)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Servers[i].code == code) {
            debug("Find_server_list(): server found at %d, code = %ld\n", i, Servers[i].code);
            return i;
        }
    }

    return UNKNOWN_REPINFOIND;
}

void Clean_viewer_List()
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        Viewers[i].code = 0;
        Servers[i].active = false;
        OldViewers[i].code = 0;
        OldServers[i].active = false;
    }
}

void Add_viewer_list(int socket, long code, char *peerip)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Viewers[i].code == code) {
            debug("Add_viewer_list(): similar viewer already there (reconnect ?)\n");
            return;
        }
    }

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Viewers[i].code == 0) {
            debug("Add_viewer_list(): Viewer added to list %d\n", code);
            Viewers[i].code = code;
            Viewers[i].socket = socket;
            strlcpy(Viewers[i].peerip, peerip, MAX_IP_LEN);
            Viewers[i].timestamp = time(NULL);
            Viewers[i].active = false;
            return;
        }
    }
}

void Remove_viewer_list(long code)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Viewers[i].code == code) {
            debug("Remove_viewer_list(): Viewer removed from list %d\n", code);
            Viewers[i].code = 0;
            Viewers[i].active = false;
            return;
        }
    }
}


void Set_viewer_active(long code)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Viewers[i].code == code) {
            Viewers[i].active = true;
            debug("Set_viewer_active(): activated viewer at %d, code = %ld\n", i, Viewers[i].code);
            return;
        }
    }
}


int Find_viewer_list(long code)
{
    int i;

    for (i = 0; i < MAX_SESSIONS; i++) {
        if (Viewers[i].code == code) {
            debug("Find_viewer_list(): viewer found at %d, code = %ld\n", i, Viewers[i].code);
            return i;
        }
    }

    return UNKNOWN_REPINFOIND;
}

void error(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stderr, "ERROR: ");
    vfprintf(stderr, fmt, args);
    va_end(args);
}

void fatal(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    fprintf(stderr, "FATAL: ");
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}




//Parse IdCode string of format "ID:xxxxx", where xxxxx is some positive (non-zero) long integer number
//Return -1 on error, xxxxx on success
long ParseId(char *IdCode)
{
    unsigned int ii;
    int retval;

    debug("ParseId(): IdCode = %s\n", IdCode);

    //Require that 1st 3 characters of IdCode are 'I','D',':'
    if ((IdCode[0] != 'I') || (IdCode[1] != 'D') || (IdCode[2] != ':')) {
        debug("ParseId(): IdCode format error, does not start ""ID:"" \n");
        return -1;
    }
    else {
        //Require that all other characters of IdCode are digits
        for (ii = 3; ii < strlen(IdCode); ii++) {
            if (!isdigit(IdCode[ii])) {
                debug("ParseId(): IdCode format error, code should consist of decimal digits\n");
                return -1;
            }
        }

        retval = strtol(&(IdCode[3]), NULL, 10);
        if (retval <= 0) {
            debug("ParseId(): IdCode format error, code should be positive long integer number\n");
            return -1;
        }
        else if (retval == LONG_MAX) {
            debug("ParseId(): IdCode format error, code is too big\n");
            return -1;
        }

        return retval;
    }
}

long ParseHTTPSId(char *IdCode, char *isServer)
{
    unsigned int ii;
    int retval;

    debug("ParseHTTPSId(): IdCode = %s\n", IdCode);

    //Require that 1st 4 characters of IdCode are 'I','D','?',':'
    if ((IdCode[0] != 'I') || (IdCode[1] != 'D') || (IdCode[3] != ':')) {
        debug("ParseHTTPSId(): IdCode format error, does not start ""ID?:"" \n");
        return -1;
    }
    if (IdCode[2] == 'V') {
		*isServer = 0;
	} else if (IdCode[2] == 'S') {
		*isServer = 1;
	} else {
        debug("ParseHTTPSId(): IdCode format error, not a server or a viewer\n");
        return -1;
	}

	//Require that all other characters of IdCode are digits
	for (ii = 4; ii < strlen(IdCode); ii++) {
		if (!isdigit(IdCode[ii])) {
			debug("ParseHTTPSId(): IdCode format error, code should consist of decimal digits\n");
			return -1;
		}
	}

	retval = strtol(&(IdCode[4]), NULL, 10);
	if (retval <= 0) {
		debug("ParseHTTPSId(): IdCode format error, code should be positive long integer number\n");
		return -1;
	}
	else if (retval == LONG_MAX) {
		debug("ParseHTTPSId(): IdCode format error, code is too big\n");
		return -1;
	}

	return retval;
}

int WriteExact(int sock, char *buf, int len)
{
    int n;

    while (len > 0) {
        n = send(sock, buf, len, 0);

        if (n > 0) {
            buf += n;
            len -= n;
        }
        else if (n == 0) {
            fprintf(stderr, "WriteExact: write returned 0?\n");
            exit(1);
        }
        else {
            return n;
        }
    }

    return 1;
}

int ReadExact(int sock, char *buf, int len)
{
    int n;

    while (len > 0) {
        n = recv(sock, buf, len, 0);

        if (n > 0) {
            buf += n;
            len -= n;
        }
        else {
            return n;
        }
    }

    return 1;
}


//This function is periodically called from route_connections() to remove
//Servers / Viewers that did not receive any matching other end connection
void remove_old_inactive_connections(void)
{
    int i;
    unsigned long tick = time(NULL);

    for (i = 0; i < MAX_SESSIONS; i++) {
        //Remove old inactive viewers
        if ((tick - Viewers[i].timestamp) > MAX_IDLE_CONNECTION_TIME) {
            if ((Viewers[i].active == false) && (Viewers[i].code != 0)) {
                close(Viewers[i].socket);
                debug("remove_old_inactive_connections(): Removing viewer %ld at index %d \n", Viewers[i].code, i);
                Remove_viewer_list(Viewers[i].code);
            }
        }

        //Remove old inactive servers
        if ((tick - Servers[i].timestamp) > MAX_IDLE_CONNECTION_TIME) {
            if ((Servers[i].active == false) && (Servers[i].code != 0)) {
                close(Servers[i].socket);
                debug("remove_old_inactive_connections(): Removing server %ld at index %d\n", Servers[i].code, i);
                Remove_server_list(Servers[i].code);
            }
        }
    }
}


//Accept connections from both servers and viewers
//(mode == 0 means servers, mode==1 means viewers, mode==2 means https)
void accept_connection(int socket, int mode)
{
rfbProtocolVersionMsg pv;
int connection;
char Id[MAX_HOST_NAME_LEN + 1];
long code;
struct sockaddr_in client;
socklen_t socklen;
char *peerip;
char isServer=(mode==0)?1:0;

    socklen = sizeof(struct sockaddr_in);
    connection = accept(socket, (struct sockaddr *) &client, &socklen);

    if (connection < 0)
        debug("accept_connection(): accept() failed, errno=%d (%s)\n", errno, strerror(errno));
    else {
        peerip = inet_ntoa(client.sin_addr);

        debug("accept_connection(): connection accepted ok from ip: %s\n", peerip);

        if (mode==1) {
            //We handshake viewers by transmitting rfbProtocolVersion first
            snprintf(pv, SIZE_rfbProtocolVersionMsg+1, rfbProtocolVersionFormat,
                rfbProtocolMajorVersion, rfbProtocolMinorVersion);

            if (WriteExact(connection, pv, SIZE_rfbProtocolVersionMsg) < 0) {
                debug("accept_connection(): Writing protocol version error\n");
                close(connection);
                return;
            }
        }

        if (ReadExact(connection, Id, MAX_HOST_NAME_LEN) < 0) {
            debug("accept_connection(): Reading id error\n");
            close(connection);
            return;
        }

		if(mode<2) {
			code = ParseId(Id);
		} else {
			code = ParseHTTPSId(Id, &isServer);
		}

		if (-1 == code) {
			debug("accept_connection(): ParseId returned error\n");
			close(connection);
			return;
		}

		// hope it is ok for the viewers to receive the protocol version after reding the id
        if (!isServer && mode==2) {
            //We handshake viewers by transmitting rfbProtocolVersion first
            snprintf(pv, SIZE_rfbProtocolVersionMsg+1, rfbProtocolVersionFormat,
                rfbProtocolMajorVersion, rfbProtocolMinorVersion);

            if (WriteExact(connection, pv, SIZE_rfbProtocolVersionMsg) < 0) {
                debug("accept_connection(): Writing protocol version error\n");
                close(connection);
                return;
            }
        }


        debug("accept_connection():  %s sent code %ld \n", (isServer==0) ? "Viewer" : "Server", code);


        if (isServer == 0) {
            //New viewer, find respective server
            Add_viewer_list(connection, code, peerip);

            if (Find_server_list(code) != UNKNOWN_REPINFOIND) {
                int server;
                pid_t pid;

                //found respective server, activate viewer and server
                Set_viewer_active(code);
                Set_server_active(code);

                server = Servers[Find_server_list(code)].socket;


                //fork repeater
                pid = fork();
                if (0 == pid) {
                    //child code
                    debug("accept_connection(): forking do_repeater(%d, %d)\n", server, connection);
                    exit(do_repeater(server, connection));
                }
                else {
                    //parent code
                    //Add necessary information of child to RepeaterProcs list so SIG_CHLD signal handler
                    //can properly clean up after child has exited
                    Add_repeaterproc_list(code, pid);
                }
            }
            else {
                debug("accept_connection():  respective server has not connected yet\n");
            }
        }
        else {
            //New server, find respective viewer
            Add_server_list(connection, code, peerip);

            if (Find_viewer_list(code) != UNKNOWN_REPINFOIND) {
                int viewer;
                pid_t pid;

                //found respective viewer, activate server and viewer
                Set_server_active(code);
                Set_viewer_active(code);

                viewer = Viewers[Find_viewer_list(code)].socket;

                //fork repeater
                pid = fork();
                if (0 == pid) {
                    //child code
                    debug("accept_connection(): forking do_repeater(%d, %d)\n", connection, viewer);
                    exit(do_repeater(connection, viewer));
                }
                else {
                    //parent code
                    //Add necessary information of child to RepeaterProcs list so SIG_CHLD signal handler
                    //can properly clean up after child has exited
                    Add_repeaterproc_list(code, pid);
                }
            }
            else {
                debug("accept_connection():  respective viewer has not connected yet\n");
            }
        }

    }
}

//Initialize listening on port.
//Listening itself happens on function route_connections
void start_listening_on_port(listenportinfo * pInfo)
{
    int yes = 1;
    struct sockaddr_in name;

    pInfo->socket = socket(PF_INET, SOCK_STREAM, 0);

    if (pInfo->socket < 0)
        fatal("start_listening_on_port(): socket() failed, errno=%d (%s)\n", errno, strerror(errno));
    else
        debug("start_listening_on_port(): socket() initialized\n");

    if (setsockopt(pInfo->socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
        fatal("start_listening_on_port(): setsockopt() failed, errno=%d (%s)\n", errno, strerror(errno));
    else
        debug("start_listening_on_port(): setsockopt() success\n");

    name.sin_family = AF_INET;

    name.sin_port = htons(pInfo->port);

    name.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(pInfo->socket, (struct sockaddr *) &name, sizeof(name)) < 0)
        fatal("start_listening_on_port(): bind() failed, errno=%d (%s)\n", errno, strerror(errno));
    else
        debug("start_listening_on_port(): bind() succeeded to port %d\n", pInfo->port);

    if (listen(pInfo->socket, 1) < 0)
        fatal("start_listening_on_port(): listen() failed, errno=%d (%s)\n", errno, strerror(errno));
    else
        debug("start_listening_on_port(): listen() succeeded\n");

}


//This function (called from route_connections) periodically checks changes in
//Servers[] / Viewers[] tables.
//This could be used to update changes in web page, database etc.
//Current version only outputs a debug() line
void UpdateServerViewerInfo(void)
{
    int i;

    //Check changes in Servers
    for (i = 0; i < MAX_SESSIONS; i++) {
        if (memcmp( &(Servers[i]), &(OldServers[i]), sizeof(repeaterinfo))) {
            //Something has changed in index i, update that in database
            debug("UpdateServerViewerInfo(): Servers[] has changed at index %d\n", i);

            //New server connection ?
            if (Servers[i].code != 0)
                debug("UpdateServerViewerInfo(): New server connection from : %s\n", Servers[i].peerip);

            //Update to current situation
            OldServers[i] = Servers[i];
        }
    }

    //Check changes in Viewers
    for (i = 0; i < MAX_SESSIONS; i++) {
        if (memcmp(&(Viewers[i]), &(OldViewers[i]), sizeof(repeaterinfo))) {
            //Something has changed in index i, update that in database
            debug("UpdateServerViewerInfo(): Viewers[] has changed at index %d\n", i);


            //New viewer connection ?
            if (Viewers[i].code != 0)
                debug("UpdateServerViewerInfo(): New viewer connection from : %s\n", Viewers[i].peerip);

            //Update to current situation
            OldViewers[i] = Viewers[i];
        }
    }
}


//Listen for new connections on both server and viewer ports,
//call accept_connection() to accept them.
//Periodically also remove old inactive connections by calling remove_old_inactive_connections()
//Periodically call UpdateServerViewerInfo() to check changes in Servers[]/Viewers[] tables
void route_connections(int viewersocket, int serversocket, int httpssocket)
{
    int seconds;
    fd_set readfds;
    int numfds;
    bool select_ok;
    struct timeval tv;
    const int SELECTWAIT=10;

    seconds = 0;
    numfds = ((viewersocket > serversocket) ? viewersocket : serversocket);
    numfds = ((numfds > httpssocket) ? numfds : httpssocket) + 1;

    debug("route_connections(): starting select() loop, terminate with ctrl+c\n");
    while (stopped == false) {
        FD_ZERO(&readfds);
        FD_SET(viewersocket, &readfds);
        FD_SET(serversocket, &readfds);
        if(httpssocket) /* only if we need https-sockes */
        	FD_SET(httpssocket, &readfds);

        tv.tv_sec = SELECTWAIT;
        tv.tv_usec = 0;

        select_ok = true;
        if (-1 == select(numfds, &readfds, NULL, NULL, &tv)) {
            select_ok = false;
            if (stopped == false) {
                //we can get EINTR when do_repeater ends (child exits)
                if (EINTR != errno) {
                    debug("route_connections(): select() failed, errno=%d (%s)\n", errno, strerror(errno));
                }
            }
        }

        if ((select_ok == true) && (stopped == false)) {
            //New viewer trying to connect ?
            if (FD_ISSET(viewersocket, &readfds)) {
                debug("route_connections(): new viewer connecting, accepting...\n");
                accept_connection(viewersocket, 1);
            }

            //New server trying to connect ?
            if (FD_ISSET(serversocket, &readfds)) {
                debug("route_connections(): new server connecting, accepting...\n");
                accept_connection(serversocket, 0);
            }

			//New server or client tries to connect over https
            if (FD_ISSET(httpssocket, &readfds)) {
                debug("route_connections(): new viewer or server connecting, accepting...\n");
                accept_connection(httpssocket, 2);
            }

            //Remove old inactive connections
            seconds += SELECTWAIT;
            if (seconds >= 60) {
                seconds = 0;
                remove_old_inactive_connections();
            }

            //Update external info about Server/Viewers
            UpdateServerViewerInfo();
        }
    }
}


//After do_repeater process has exited, this function reads exit code/pid and clears
//Server[], Viewers[] and RepeaterProcs[] tables accordingly
void Clean_up_after_repeaterproc_exit(int exitcode, pid_t pid) {
    long code;
    int index;
    int serverind;
    int viewerind;

    debug("Clean_up_after_repeaterproc_exit(): exitcode=%d, pid=%d\n", exitcode, pid);
    index = Find_repeaterproc_list(pid);
    if (index != UNKNOWN_REPINFOIND) {
        code = RepeaterProcs[index].code;
        serverind = Find_server_list(code);
        viewerind = Find_viewer_list(code);

        if ((serverind != UNKNOWN_REPINFOIND) && (viewerind != UNKNOWN_REPINFOIND))
        {
            //Remove repeaterproc from list
            Remove_repeaterproc_list(pid);

            debug("Clean_up_after_repeaterproc_exit(): code=%ld, serverind=%d, viewerind=%d\n",
                code, serverind, viewerind);

            switch(exitcode) {
                case 1:
                    //Error in select(), fall through
                case 2:
                    //Server has disconnected, fall through
                case 3:
                    //Viewer has disconnected, fall through
                case 4:
                    //Error when reading from viewer, fall through
                case 5:
                    //Error when reading from server
                    close(Viewers[viewerind].socket);
                    close(Servers[serverind].socket);
                    Remove_server_list(code);
                    Remove_viewer_list(code);
                    break;

                default:
                    break;
            }
        }
        else {
            debug("Clean_up_after_repeaterproc_exit(): illegal viewerind = %d or serverind =%d\n", viewerind, serverind);
        }
    }
    else {
        debug("Clean_up_after_repeaterproc_exit(): proc not found\n");
    }
}



//Clean up after child process exit
void sigchld_handler(int signal)
{
    int status;
    pid_t pid;

    if (signal == SIGCHLD) {
        do {
            pid = wait(&status);
            if (pid > 0) {
                Clean_up_after_repeaterproc_exit(WEXITSTATUS(status), pid);
            }
        } while (pid > 0);
    }
    else {
        debug("sigchld_handler(): signal is not SIGCHLD\n");
    }
}

//Terminate program with ctrl+c cleanly
void sigint_handler(int s)
{
    stopped = true;
}

int main(int argc, char **argv)
{
    //Ports where we listen
    u_short viewer_port;
    u_short server_port;
    u_short https_port;

    //Viewer port listener variable
    listenportinfo viewerListener;

    //Server port listener variable
    listenportinfo serverListener;

    //HTTPS port listener variable
    listenportinfo httpsListener;

    //Signal handlers
    struct sigaction sa_child;
    struct sigaction sa_int;

    stopped = false;

    fprintf(stderr, "UltraVnc Linux Repeater version %s\n", REPEATERVERSION);

    Clean_server_List();
    Clean_viewer_List();
    Clean_repeaterproc_list();

    //Initialize port variables according to command-line parameters
    viewer_port = 5900;
    server_port = 5500;
    https_port = 0;

    if (argc >= 2)
        viewer_port = atoi(argv[1]);

    if (argc >= 3)
        server_port = atoi(argv[2]);

    if (argc >= 4)
        https_port = atoi(argv[3]);

    //Initialize signal handlers
    memset(&sa_child, 0, sizeof(sa_child));
    sa_child.sa_handler = &sigchld_handler;
    sigaction(SIGCHLD, &sa_child, NULL);

    memset(&sa_int, 0, sizeof(sa_int));
    sa_int.sa_handler = &sigint_handler;
    sigaction(SIGINT, &sa_int, NULL);

    //Initialize and start listening on viewer port
    viewerListener.port = viewer_port;
    start_listening_on_port(&viewerListener);

    //Initialize and start listening on server port
    serverListener.port = server_port;
    start_listening_on_port(&serverListener);

	if(https_port) {
		httpsListener.port = https_port;
		start_listening_on_port(&httpsListener);
	} else {
		httpsListener.port = 0;
		httpsListener.socket = 0;
	}

    //Accept & Route new connections
    route_connections(viewerListener.socket, serverListener.socket, httpsListener.socket);

    debug("main(): relaying done.\n");
    close(viewerListener.socket);
    close(serverListener.socket);
    return 0;
}

