/* $Id: identd.c,v 0.1 1994/05/22 21:41:23 qpliu Exp $
 * identd.c by qpliu 1994 -- this program is in the public domain.
 * RFC 1413 ident server using the linux /proc fs.
 * This probably could be done much more simply with perl.
 */

/* Aug. 95 - modded by VectorX to accept a file of no more
   then 14 characters on one line, called .fakeid, in the 
   users home directory, which will be substituted for the
   actual identity of the user being looked up. Re-define 
   USERIDFILE and recompile to change he default lookup.

   To install just compile, rename /usr/sbin/in.identd to a 
    backup, and replace w/this file.
*/


#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <ctype.h>
#include <netinet/in.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* TIMEOUT is the number of seconds to wait before closing the connection if
 * the client doesn't provide the port pairs.
 */
#define	TIMEOUT	120

/* PROCINFO_PATH is where the /proc tcp information is found */
#define	PROCINFO_PATH	"/proc/net/tcp"

/* USERIDFILE is the file that can spoof the identd information */
#define USERIDFILE	"/.fakeid"

/* PROCINFO_BUFFER_SIZE must be bigger than 80 */
#define	PROCINFO_BUFFER_SIZE	2048
#define	SOCKET_BUFFER_SIZE	100

unsigned short lport = 0, rport = 0;

#define	INVALID_PORT	0
#define	NO_USER		1
#define	HIDDEN_USER	2
#define	UNKNOWN_ERROR	3

char *errors[] = {
    "INVALID-PORT", "NO-USER", "HIDDEN-USER", "UNKNOWN-ERROR"
};

void
error (int c)
{
    char str[80];

    sprintf (str, "%u , %u : ERROR : %s\r\n", lport, rport, errors[c]);
    write (1, str, strlen (str));
    exit (0);
}

void
main ()
{
    struct sockaddr_in sin;
    unsigned long here, there;
    int len = sizeof (sin);
    struct fd_set fdset;
    struct timeval timeout;
    char buffer[PROCINFO_BUFFER_SIZE > SOCKET_BUFFER_SIZE ?
		PROCINFO_BUFFER_SIZE : SOCKET_BUFFER_SIZE];
        char *spoofdir;
	char *spoofid;
	char spoofbuf[14];

    int index;
    enum {
	have_neither, have_lport_start, have_lport,
	have_comma, have_rport_start, have_both
    } read_state;
    int fd, spooffd, spooflen;

    if (getsockname (0, (struct sockaddr *) &sin, &len) < 0) exit (0);
    here = sin.sin_addr.s_addr;
    if (getpeername (0, (struct sockaddr *) &sin, &len) < 0) exit (0);
    there = sin.sin_addr.s_addr;

    /* prepare to read ports */
    FD_ZERO (&fdset);
    FD_SET (0, &fdset);
    timeout.tv_sec = TIMEOUT;
    timeout.tv_usec = 0;

    /* read ports from stdin */
    read_state = have_neither;
    index = 0;
    while (read_state != have_both && 1 == select (1, &fdset, NULL, NULL, &timeout)) {
	int i, newindex;

	len = read (0, buffer + index, SOCKET_BUFFER_SIZE - index);
	if (len <= 0)
	    exit (0);

	i = index;
	newindex = 0;

	switch (read_state) {
	case have_neither:
	    while (i < len + index) {
		if (isdigit (buffer[i])) {
		    read_state = have_lport_start;
		    break;
		}
		if (!isspace (buffer[i]))
		    error (INVALID_PORT);
		i++;
	    }
	    newindex = i;
	    /* fallthrough */
	case have_lport_start:
	    while (i < len + index) {
		if (!isdigit (buffer[i])) {
		    newindex = atoi (buffer + newindex);
		    if (newindex != newindex&0xffff && newindex == 0)
			error (INVALID_PORT);
		    lport = newindex;
		    read_state = have_lport;
		    newindex = i;
		    break;
		}
		i++;
	    }
	    /* fallthrough */
	case have_lport:
	    while (i < len + index) {
		if (',' == buffer[i]) {
		    read_state = have_comma;
		    ++i;
		    break;
		}
		if (!isspace (buffer[i]))
		    error (INVALID_PORT);
		i++;
	    }
	    newindex = i;
	    /* fallthrough */
	case have_comma:
	    while (i < len + index) {
		if (isdigit (buffer[i])) {
		    read_state = have_rport_start;
		    break;
		}
		if (!isspace (buffer[i]))
		    error (INVALID_PORT);
		i++;
	    }
	    newindex = i;
	    /* fallthrough */
	case have_rport_start:
	    while (i < len + index) {
		if (!isdigit (buffer[i])) {
		    newindex = atoi (buffer + newindex);
		    if (newindex != newindex&0xffff && newindex == 0)
			error (INVALID_PORT);
		    rport = newindex;
		    read_state = have_both;
		    newindex = i;
		    break;
		}
		i++;
	    }
	    /* fallthrough */
	}
	index = i - newindex;
	memmove (buffer, buffer + newindex, index);
	FD_SET (0, &fdset);
    }
    if (read_state != have_both) exit (0);

    /* get info from /proc/net/tcp */
    fd = open (PROCINFO_PATH, O_RDONLY);
    if (fd < 0)
	error (UNKNOWN_ERROR);
    index = 0;

    /* linear search for matching addresses and ports */
    while (0 < (len = read (fd, buffer + index, PROCINFO_BUFFER_SIZE - index))) {
	int i, newindex;
	unsigned long l_here, l_there;
	unsigned short l_rport, l_lport;
	int uid;

	newindex = i = 0;
	while (i < len + index) {
	    if (sscanf (buffer + i, "%*d: %lx:%hx %lx:%hx %*x %*x:%*x %*x:%*x %*x %d",
			&l_here, &l_lport, &l_there, &l_rport, &uid) == 5) {
		/* /proc/net/tcp displays addresses in net order and
		 * ports in host order--how convenient.
		 */
		if (here == l_here && lport == l_lport && there == l_there && rport == l_rport) {
		    struct passwd *p = getpwuid (uid);

		    if (NULL == p) error (UNKNOWN_ERROR);
			    
		    spoofdir = p->pw_dir;
		    strcat(spoofdir, USERIDFILE);
	            spooffd = open(spoofdir, O_RDONLY);
	             if (spooffd < 0) {
		    	sprintf (buffer, "%u , %u : USERID : UNIX : %s\r\n",
			     lport, rport, p->pw_name);
		    }
			else
		    {	
			spooflen = read(spooffd, spoofbuf, sizeof(spoofbuf));
			spoofid = strtok(spoofbuf, "\r\n");
		    	sprintf (buffer, "%u , %u : USERID : UNIX : %s\r\n",
			     lport, rport, spoofid);
		    }
		    write (1, buffer, strlen (buffer));
		    exit (0);
		}
	    }
	    newindex = i;
	    while (buffer[i++] != '\n' && i < len + index)
		;
	}
	index = PROCINFO_BUFFER_SIZE - newindex;
	memmove (buffer, buffer + newindex, index);
    }
    error (NO_USER);
}

/* $Log: identd.c,v $
 * Revision 0.1  1994/05/22  21:41:23  qpliu
 * Seems to work.
 *
 */
