/* Copyright (c) 1995,1996,1997 NEC Corporation.  All rights reserved.       */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: cache.c,v 1.30.2.1 1997/08/29 21:13:46 wlu Exp $
 */

#include "socks5p.h"
#include "buffer.h"
#include "addr.h"
#include "wrap.h"
#include "cache.h"
#include "log.h"
#include "msg.h"

static fd_set sset;
static int initialized = 0;

lsSocksInfo *lsConList = NULL;	         /* all current lib connections      */
lsSocksInfo *lsLastCon = NULL;	         /* pointer to the proxy connect     */

static void lsProxyCacheDelete(lsProxyInfo **pp, S5IOHandle rfd) {
    lsProxyInfo *n, *p = *pp;
    
    for (n = p?p->next:NULL; p ; p = n, n = n?n->next:NULL) {
	if (--(p->refcount) > 0) continue;
	if (p->cinfo.fd == rfd) p->cinfo.fd = S5InvalidIOHandle;
	S5BufCleanContext(&p->cinfo);
	free(p);
    }
    
    *pp = NULL;
}

void lsProxyCacheDel(lsSocksInfo *pcon, lsProxyInfo *r) {
    lsProxyInfo *p, *q;
    
    if (pcon == NULL || pcon->pri == NULL || r == NULL) return;
    
    p = NULL;
    if (pcon->pri == r) {
	q = pcon->pri;
	pcon->pri = q->next;
    } else {
	for (p = q = pcon->pri; q; p = q, q = q->next)
	    if (q == r) break;
	if (q == NULL) return;
    }

    if (pcon->cur == q) pcon->cur = pcon->pri;
    if (p) p->next = q->next;
    if (q->cinfo.fd == pcon->fd) q->cinfo.fd = S5InvalidIOHandle;
    S5BufCleanContext(&q->cinfo);
    free(q);

    return;
}

void lsProxyCacheClean(lsSocksInfo *pcon) {
    lsProxyInfo *p, *q;
    
    if (pcon == NULL)                    return;
    if (pcon->status == CON_ESTABLISHED) return;
    
    for (p = q = pcon->pri; p; ) {
	if (S5IOCheck(p->cinfo.fd) >= 0) {
	    q = p;
	    p = q->next;
	} else if (p == q) {
	    pcon->pri = p->next;
	    S5BufCleanContext(&p->cinfo);
	    if (pcon->cur == p) pcon->cur = NULL;
	    free(p);
	    
	    p = q = pcon->pri;
	} else {
	    q->next = p->next;

	    S5BufCleanContext(&p->cinfo);
	    if (pcon->cur == p) pcon->cur = NULL;
	    free(p);
	    
	    p = q->next;
	}
    }

    if (pcon->cur == NULL) pcon->cur = pcon->pri;
}

lsProxyInfo *lsProxyCacheFind(const lsSocksInfo *pcon, const S5NetAddr *na, u_char how, int checkport) {
    lsProxyInfo *p;
    
    if (!na) {
	if (pcon) return pcon->cur?pcon->cur:pcon->pri;
	return NULL;
    }

    if (na->sa.sa_family == AF_INET) {
	if (na->sin.sin_addr.s_addr == INVALIDADDR) {
	    return NULL;
	}
    
	for (p = pcon?pcon->pri:NULL; p; p = p->next) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Checking %d (%s:%d)", p->how, ADDRANDPORT(&p->prxyin));

	    if (p->how                            != how)                     continue;
	    if (p->prxyin.sin.sin_family          != na->sin.sin_family)      continue;
	    if (p->prxyin.sin.sin_addr.s_addr     != na->sin.sin_addr.s_addr) continue;
	    if (checkport && p->prxyin.sin.sin_port != na->sin.sin_port)      continue;
	    return p;
	}
    }
    
    return NULL;
}

lsProxyInfo *lsProxyCacheAdd(lsSocksInfo *pcon, const S5NetAddr *na, u_char how) {
    lsProxyInfo *pri;

    pri = (lsProxyInfo *)calloc(1, sizeof(lsProxyInfo));
    if (!pri) return NULL;

    pri->next = pcon->pri;
    pcon->pri = pri;
    pri->refcount = pri->next?pri->next->refcount:1;
    pri->how      = how;

    S5BufSetupContext(&pri->cinfo);
    if (pcon->cmd != SOCKS_UDP) pri->cinfo.fd = pcon->fd;

    if (na) {
	lsAddrCopy(&pri->prxyin, na, lsAddrSize(na));
    } else {
	pri->prxyin.sin.sin_family      = AF_INET;
	pri->prxyin.sin.sin_port        = INVALIDPORT;
	pri->prxyin.sin.sin_addr.s_addr = INVALIDADDR;
    }
    
    return pcon->cur = pri;
}

int lsConnectionDel(S5IOHandle fd) {
    lsSocksInfo *n, *p, *q;
    
    if (!initialized || (fd == S5InvalidIOHandle) || !FD_ISSET(fd, &sset)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(15), 0, "lsConnectionDel: No connection found ");
	return -1;
    }

    FD_CLR(fd, &sset);
    
    /* get rid of all the matches, really? it should have only one match otherwise */    
    /* lsConnectionFind() will have trouble 				           */
    p = NULL;
    if (lsConList->fd == fd) {
	q = lsConList;
	lsConList = q->next;
    } else {
	for (p = q = lsConList; q; p = q, q = q->next)
	    if (q->fd == fd) break;
	if (q == NULL) return -1;
    }

    if (lsLastCon == q) {
	lsLastCon = NULL;
	for (n = lsConList; n; n = n->next) {
	    if (n->cmd != SOCKS_CONNECT) continue;
	    if (n == q) continue;
	    if (!n->pri || (n->pri->how == DIRECT)) continue;
	    lsLastCon = n;
	    break;
	}
    }

    if (p) p->next = q->next;
    lsProxyCacheDelete(&q->pri, q->fd);
    free(q);

    return 0;
}

lsSocksInfo *lsConnectionAdd(S5IOHandle fd) {
    lsSocksInfo *p = NULL;
    
    if (!initialized) {
	FD_ZERO(&sset);
	initialized = 1;
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsConnectionAdd: fdset cleared");
    }

    if ((p = (lsSocksInfo *)calloc(1, sizeof(lsSocksInfo))) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsConnectionAdd: calloc - out of memory");
	return NULL;
    }

    p->status  = CON_NOTESTABLISHED;
    p->next    = lsConList;
    p->fd      = fd;
    p->afd     = S5InvalidIOHandle;
    lsConList  = p;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsConnectionAdd: fdset added fd #%d", fd);
    FD_SET(fd, &sset);
    return p;
}

int lsConnectionCached(S5IOHandle fd) {
    lsSocksInfo *p;

    if (!initialized || (fd == S5InvalidIOHandle) || !FD_ISSET(fd, &sset)) return 0;

    for (p = lsConList; p != NULL; p = p->next)
	if (p->fd == fd) return 1;

    FD_CLR(fd, &sset);
    return 0;
}

lsSocksInfo *lsConnectionFind(S5IOHandle fd) {
    lsSocksInfo *p;
    
    if (!initialized || (fd == S5InvalidIOHandle) || !FD_ISSET(fd, &sset)) return NULL;
    
    for (p = lsConList; p != NULL; p = p->next) {
	if (p->fd != fd) continue;
	return p;
    }

    FD_CLR(fd, &sset);
    return NULL;
}

