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