/* 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: upwd.c,v 1.38.4.7 1999/03/29 23:03:36 jyou Exp $
 */

#include "socks5p.h"
#include "buffer.h"
#include "threads.h"
#include "confutil.h"
#include "addr.h"
#include "msg.h"
#include "log.h"

#ifdef USE_PASSWD
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#endif

#ifdef USE_SHADOW_PASSWD
#include <shadow.h>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#endif

#define RESP_OFFSET    1
#define ULEN_OFFSET(x) 1
#define USER_OFFSET(x) 2
#define PLEN_OFFSET(x) USER_OFFSET((x))+(x)[ULEN_OFFSET((x))]
#define PSWD_OFFSET(x) PLEN_OFFSET((x))+1

#define SETVERS(x, y) ((x)[0] = (y))
#define SETULEN(x, y) ((x)[ULEN_OFFSET(x)] = ((y)?(u_char)strlen((y)):0))
#define SETPLEN(x, y) ((x)[PLEN_OFFSET(x)] = ((y)?(u_char)strlen((y)):0))
#define SETUSER(x, y) if ((y)) strcpy((x)+USER_OFFSET((x)), (y));
#define SETPSWD(x, y) if ((y)) strcpy((x)+PSWD_OFFSET((x)), (y));
#define GETRESP(x)    ((u_char)(x[RESP_OFFSET]))

#define PASSWDHDRSIZE(x) PSWD_OFFSET((x))+(x)[PLEN_OFFSET((x))]
#define PASSWDRSPSIZE    2

#ifndef UPWD_TIMEOUT
#define UPWD_TIMEOUT 15
#endif

#define UPWD_IOFLAGS S5_IOFLAGS_RESTART|S5_IOFLAGS_TIMED|S5_IOFLAGS_NBYTES

char *lsUpwdDefaultFilename = SRVPWD_FILE;
char *lsUpwdDefaultEnvname  = "SOCKS5_PWDFILE";

IFTHREADED(extern MUTEX_T env_mutex;)

static int GetString(S5IOHandle fd, char *buf, double *timerm) {
    u_char len;

    buf[0] = '\0';
    if (S5IORecv(fd, NULL, (char *)&len, 1, 0, UPWD_IOFLAGS, timerm) != 1) return -1;
    if (len == 0) return 0;

    if (S5IORecv(fd, NULL, buf, len, 0, UPWD_IOFLAGS, timerm) != len) return -1;
    buf[len] = '\0';
    return len;
}

int lsPasswdCliAuth(S5IOHandle fd, S5AuthInfo *ainfo, char *user) {
    double timerm = (double)UPWD_TIMEOUT;
    char buf[2*255+3], *pwd;
    char *tmp;

    MUTEX_LOCK(env_mutex);
    if ((tmp = getenv("SOCKS5_USER"))) user = tmp;
    pwd = getenv("SOCKS5_PASSWD");
    MUTEX_UNLOCK(env_mutex);

    SETVERS(buf, 1);
    SETULEN(buf, user);
    SETUSER(buf, user);
    SETPLEN(buf, pwd);
    SETPSWD(buf, pwd);

    if (S5IOSend(fd, NULL, buf, PASSWDHDRSIZE(buf), 0, UPWD_IOFLAGS, &timerm) != PASSWDHDRSIZE(buf)) return AUTH_FAIL;
    if (S5IORecv(fd, NULL, buf, PASSWDRSPSIZE,      0, UPWD_IOFLAGS, &timerm) != PASSWDRSPSIZE)      return AUTH_FAIL;
    return (GETRESP(buf) == 0x00)?AUTH_OK:AUTH_FAIL;
}

int lsPasswdSrvAuth(S5IOHandle sd, S5AuthInfo *ainfo, char *name) {
    char resp[] = { 0x05, (char)0x01 }, ver, passwd[256];
    double timerm = UPWD_TIMEOUT;
    int rval = AUTH_FAIL;
    
    IFTHREADED(static MUTEX_T upwd_mutex = MUTEX_INITIALIZER;)
    MUTEX_LOCK(upwd_mutex);

    if (S5IORecv(sd, NULL, &ver, 1, 0, UPWD_IOFLAGS, &timerm) != 1) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Failed to receive version number");
	goto done;
    }

    if ((u_char)ver != 0x01) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Incorrect version number: %d", ver);
	goto done;
    }
    
    if (GetString(sd, name, &timerm) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Failed to get valid username");
	goto done;
    }
    
    if (GetString(sd, passwd, &timerm) < 0) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Failed to get valid password");
	goto done;
    }

#ifdef  USE_PASSWD
    {
	struct passwd *pw = getpwnam(name);
	
	if (pw == NULL || pw->pw_uid == 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: No Password entry for user: %s", name);
	    goto done;
	}
	
	if (pw->pw_passwd && strcmp(crypt(passwd, pw->pw_passwd), pw->pw_passwd)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Password incorrect for user: %s", name);
	    goto done;
	}
    }
#else
#ifdef  USE_SHADOW_PASSWD
    {
	struct spwd *pw = getspnam(name);

	if (pw == NULL) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: No Shadow Password entry for user: %s", name);
	    goto done;
	}

	if (pw->sp_pwdp && strcmp(crypt(passwd, pw->sp_pwdp),pw->sp_pwdp)) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Shadow Password incorrect for user: %s", name);
	    goto done;
	}
    }
#else
    {
	char c, *myfile, *tmp, *tmpname, *tmppasswd;
	static char *buf = NULL;
	struct stat sb;
	S5IOHandle fd;

	if (buf) goto findname;

	MUTEX_LOCK(env_mutex);
	myfile = getenv(lsUpwdDefaultEnvname);
	myfile = myfile?myfile:lsUpwdDefaultFilename;
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Password file is %s", myfile);
	fd = open(myfile, O_RDONLY);
	MUTEX_UNLOCK(env_mutex);
	
	/* No passwd file probably means you don't car about having passwds  */
	if (fd == S5InvalidIOHandle) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Error opening password file: %m");
	    goto done;
	}

	/* Fstat to see how big a buffer we should allocated...what would    */
	/* cause this to fail?  Either way, if it does, bail.                */
	if (fstat(fd, &sb) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Error stating open password file: %m");
	    REAL(close)(fd);
	    goto done;
	}

	/* If we can't allocate a buffer as big as the password file itself, */
	/* bail...                                                           */
	if ((buf = (char *)malloc((sb.st_size+1)*sizeof(char))) == NULL) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Malloc failed for password file");
	    REAL(close)(fd);
	    goto done;
	}

	/* Read in the contents of the file...                               */
	if (READFILE(fd, buf, sb.st_size) < 0) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Error reading open password file: %m");
	    free(buf); buf = NULL;
	    REAL(close)(fd);
	    goto done;
	}

	buf[sb.st_size] = '\0';
	REAL(close)(fd);

      findname:
	for (tmp = buf ; tmp; tmp = strchr(tmp, '\n'), tmp = (tmp && *tmp)?tmp+1:NULL) {
	    tmpname = tmp;
	    while ((*tmpname != '\n') && isspace((unsigned char)*tmpname)) tmpname++;
	    if (*tmpname == '\n') continue;

	    tmppasswd = tmpname;
	    while ((*tmppasswd != '\n') && !isspace((unsigned char)*tmppasswd)) tmppasswd++;
	    if (*tmppasswd == '\n') continue;
	    c = *tmppasswd;
	    *tmppasswd = '\0';
	    if (strcmp(tmpname, name) != 0) {
		*tmppasswd = c;
		continue;
	    }

	    *tmppasswd = c;
	    while ((*tmppasswd != '\n') && isspace((unsigned char)*tmppasswd)) tmppasswd++;
	    if (*tmppasswd == '\n') continue;

	    tmpname = tmppasswd;
	    while (!isspace((unsigned char)*tmpname)) tmpname++;
	    c = *tmpname;
	    *tmpname = '\0';
	    if (strcmp(tmppasswd, passwd) != 0) {
		*tmpname = c;
		continue;
	    }

	    *tmpname = c;
	    break;
	}

	if (!tmp) {
	    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: User: %s no match in password file", name);
	    goto done;
	}
    }
#endif	/* USE_SHADOW_PASSWD */
#endif  /* USE_PASSWD        */
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: successful: user is %s", name);
    rval = AUTH_OK;
    
  done:
    MUTEX_UNLOCK(upwd_mutex);
    if (rval == AUTH_OK) resp[1] = 0x00;
    memset(passwd, 0, sizeof(passwd));
    
    if (S5IOSend(sd, NULL, resp, sizeof(resp), 0, UPWD_IOFLAGS, &timerm) != sizeof(resp)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "UPWD: Failed to send response to client");
	return AUTH_FAIL;
    }
    
    return rval;
}

