root / trunk / twisted / protocols / ident.py

Revision 24441, 7.3 kB (checked in by thijs, 1 year ago)

Merge maintainer-email-2438: Get rid of references to maintainer email addresses from code.

Author: thijs
Reviewer: exarkun
Fixes: #2438

Line 
1 # -*- test-case-name: twisted.test.test_ident -*-
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5
6 """
7 Ident protocol implementation.
8
9 @author: Jp Calderone
10 """
11
12 from __future__ import generators
13
14 import struct
15
16 from twisted.internet import defer
17 from twisted.protocols import basic
18 from twisted.python import log, failure
19
20 _MIN_PORT = 1
21 _MAX_PORT = 2 ** 16 - 1
22
23 class IdentError(Exception):
24     """
25     Can't determine connection owner; reason unknown.
26     """
27
28     identDescription = 'UNKNOWN-ERROR'
29
30     def __str__(self):
31         return self.identDescription
32
33
34 class NoUser(IdentError):
35     """
36     The connection specified by the port pair is not currently in use or
37     currently not owned by an identifiable entity.
38     """
39     identDescription = 'NO-USER'
40
41
42 class InvalidPort(IdentError):
43     """
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.
48     """
49     identDescription = 'INVALID-PORT'
50
51
52 class HiddenUser(IdentError):
53     """
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.
56     """
57     identDescription = 'HIDDEN-USER'
58
59
60 class IdentServer(basic.LineOnlyReceiver):
61     """
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
66     server's system.
67
68     Server authors should subclass this class and override the lookup method.
69     The default implementation returns an UNKNOWN-ERROR response for every
70     query.
71     """
72
73     def lineReceived(self, line):
74         parts = line.split(',')
75         if len(parts) != 2:
76             self.invalidQuery()
77         else:
78             try:
79                 portOnServer, portOnClient = map(int, parts)
80             except ValueError:
81                 self.invalidQuery()
82             else:
83                 if _MIN_PORT <= portOnServer <= _MAX_PORT and _MIN_PORT <= portOnClient <= _MAX_PORT:
84                     self.validQuery(portOnServer, portOnClient)
85                 else:
86                     self._ebLookup(failure.Failure(InvalidPort()), portOnServer, portOnClient)
87
88     def invalidQuery(self):
89         self.transport.loseConnection()
90
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
97             )
98
99     def _cbLookup(self, (sysName, userId), sport, cport):
100         self.sendLine('%d, %d : USERID : %s : %s' % (sport, cport, sysName, userId))
101
102     def _ebLookup(self, failure, sport, cport):
103         if failure.check(IdentError):
104             self.sendLine('%d, %d : ERROR : %s' % (sport, cport, failure.value))
105         else:
106             log.err(failure)
107             self.sendLine('%d, %d : ERROR : %s' % (sport, cport, IdentError(failure.value)))
108
109     def lookup(self, serverAddress, clientAddress):
110         """Lookup user information about the specified address pair.
111
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::
114
115             U{http://www.iana.org/assignments/operating-system-names}
116
117         This method may also raise any IdentError subclass (or IdentError
118         itself) to indicate user information will not be provided for the
119         given query.
120
121         A Deferred may also be returned.
122
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.
127
128         @param clientAddress: Like L{serverAddress}, but represents the
129         client endpoint of the address being queried.
130         """
131         raise IdentError()
132
133 class ProcServerMixin:
134     """Implements lookup() to grab entries for responses from /proc/net/tcp
135     """
136
137     SYSTEM_NAME = 'LINUX'
138
139     try:
140         from pwd import getpwuid
141         def getUsername(self, uid, getpwuid=getpwuid):
142             return getpwuid(uid)[0]
143         del getpwuid
144     except ImportError:
145         def getUsername(self, uid):
146             raise IdentError()
147
148     def entries(self):
149         f = file('/proc/net/tcp')
150         f.readline()
151         for L in f:
152             yield L.strip()
153
154     def dottedQuadFromHexString(self, hexstr):
155         return '.'.join(map(str, struct.unpack('4B', struct.pack('=L', int(hexstr, 16)))))
156
157     def unpackAddress(self, packed):
158         addr, port = packed.split(':')
159         addr = self.dottedQuadFromHexString(addr)
160         port = int(port, 16)
161         return addr, port
162
163     def parseLine(self, line):
164         parts = line.strip().split()
165         localAddr, localPort = self.unpackAddress(parts[1])
166         remoteAddr, remotePort = self.unpackAddress(parts[2])
167         uid = int(parts[7])
168         return (localAddr, localPort), (remoteAddr, remotePort), uid
169
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))
175
176         raise NoUser()
177
178
179 class IdentClient(basic.LineOnlyReceiver):
180
181     errorTypes = (IdentError, NoUser, InvalidPort, HiddenUser)
182
183     def __init__(self):
184         self.queries = []
185
186     def lookup(self, portOnServer, portOnClient):
187         """Lookup user information about the specified address pair.
188         """
189         self.queries.append((defer.Deferred(), portOnServer, portOnClient))
190         if len(self.queries) > 1:
191             return self.queries[-1][0]
192
193         self.sendLine('%d, %d' % (portOnServer, portOnClient))
194         return self.queries[-1][0]
195
196     def lineReceived(self, line):
197         if not self.queries:
198             log.msg("Unexpected server response: %r" % (line,))
199         else:
200             d, _, _ = self.queries.pop(0)
201             self.parseResponse(d, line)
202             if self.queries:
203                 self.sendLine('%d, %d' % (self.queries[0][1], self.queries[0][2]))
204
205     def connectionLost(self, reason):
206         for q in self.queries:
207             q[0].errback(IdentError(reason))
208         self.queries = []
209
210     def parseResponse(self, deferred, line):
211         parts = line.split(':', 2)
212         if len(parts) != 3:
213             deferred.errback(IdentError(line))
214         else:
215             ports, type, addInfo = map(str.strip, parts)
216             if type == 'ERROR':
217                 for et in self.errorTypes:
218                     if et.identDescription == addInfo:
219                         deferred.errback(et(line))
220                         return
221                 deferred.errback(IdentError(line))
222             else:
223                 deferred.callback((type, addInfo))
224
225 __all__ = ['IdentError', 'NoUser', 'InvalidPort', 'HiddenUser',
226            'IdentServer', 'IdentClient',
227            'ProcServerMixin']
Note: See TracBrowser for help on using the browser.