| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 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'] |
|---|