#!/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)