#!/usr/bin/env python

#
# identd.py 
# By A.M. Kuchling (amk@amk.ca)
#
# This program implements the ident protocol, used to authenticate
# user names on IRC.  I wrote this script when I needed identd to
# access IRC from a Red Hat Linux machine, but couldn't find identd in
# any of the RPMs I tried.  It uses the /proc filesystem, and so will 
# only work on Linux.
#
# This script is designed to be called from inetd; add the following
# line to inetd.conf:
#
# auth   stream  tcp     nowait    nobody    /usr/sbin/identd.py identd.py -l -e -o
#
# This means the script need simply use stdin and stdout, and doesn't
# have to implement a network server from scratch, which involves 
# setting up a socket, listening to it and waiting for incoming
# connections.  It's instructive to compare this program with
# fingerd.py, which uses SocketServer.py.  This program contains 32
# lines of code, while fingerd.py contains only 14, yet is a complete
# standalone server that doesn't need inetd's help.
#

import string, sys, traceback

# Read a single line of input from standard input
input=sys.stdin.readline()
input=string.strip(input)

# The input looks like 1234,789 where 1234 and 789 are the port
# numbers of the sockets on the foreign and the local machine.  The
# ident daemon can then look up the user who owns that socket and
# return the user's ID. (These days, when anyone can be running their
# own Linux box, it's not much of a protection, but many MUDs and IRC
# servers still use it.)

# Enclose everything in a try...except block, since this server must
# produce some output no matter what.

try:
    # Write the input to a log file, so I can track what requests 
    # have been made.  (/scratch is a directory that gets erased on
    # every bootup.)
    f=open('/scratch/identd.calls', 'a') 
    f.write(input+'\n')
    f.close()

    # Break the input line apart to get the server and client ports,
    # and remove any whitespace.
    L=string.split(input, ',')
    L=map(string.strip, L)
    server_port, client_port = string.atoi(L[0]), string.atoi(L[1])

    # Open /proc/net/tcp, and scan each line.  Lines in /proc/net/tcp
    # look like:
    # 79: 00000000:004F 00000000:0000 0A 00000000:00000000 00:FFBE3990 00000000 0 0 1287
    #              ^==local port ^==remote port                                   ^=numeric user ID

    f=open('/proc/net/tcp', 'r')
    f.readline()			# The first line is just headers

    while (1):
	# Read a single line
	L=f.readline()

	# If we reach the end of the file, exit this loop
	if L=="": break

	# Break the line into a list, splitting at the whitespace.
	L=string.split(L) 

	# Break the local and remote addresses into the IP address 
	# and the port number.
	[localaddr, localport] = string.split(L[1], ':')
	[remoteaddr, remoteport] = string.split(L[2], ':')    

	# Convert the hexadecimal port numbers into integers.
	localport=string.atoi(localport, 16) 
	remoteport=string.atoi(remoteport, 16) 

	# If they match the requested port numbers, get the
	# corresponding user name. 
	if (localport==server_port and
	    remoteport==client_port):
	    import pwd
            uid=string.atoi(L[7])
	    userid=pwd.getpwuid(uid)[0]
	    # Output a string containing the port numbers and the user ID.
	    sys.stdout.write('%s,%s:USERID:UNIX %s\r\n' % 
			     (server_port, client_port, userid) )
	    sys.exit(0)

    # No match for the local and remote port numbers was found, so 
    # we'll output an error
    sys.stdout.write(input+':ERROR:NO-USER\r\n')

# The sys.exit(0) call raises the SystemExit exception.  We don't want
# to do anything when sys.exit() is called, so its handler is simply a
# "pass" statement (which does nothing).
except SystemExit: pass

# All other exceptions will be caught by this handler.  An error
# response has to be returned to the client.
except:		
    sys.stdout.write(input+':ERROR:UNKNOWN-ERROR'+ CRLF)