/* Copyright (c) 1995-1999 NEC USA, Inc.  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: protocol.c,v 1.82.4.6 1999/02/03 22:35:18 steve Exp $
 */

#include "socks5p.h"
#include "buffer.h"
#include "addr.h"
#include "protocol.h"
#include "hostname.h"
#include "msg.h"
#include "null.h"
#include "upwd.h"
#include "gss.h"
#include "log.h"


#ifndef PROTO_EXCHANGE_TIMEOUT
#define PROTO_EXCHANGE_TIMEOUT 15
#endif

#ifndef PROTO_RECVRESP_TIMEOUT
#define PROTO_RECVRESP_TIMEOUT 75
#endif

#ifndef PROTO_SENDRESP_TIMEOUT
#define PROTO_SENDRESP_TIMEOUT 10
#endif

#define PROTO_IOFLAGS S5_IOFLAGS_RESTART|S5_IOFLAGS_TIMED|S5_IOFLAGS_NBYTES

#define RSPSIZE4       8
#define ADDRLEN(x)     (HOSTLEN((x)) + PORTLEN((x)))
#define HDRSIZE4(x)    (int)(RSPSIZE4 + strlen((x)+RSPSIZE4) + 1)

int LIBPREFIX2(init)(const char *name) {
    static int done = 0;

    if (done) return 0;

    done = 1;

    if (S5LogDefaultHandle == NULL) {
    	S5LogStart(&S5LogDefaultHandle, -1, -1, "libsocks5");
    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "NEC NWSL %s library", SOCKS5_VERSION_NAME);
    }

    
    return 0;
}

char *lsEffUser() {
    static char EffUser[S5_USERNAME_SIZE];
    static int done = 0;
    struct passwd *pw;

    if (done) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GetUserName: name was cached...");
    } else {
	char *name;
	done = 1;

	if ((name = getlogin()) != NULL) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GetUserName: got name from getlogin...");
	    strncpy(EffUser, name, MIN(strlen(name), S5_USERNAME_SIZE));
	    EffUser[MIN(strlen(name), S5_USERNAME_SIZE)] = '\0';
	    return EffUser;
	}
	
	if ((pw = getpwuid(geteuid())) != NULL) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "GetUserName: got name from getpwuid...");
	    strncpy(EffUser, pw->pw_name, MIN(strlen(pw->pw_name), S5_USERNAME_SIZE));
	    EffUser[MIN(strlen(pw->pw_name), S5_USERNAME_SIZE)] = '\0';
	    return EffUser;
	}

	S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "GetUserName: couldn't get a name for the current user (uid: %d)", geteuid());
	strcpy(EffUser, "Unknown");
    }

    return EffUser;
}

int lsGetProtoAddrLenFromAddr(u_char version, const S5NetAddr *na) {
    switch (na->sa.sa_family) {
	case AF_INET:
	    return IPV4HDRSIZE;
	case AF_S5NAME:
	    if (version != SOCKS5_VERSION) return -1;
	    return NAMEHDRSIZE(na->sn.sn_name);
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    if (version != SOCKS5_VERSION) return -1;
	    return IPV6HDRSIZE;
#endif
	default:
	    return -1;
    }
}

int lsGetProtoAddrLenFromBuf(u_char version, const char *buf) {
    switch (version) {
	case SOCKS4_VERSION:
	    return sizeof(u_short) + sizeof(struct in_addr);
	case SOCKS5_VERSION:
	    break;
	default:
	    return -1;
    }

    switch (buf[RP_FLAGS]) {
	case SOCKS5_IPV4ADDR:
	    return sizeof(u_short) + sizeof(struct in_addr);
	case SOCKS5_HOSTNAME:
	    return sizeof(u_short) + (u_char)buf[RP_HOSTOFF] + 1;
#ifdef HAVE_NETINET6_IN6_H
	case SOCKS5_IPV6ADDR:
	    return sizeof(u_short) + sizeof(struct in_addr6);
#endif
	default:
	    return -1;
    }
}

int lsGetProtoAddr(u_char version, const char *buf, S5NetAddr *result) {
    if (version == SOCKS4_VERSION) {
	memset(&result->sin, 0, sizeof(ssi));
	result->sin.sin_family = AF_INET;
	memcpy(&result->sin.sin_addr, buf+SP_HOSTOFF, sizeof(struct in_addr));
	memcpy(&result->sin.sin_port, buf+SP_PORTOFF, sizeof(u_short));
	return 0;
    }

    switch (buf[RP_FLAGS]) {
	case SOCKS5_HOSTNAME:
	    memset(result, 0, sizeof(ssn));
	    result->sa.sa_family = AF_S5NAME;
	    memcpy(result->sn.sn_name,  buf+RP_HOSTOFF+1, (u_char)buf[RP_HOSTOFF]);
	    memcpy(&result->sn.sn_port, buf+RP_HOSTOFF+1+buf[RP_HOSTOFF], sizeof(u_short));
	    result->sn.sn_name[(int)(u_char)buf[RP_HOSTOFF]] = '\0';
	    return 0;
	case SOCKS5_IPV4ADDR:
	    memset(result, 0, sizeof(ssi));
	    result->sin.sin_family = AF_INET;
	    memcpy(&result->sin.sin_addr, buf+RP_HOSTOFF, sizeof(struct in_addr));
	    memcpy(&result->sin.sin_port, buf+RP_HOSTOFF + sizeof(struct in_addr), sizeof(u_short));
	    return 0;
#ifdef HAVE_NETINET6_IN6_H
	case SOCKS5_IPV6ADDR:
	    memset(result, 0, sizeof(ssi6));
#ifdef SIN6_LEN
	    result->sin6.sin6_len    = sizeof(struct sockaddr_in6);
#endif
	    result->sin6.sin6_family = AF_INET6;
	    memcpy(&result->sin6.sin6_addr, buf+RP_HOSTOFF, sizeof(struct in_addr6));
	    memcpy(&result->sin6.sin6_port, buf+RP_HOSTOFF + sizeof(struct in_addr6), sizeof(u_short));
	    return 0;
#endif
	default:
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "Unknown address type: %d");
	    return -1;
    }
}

int lsSetProtoAddr(u_char version, char *buf, const S5NetAddr *name) {
    if (version == SOCKS4_VERSION) {
	if (name->sa.sa_family != AF_INET) return -1;
	memcpy(buf+SP_HOSTOFF, &name->sin.sin_addr, sizeof(struct in_addr));
	memcpy(buf+SP_PORTOFF, &name->sin.sin_port, sizeof(u_short));
	return 0;
    }

    switch (name->sa.sa_family) {
	case AF_S5NAME:
	    buf[RP_FLAGS] = SOCKS5_HOSTNAME;
	    buf[RP_HOSTOFF] = strlen(name->sn.sn_name);
	    memcpy(buf+RP_HOSTOFF+1,                 name->sn.sn_name,  (u_char)buf[RP_HOSTOFF]);
	    memcpy(buf+RP_HOSTOFF+1+buf[RP_HOSTOFF], &name->sn.sn_port, sizeof(u_short));
	    return 0;
	case AF_INET:
	    buf[RP_FLAGS] = SOCKS5_IPV4ADDR;
	    memcpy(buf+RP_HOSTOFF,                          &name->sin.sin_addr, sizeof(struct in_addr));
	    memcpy(buf+RP_HOSTOFF + sizeof(struct in_addr), &name->sin.sin_port, sizeof(u_short));
	    return 0;
#ifdef HAVE_NETINET6_IN6_H
	case AF_INET6:
	    buf[RP_FLAGS] = SOCKS5_IPV6ADDR;
	    memcpy(buf+RP_HOSTOFF,                           &name->sin6.sin6_addr, sizeof(struct in_addr6));
	    memcpy(buf+RP_HOSTOFF + sizeof(struct in_addr6), &name->sin6.sin6_port, sizeof(u_short));
	    return 0;
#endif
	default:
	    return -1;
    }
}

static int lsSendProto(S5IOHandle fd, S5IOInfo *cinfo, const S5NetAddr *addr, u_char version, u_char cp, u_char rs, char *opt) {
    double timerm = PROTO_EXCHANGE_TIMEOUT;
    char buf[MAXHDRSIZE];
    int size;

    memset(buf, 0, sizeof(buf));

    if (version == SOCKS5_VERSION) {
	buf[RP_VERSION] = SOCKS5_VERSION;
	buf[RP_REPLY]   = cp;
	buf[RP_RESERVE] = rs;
	lsSetProtoAddr(version, buf, addr);
	size = HDRSIZE(buf);
    } else {
	buf[SP_VERSION] = version;
	buf[SP_COMMAND] = cp;
	memcpy(buf+SP_HOSTOFF, &addr->sin.sin_addr, sizeof(struct in_addr));
	memcpy(buf+SP_PORTOFF, &addr->sin.sin_port, sizeof(u_short));
	if (opt != NULL) {
	    strcpy(buf+SP_USEROFF, (char *)opt);
	    size = HDRSIZE4(buf);
	} else size = RSPSIZE4; 
    }
    
    if (S5IOSend(fd, cinfo, buf, size, 0, PROTO_IOFLAGS, &timerm) != size) return -1;
    return 0;
}

int lsSendRequest(S5IOHandle fd, S5IOInfo *cinfo, const S5NetAddr *dest, u_char version, u_char command, u_char flags, char *opt) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsSendRequest: dest is (%s:%d)", ADDRANDPORT(dest));

    if (lsSendProto(fd, cinfo, dest, version, command, flags, opt) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsSendRequest: network failure");
	return -1;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsSendRequest: request sent");
    return 0;
}

int lsReadRequest(S5IOHandle fd, S5IOInfo *cinfo, S5NetAddr *peer, u_char *version, u_char *cmd, u_char *flags) {
    double timerm = (double)PROTO_RECVRESP_TIMEOUT;
    char buf[MAXHDRSIZE];
    int len;

    if (S5IORecv(fd, cinfo, buf, RP_HOSTOFF+1, 0, PROTO_IOFLAGS, &timerm) != RP_HOSTOFF+1) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read request failed: %m");
        return -1;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(5), 0, "Socks5: Read initial protocol");

    *version  = (u_char)buf[RP_VERSION];

    if ((len = lsGetProtoAddrLenFromBuf(*version, buf)) < 0) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Unable to determine address length from buffer (bad address type?)");
        return -1;
    }

    /* read rest of address information                                      */
    if (S5IORecv(fd, cinfo, buf+RP_HOSTOFF+1, len-1, 0, PROTO_IOFLAGS, &timerm) != len-1) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Read request failed: %m");
        return -1;
    }

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Socks5: Read address part of protocol");

    if (lsGetProtoAddr(*version, buf, peer) < 0) {
        S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "Socks5: Invalid address passed from client");
        return -1;
    }

    *cmd  = (u_char)buf[RP_COMMAND];
    *flags = (u_char)buf[RP_RESERVE];

    return 0;
}

int lsSendResponse(S5IOHandle fd, S5IOInfo *cinfo, const S5NetAddr *reply, u_char version, u_char error, u_char flags, char *opt) {
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsSendResponse: reply is (%s:%d)", ADDRANDPORT(reply));

    if (lsSendProto(fd, cinfo, reply, (version == SOCKS4_VERSION)?0:version, error, flags, opt) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsSendResponse: network failure");
	return -1;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsSendResponse: response sent");
    return 0;
}

/* Read a socks5 response back from the server...                            */
int lsReadResponse(S5IOHandle fd, S5IOInfo *cinfo, S5NetAddr *peer, u_char version, u_char *res, u_char *flags) {
    double timerm = (double)PROTO_RECVRESP_TIMEOUT;
    char tmpbuf[MAXHDRSIZE];
    int len, hdlen;

    switch (version) {
	case SOCKS4_VERSION:
	    hdlen = SP_PORTOFF;
	    break;
	case SOCKS5_VERSION:
	    hdlen = RP_HOSTOFF+1;
	    break;
	default:
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_WARNING, 0, "lsReadResponse: Invalid version: %d", version);
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
    }
	
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsReadResponse: Reading response (version: %d)...", version);

    if (S5IORecv(fd, cinfo, tmpbuf, hdlen, 0, PROTO_IOFLAGS, &timerm) != hdlen) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsReadResponse: read: %m");
	SETSOCKETERROR(ECONNREFUSED);
	return -1;
    }

    if ((len = lsGetProtoAddrLenFromBuf(version, tmpbuf)) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsReadResponse: Invalid address type: %d", tmpbuf[RP_HOSTOFF]);
	SETSOCKETERROR(ECONNREFUSED);
	return -1;
    }
    
    /* If this was a socks5 request, we read one too many before so that the */
    /* address length thing would work, so we take it off here...            */
    if (version == SOCKS5_VERSION) {
	len--;
    }

    if (S5IORecv(fd, cinfo, tmpbuf+hdlen, len, 0, PROTO_IOFLAGS, &timerm) != len) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsReadResponse: Address read: %m");
	SETSOCKETERROR(ECONNREFUSED);
	return -1;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsReadResponse: Server response read");
    
    if (lsGetProtoAddr(version, tmpbuf, peer) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsReadResponse: Bad address in Response");
	SETSOCKETERROR(ECONNREFUSED);
	return -1;
    }
    
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsReadResponse: Response Address: %d:%s:%d", peer->sa.sa_family, ADDRANDPORT(peer));
	
    if (version == SOCKS4_VERSION) {
	switch ((int)(*res = (u_char)tmpbuf[SP_COMMAND])) {
	    case SOCKS_NOERR:
	    case SOCKS_RESULT:
		return 0;
	    case SOCKS_FAIL:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks4 Server: permission denied");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS_NO_IDENTD:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks4 Server: couldn't contact your identd");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS_BAD_ID:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks4 Server: user denied");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    default:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks4 Server: Unknown reply code: %d", (int)(u_char)tmpbuf[SP_COMMAND]);
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	}
    } else {
	*flags = (u_char)tmpbuf[RP_RESERVE];
	switch ((int)(*res = tmpbuf[RP_REPLY])) {
	    case SOCKS5_TTLEXP:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Server timed out");
		SETSOCKETERROR(ETIMEDOUT);
		return -1;
	    case SOCKS5_BADCMND:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Bad Command");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS5_NETUNREACH:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Net unreachable");
		SETSOCKETERROR(ENETUNREACH);
		return -1;
	    case SOCKS5_HOSTUNREACH:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Host unreachable");
		SETSOCKETERROR(EHOSTUNREACH);
		return -1;
	    case SOCKS5_BADADDR:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Bad Address type");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS5_CONNREF:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Connection failed");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS5_FAIL:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Server failure");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS5_AUTHORIZE:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Authorization failed");
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	    case SOCKS5_RESULT:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsReadResponse: Received a good status reply");
		return 0;
	    default:
		S5LogUpdate(S5LogDefaultHandle, S5_LOG_ERROR, 0, "Socks5 Server: Unknown reply code: %d", *res);
		SETSOCKETERROR(ECONNREFUSED);
		return -1;
	}
    }
}

/* perform the authentication and  the authsend "command" message to */
/* the proxy server                                                  */
int lsProtoExchg(S5IOHandle fd, S5IOInfo *cinfo, const S5NetAddr *dest, char *effuser, u_char version, u_char command, u_char flags) {
    char tmpbuf[MAXHDRSIZE], *tmp = tmpbuf+2, auth = AUTH_FAIL;
    double timerm = PROTO_EXCHANGE_TIMEOUT;

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsProtoExchg: dest is (%s:%d)", ADDRANDPORT(dest));

    memset(tmpbuf, 0, sizeof(tmpbuf));
    S5BufSetupContext(cinfo);
    cinfo->fd = fd;

    if (version == SOCKS4_VERSION) {    
	if (lsSendRequest(fd, cinfo, dest, version, command, flags, effuser) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsProtoExchg: write: %m");
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
	}
	
	return 0;
    } else {
	*tmp++ = AUTH_NONE;
	*tmp++ = AUTH_PASSWD;
#ifdef HAVE_LIBGSSAPI_KRB5
	*tmp++ = AUTH_GSSAPI;
#endif
	tmpbuf[1] = tmp - (tmpbuf + 2);
	tmpbuf[RP_VERSION] = SOCKS5_VERSION;
    
	if (S5IOSend(fd, cinfo, tmpbuf, 2+(u_int)tmpbuf[1], 0, PROTO_IOFLAGS, &timerm) != 2+(int)(u_int)tmpbuf[1]) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsProtoExchg: write: %m");
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
	}
    
	if (S5IORecv(fd, cinfo, tmpbuf, 2, 0, PROTO_IOFLAGS, &timerm) != 2) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsProtoExchg: read: %m");
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
	}
    
	if (tmpbuf[0] != SOCKS5_VERSION) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsProtoExchg: Cannot Speak Socks5 protocol to Socks4 Server.");
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
	}
    
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsProtoExchg: server asked us to do method #%d", (int)tmpbuf[1]);

	switch ((u_char)tmpbuf[1]) {
	    case AUTH_NONE:   auth = lsNullCliAuth  (fd, &cinfo->auth, effuser); break;
	    case AUTH_PASSWD: auth = lsPasswdCliAuth(fd, &cinfo->auth, effuser); break;
	    case AUTH_GSSAPI: auth = lsGssapiCliAuth(fd, &cinfo->auth, effuser); break;
	}
    
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsProtoExchg: authentication done: %s", (auth == AUTH_OK)?"ok":"failed");

	if (auth != AUTH_OK) {
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
	}

	if (lsSendRequest(fd, cinfo, dest, version, command, flags, NULL) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(0), 0, "lsProtoExchg: write: %m");
	    SETSOCKETERROR(ECONNREFUSED);
	    return -1;
	}
    
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "lsProtoExchg: done");
	return 0;
    }
}

