| OLD | NEW |
| (Empty) |
| 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: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} | |
| 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 <= portO
nClient <= _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, user
Id)) | |
| 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(fail
ure.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(hexst
r, 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'] | |
| OLD | NEW |