/* 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: sident.c,v 1.19.2.1 1997/08/25 02:32:43 jduan Exp $
 */

#include "socks5p.h"
#include "threads.h"
#include "daemon.h"
#include "addr.h"
#include "log.h"

#ifdef HAVE_LIBIDENT
#include <ident.h>
#endif

#ifndef IDENTTIMEOUT
#define IDENTTIMEOUT 15
#endif

#ifdef HAVE_LIBIDENT
static inline void filelock(S5IOHandle fd, int on) {
#if defined(HAVE_FLOCK) && !defined(F_SETLKW)
    flock(fd, on?LOCK_EX:LOCK_UN);
#else
    struct flock fl;
    
    fl.l_type   = on?F_WRLCK:F_UNLCK;
    fl.l_whence = SEEK_SET;
    fl.l_start  = 0;
    fl.l_len    = 0;

    fcntl(fd, F_SETLKW, &fl);
#endif
}

static inline S5IOHandle openidtfile(void) {
    int flags = O_RDWR | O_CREAT;
    S5IOHandle fd;
    struct stat sbuf;
    static char *myfile = NULL;
    
    if (!myfile) {
    	MUTEX_LOCK(env_mutex);
    	myfile = getenv("SOCKS5_IDENTFILE");
    	myfile = myfile?strdup(myfile):strdup(SRVIDT_FILE);
    }

    if (lstat(myfile, &sbuf) || (S_ISLNK(sbuf.st_mode) && geteuid() != sbuf.st_uid)) flags |= O_EXCL;
    fd = open(myfile, flags, 0644);
    MUTEX_UNLOCK(env_mutex);

    if (fd == S5InvalidIOHandle) return fd;
    filelock(fd, 1);
    return fd;
}
#endif

int IdentQuery(S5IOHandle fd, char *name) {
#ifndef HAVE_LIBIDENT
    return 0;
#else
    IDENT *idp;
    char *ev;
    int rval;

    IFTHREADED(static MUTEX_T idt_mutex = MUTEX_INITIALIZER;)

    IFTHREADED(MUTEX_SETUP(idt_mutex);)

    MUTEX_LOCK(env_mutex);
    ev = getenv("SOCKS5_NOIDENT");
    MUTEX_UNLOCK(env_mutex);
    
    if (ev) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Ident: No Ident lookup (by request)");
	*name = '\0';
	return -1;
    } 

    MUTEX_LOCK(idt_mutex);

    if ((idp = ident_lookup(fd, IDENTTIMEOUT)) == NULL) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Ident: Ident lookup failed: %m");
	*name = '\0';
	rval = -1;
    } else {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Ident: Ident lookup name was: %s", idp->identifier);
	strncpy(name, idp->identifier, MIN(strlen(idp->identifier)+1, S5_USERNAME_SIZE));
	if (strlen(idp->identifier)+1 > S5_USERNAME_SIZE) name[S5_USERNAME_SIZE-1] = '\0';
	ident_free(idp);
	rval = 0;
    }
    
    MUTEX_UNLOCK(idt_mutex);
    return rval;
#endif
}
    
void InitIdentEntry(char *idtentry) {
    idtentry[0] = '\0';
}

void RemoveIdentEntry(char *idtentry) {
#ifdef HAVE_LIBIDENT
    char *fbuf = NULL, *offset;
    struct stat sb;
    S5IOHandle fd;

    if (*idtentry == '\0') return;
    if ((fd = openidtfile()) == S5InvalidIOHandle) return;

    if (fstat(fd, &sb) < 0) goto end;
    if (!(fbuf = (char *)malloc((sb.st_size+1)*sizeof(char)))) goto end;
    if (READFILE(fd, fbuf, sb.st_size) < 0) goto end;
    fbuf[sb.st_size] = '\0';
    
    if ((offset = strstr(fbuf, idtentry)) == NULL) goto end;
    lseek(fd, offset-fbuf, 0);
    WRITEFILE(fd, offset + strlen(idtentry), (sb.st_size - (offset-fbuf) - strlen(idtentry)));

    /* XXX For now, if you don't have ftruncated, you loose.  BSD & SVR4 */
    /* both do, so there shouldn't be too many losers.                   */
    ftruncate(fd, sb.st_size - strlen(idtentry));
    
  end:
    if (fbuf) free(fbuf);
    if (fd >= 0) close(fd);
#endif
}

void MakeIdentEntry(S5IOHandle in, S5IOHandle out, S5LinkInfo *linkinfo, char *idtentry) {
#ifdef HAVE_LIBIDENT
    char *user, identname[S5_USERNAME_SIZE];
    S5NetAddr tmp, *dst;
    S5IOHandle fd;
    int len;

    if (*idtentry != '\0') {
	RemoveIdentEntry(idtentry);
    }

    if (linkinfo->peerAuth == AUTH_NONE || IdentQuery(in, identname) < 0) {
	user = linkinfo->srcUser;
    } else {
	user = identname;
    }

    if (!strlen(user)) {
	S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Ident: Not recording ident info for empty username");
	return;
    }

    if (linkinfo->nextVersion) {
	dst = &linkinfo->sckAddr;
    } else {
	dst = &linkinfo->dstAddr;
    }

    len = sizeof(S5NetAddr);
    memset(&tmp, 0, len);
    getsockname(out, &tmp.sa, &len);

    sprintf(idtentry, "%s,%d,", ADDRANDPORT(dst));
    sprintf(idtentry+strlen(idtentry), "%s,%d,", ADDRANDPORT(&tmp));
    sprintf(idtentry+strlen(idtentry), "%s\n",   user);

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Ident: Recording ident info for user: %s", user);

    if ((fd = openidtfile()) == S5InvalidIOHandle) return;
    WRITEFILE(fd, idtentry, strlen(idtentry));
    close(fd);

    S5LogUpdate(S5LogDefaultHandle, S5_LOG_DEBUG(10), 0, "Ident: Ident info recorded");
#endif
}

