1 # -*- test-case-name: twisted.test.test_ident -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Ident protocol implementation.
8 @author: Jean-Paul Calderone
11 from __future__ import generators
15 from twisted.internet import defer
16 from twisted.protocols import basic
17 from twisted.python import log, failure
20 _MAX_PORT = 2 ** 16 - 1
22 class IdentError(Exception):
24 Can't determine connection owner; reason unknown.
27 identDescription = 'UNKNOWN-ERROR'
30 return self.identDescription
33 class NoUser(IdentError):
35 The connection specified by the port pair is not currently in use or
36 currently not owned by an identifiable entity.
38 identDescription = 'NO-USER'
41 class InvalidPort(IdentError):
43 Either the local or foreign port was improperly specified. This should
44 be returned if either or both of the port ids were out of range (TCP
45 port numbers are from 1-65535), negative integers, reals or in any
46 fashion not recognized as a non-negative integer.
48 identDescription = 'INVALID-PORT'
51 class HiddenUser(IdentError):
53 The server was able to identify the user of this port, but the
54 information was not returned at the request of the user.
56 identDescription = 'HIDDEN-USER'
59 class IdentServer(basic.LineOnlyReceiver):
61 The Identification Protocol (a.k.a., "ident", a.k.a., "the Ident
62 Protocol") provides a means to determine the identity of a user of a
63 particular TCP connection. Given a TCP port number pair, it returns a
64 character string which identifies the owner of that connection on the
67 Server authors should subclass this class and override the lookup method.
68 The default implementation returns an UNKNOWN-ERROR response for every
72 def lineReceived(self, line):
73 parts = line.split(',')
78 portOnServer, portOnClient = map(int, parts)
82 if _MIN_PORT <= portOnServer <= _MAX_PORT and _MIN_PORT <= portOnClient <= _MAX_PORT:
83 self.validQuery(portOnServer, portOnClient)
85 self._ebLookup(failure.Failure(InvalidPort()), portOnServer, portOnClient)
87 def invalidQuery(self):
88 self.transport.loseConnection()
91 def validQuery(self, portOnServer, portOnClient):
93 Called when a valid query is received to look up and deliver the
96 @param portOnServer: The server port from the query.
97 @param portOnClient: The client port from the query.
99 serverAddr = self.transport.getHost().host, portOnServer
100 clientAddr = self.transport.getPeer().host, portOnClient
101 defer.maybeDeferred(self.lookup, serverAddr, clientAddr
102 ).addCallback(self._cbLookup, portOnServer, portOnClient
103 ).addErrback(self._ebLookup, portOnServer, portOnClient
107 def _cbLookup(self, (sysName, userId), sport, cport):
108 self.sendLine('%d, %d : USERID : %s : %s' % (sport, cport, sysName, userId))
110 def _ebLookup(self, failure, sport, cport):
111 if failure.check(IdentError):
112 self.sendLine('%d, %d : ERROR : %s' % (sport, cport, failure.value))
115 self.sendLine('%d, %d : ERROR : %s' % (sport, cport, IdentError(failure.value)))
117 def lookup(self, serverAddress, clientAddress):
118 """Lookup user information about the specified address pair.
120 Return value should be a two-tuple of system name and username.
121 Acceptable values for the system name may be found online at::
123 U{http://www.iana.org/assignments/operating-system-names}
125 This method may also raise any IdentError subclass (or IdentError
126 itself) to indicate user information will not be provided for the
129 A Deferred may also be returned.
131 @param serverAddress: A two-tuple representing the server endpoint
132 of the address being queried. The first element is a string holding
133 a dotted-quad IP address. The second element is an integer
134 representing the port.
136 @param clientAddress: Like L{serverAddress}, but represents the
137 client endpoint of the address being queried.
141 class ProcServerMixin:
142 """Implements lookup() to grab entries for responses from /proc/net/tcp
145 SYSTEM_NAME = 'LINUX'
148 from pwd import getpwuid
149 def getUsername(self, uid, getpwuid=getpwuid):
150 return getpwuid(uid)[0]
153 def getUsername(self, uid):
157 f = file('/proc/net/tcp')
162 def dottedQuadFromHexString(self, hexstr):
163 return '.'.join(map(str, struct.unpack('4B', struct.pack('=L', int(hexstr, 16)))))
165 def unpackAddress(self, packed):
166 addr, port = packed.split(':')
167 addr = self.dottedQuadFromHexString(addr)
171 def parseLine(self, line):
172 parts = line.strip().split()
173 localAddr, localPort = self.unpackAddress(parts[1])
174 remoteAddr, remotePort = self.unpackAddress(parts[2])
176 return (localAddr, localPort), (remoteAddr, remotePort), uid
178 def lookup(self, serverAddress, clientAddress):
179 for ent in self.entries():
180 localAddr, remoteAddr, uid = self.parseLine(ent)
181 if remoteAddr == clientAddress and localAddr[1] == serverAddress[1]:
182 return (self.SYSTEM_NAME, self.getUsername(uid))
187 class IdentClient(basic.LineOnlyReceiver):
189 errorTypes = (IdentError, NoUser, InvalidPort, HiddenUser)
194 def lookup(self, portOnServer, portOnClient):
195 """Lookup user information about the specified address pair.
197 self.queries.append((defer.Deferred(), portOnServer, portOnClient))
198 if len(self.queries) > 1:
199 return self.queries[-1][0]
201 self.sendLine('%d, %d' % (portOnServer, portOnClient))
202 return self.queries[-1][0]
204 def lineReceived(self, line):
206 log.msg("Unexpected server response: %r" % (line,))
208 d, _, _ = self.queries.pop(0)
209 self.parseResponse(d, line)
211 self.sendLine('%d, %d' % (self.queries[0][1], self.queries[0][2]))
213 def connectionLost(self, reason):
214 for q in self.queries:
215 q[0].errback(IdentError(reason))
218 def parseResponse(self, deferred, line):
219 parts = line.split(':', 2)
221 deferred.errback(IdentError(line))
223 ports, type, addInfo = map(str.strip, parts)
225 for et in self.errorTypes:
226 if et.identDescription == addInfo:
227 deferred.errback(et(line))
229 deferred.errback(IdentError(line))
231 deferred.callback((type, addInfo))
233 __all__ = ['IdentError', 'NoUser', 'InvalidPort', 'HiddenUser',
234 'IdentServer', 'IdentClient',