1
# -*- test-case-name: twisted.test.test_ident -*-
2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
Ident protocol implementation.
12
from __future__ import generators
16
from twisted.internet import defer
17
from twisted.protocols import basic
18
from twisted.python import log, failure
21
_MAX_PORT = 2 ** 16 - 1
23
class IdentError(Exception):
25
Can't determine connection owner; reason unknown.
28
identDescription = 'UNKNOWN-ERROR'
31
return self.identDescription
34
class NoUser(IdentError):
36
The connection specified by the port pair is not currently in use or
37
currently not owned by an identifiable entity.
39
identDescription = 'NO-USER'
42
class InvalidPort(IdentError):
44
Either the local or foreign port was improperly specified. This should
45
be returned if either or both of the port ids were out of range (TCP
46
port numbers are from 1-65535), negative integers, reals or in any
47
fashion not recognized as a non-negative integer.
49
identDescription = 'INVALID-PORT'
52
class HiddenUser(IdentError):
54
The server was able to identify the user of this port, but the
55
information was not returned at the request of the user.
57
identDescription = 'HIDDEN-USER'
60
class IdentServer(basic.LineOnlyReceiver):
62
The Identification Protocol (a.k.a., "ident", a.k.a., "the Ident
63
Protocol") provides a means to determine the identity of a user of a
64
particular TCP connection. Given a TCP port number pair, it returns a
65
character string which identifies the owner of that connection on the
68
Server authors should subclass this class and override the lookup method.
69
The default implementation returns an UNKNOWN-ERROR response for every
73
def lineReceived(self, line):
74
parts = line.split(',')
79
portOnServer, portOnClient = map(int, parts)
83
if _MIN_PORT <= portOnServer <= _MAX_PORT and _MIN_PORT <= portOnClient <= _MAX_PORT:
84
self.validQuery(portOnServer, portOnClient)
86
self._ebLookup(failure.Failure(InvalidPort()), portOnServer, portOnClient)
88
def invalidQuery(self):
89
self.transport.loseConnection()
91
def validQuery(self, portOnServer, portOnClient):
92
serverAddr = self.transport.getHost()[1], portOnServer
93
clientAddr = self.transport.getPeer()[1], portOnClient
94
defer.maybeDeferred(self.lookup, serverAddr, clientAddr
95
).addCallback(self._cbLookup, portOnServer, portOnClient
96
).addErrback(self._ebLookup, portOnServer, portOnClient
99
def _cbLookup(self, (sysName, userId), sport, cport):
100
self.sendLine('%d, %d : USERID : %s : %s' % (sport, cport, sysName, userId))
102
def _ebLookup(self, failure, sport, cport):
103
if failure.check(IdentError):
104
self.sendLine('%d, %d : ERROR : %s' % (sport, cport, failure.value))
107
self.sendLine('%d, %d : ERROR : %s' % (sport, cport, IdentError(failure.value)))
109
def lookup(self, serverAddress, clientAddress):
110
"""Lookup user information about the specified address pair.
112
Return value should be a two-tuple of system name and username.
113
Acceptable values for the system name may be found online at::
115
U{http://www.iana.org/assignments/operating-system-names}
117
This method may also raise any IdentError subclass (or IdentError
118
itself) to indicate user information will not be provided for the
121
A Deferred may also be returned.
123
@param serverAddress: A two-tuple representing the server endpoint
124
of the address being queried. The first element is a string holding
125
a dotted-quad IP address. The second element is an integer
126
representing the port.
128
@param clientAddress: Like L{serverAddress}, but represents the
129
client endpoint of the address being queried.
133
class ProcServerMixin:
134
"""Implements lookup() to grab entries for responses from /proc/net/tcp
137
SYSTEM_NAME = 'LINUX'
140
from pwd import getpwuid
141
def getUsername(self, uid, getpwuid=getpwuid):
142
return getpwuid(uid)[0]
145
def getUsername(self, uid):
149
f = file('/proc/net/tcp')
154
def dottedQuadFromHexString(self, hexstr):
155
return '.'.join(map(str, struct.unpack('4B', struct.pack('=L', int(hexstr, 16)))))
157
def unpackAddress(self, packed):
158
addr, port = packed.split(':')
159
addr = self.dottedQuadFromHexString(addr)
163
def parseLine(self, line):
164
parts = line.strip().split()
165
localAddr, localPort = self.unpackAddress(parts[1])
166
remoteAddr, remotePort = self.unpackAddress(parts[2])
168
return (localAddr, localPort), (remoteAddr, remotePort), uid
170
def lookup(self, serverAddress, clientAddress):
171
for ent in self.entries():
172
localAddr, remoteAddr, uid = self.parseLine(ent)
173
if remoteAddr == clientAddress and localAddr[1] == serverAddress[1]:
174
return (self.SYSTEM_NAME, self.getUsername(uid))
179
class IdentClient(basic.LineOnlyReceiver):
181
errorTypes = (IdentError, NoUser, InvalidPort, HiddenUser)
186
def lookup(self, portOnServer, portOnClient):
187
"""Lookup user information about the specified address pair.
189
self.queries.append((defer.Deferred(), portOnServer, portOnClient))
190
if len(self.queries) > 1:
191
return self.queries[-1][0]
193
self.sendLine('%d, %d' % (portOnServer, portOnClient))
194
return self.queries[-1][0]
196
def lineReceived(self, line):
198
log.msg("Unexpected server response: %r" % (line,))
200
d, _, _ = self.queries.pop(0)
201
self.parseResponse(d, line)
203
self.sendLine('%d, %d' % (self.queries[0][1], self.queries[0][2]))
205
def connectionLost(self, reason):
206
for q in self.queries:
207
q[0].errback(IdentError(reason))
210
def parseResponse(self, deferred, line):
211
parts = line.split(':', 2)
213
deferred.errback(IdentError(line))
215
ports, type, addInfo = map(str.strip, parts)
217
for et in self.errorTypes:
218
if et.identDescription == addInfo:
219
deferred.errback(et(line))
221
deferred.errback(IdentError(line))
223
deferred.callback((type, addInfo))
225
__all__ = ['IdentError', 'NoUser', 'InvalidPort', 'HiddenUser',
226
'IdentServer', 'IdentClient',