6d97abdf1fdaad022e22644a0a033e6f78868e24
[tlspool] / lib / python / py-tlspool.i
1 /* This is the specifics module for SWIG mapping to Python.
2  * It includes generic definitions from ../swig-tlspool.i
3  *
4  * This separation enables us to override function names, for instance
5  * to raw/internal names, and then to add language-specific wrappers.
6  *
7  * From: Rick van Rein <rick@openfortress.nl>
8  */
9
10 %module tlspool
11
12
13 /* Renames, prefixing "_" when wrapped below for better parameter handling
14  */
15 %rename(_pid) tlspool_pid;
16 %rename(_open_poolhandle) tlspool_open_poolhandle;
17 %rename(_ping) tlspool_ping;
18 %rename(_starttls) tlspool_starttls;
19 %rename(_control_detach) tlspool_control_detach;
20 %rename(_control_reattach) tlspool_control_reattach;
21 %rename(_prng) tlspool_prng;
22
23
24 // type maps to translate between SWIG's abstract data types and Python types
25
26 %typemap(in) ctlkey_t {
27         ssize_t inlen = 0;
28         if ((PyString_AsStringAndSize ($input, (char **) &($1), &inlen) == -1) || (inlen != TLSPOOL_CTLKEYLEN)) {
29                 PyErr_SetString (PyExc_ValueError, "Control keys are binary strings of length 16");
30                 return NULL;
31         }
32 }
33
34 %typemap(out) ctlkey_t {
35         if ($result == NULL) {
36                 $1 = Py_None;
37         } else {
38                 $1 = PyString_FromStringAndSize ($result, TLSPOOL_CTLKEYLEN);
39         }
40 }
41
42
43 // apply the settings above as modifiers to the generic TLS Pool wrapping
44
45 %include "../swig-tlspool.i"
46
47
48 // helper function to raise OSError with the parameter set to C-reachable errno
49
50 %inline %{
51
52 PyObject *raise_errno (void) {
53         return PyErr_SetFromErrno (PyExc_OSError);
54 }
55
56 %}
57
58
59
60 // full-blown Python code to include
61
62 %pythoncode %{
63
64
65 import os
66 import socket
67
68 if not 'IPPROTO_SCTP' in dir (socket):
69         socket.IPPROTO_SCTP = 132
70
71
72 def pid (pidfile=None):
73         """This function returns the process identity of the TLS Pool.
74            When no pidfile is provided, the default path as configured in the
75            TLS Pool libary will be used.  An Exception is thrown when there is
76            no TLS Pool.
77         """
78         process_id = _pid (pidfile)
79         if process_id < 0:
80                 _tlspool.raise_errno ()
81         else:
82                 return process_id
83
84 def open_poolhandle (path=None):
85         """This function returns the OS-specific socket handle value for the
86            TLS Pool.  It is already connected, and shared with the internal
87            management of this module, so it must not be closed by the caller.
88            When no path is provided, the default path is used instead.
89            This function blocks until a connection to the TLS Pool succeeds.
90            The path is only used in the first call, and only when no prior
91            contact to the TLS Pool has been made; if that has happened, then
92            this function returns the previously found socket handle.
93         """
94         fd = _open_poolhandle (path)
95         if fd < 0:
96                 _tlspool.raise_errno ()
97         else:
98                 return fd
99
100 def ping (YYYYMMDD_producer=_tlspool.TLSPOOL_IDENTITY_V2,
101                         facilities=_tlspool.PIOF_FACILITY_ALL_CURRENT):
102         """This function takes in a string with a date in YYYYMMDD format, followed
103            by a user@domain producer identifier.  It takes in an integer value
104            that is the logical or of PIOF_FACILITY_xxx values.  This is sent to
105            the TLS Pool through tlspool_ping() and the response is returned as a
106            similar tuple (YYYYMMDD_producer, facilities) as returned by the
107            TLS Pool.  This function blocks until a connection to the TLS Pool has
108            been found.  It is a good first command to send to the TLS Pool.
109         """
110         pp = ping_data ()
111         pp.YYYYMMDD_producer = YYYYMMDD_producer
112         pp.facilities = facilities
113         if _ping (pp) < 0:
114                 _tlspool.raise_errno ()
115         else:
116                 return (pp.YYYYMMDD_producer, pp.facilities)
117
118 def make_tlsdata (localid='', remoteid='',
119                 flags=0, local_flags=0,
120                 ipproto=socket.IPPROTO_TCP, streamid=0, service='',
121                 timeout=0, ctlkey=None):
122         """Make a new tlsdata structure, based the fields that may be supplied
123            as flags, or otherwise as defaults.  Note that the field "local" is
124            renamed to "local_flags" for reasons of clarity.  This helper function
125            returns a tlsdata structure or raises an exception.
126         """
127         tlsdata = starttls_data ()
128         if ctlkey is not None:
129                 tlsdata.ctlkey = ctlkey
130         tlsdata.service = service
131         tlsdata.localid = localid
132         tlsdata.remoteid = remoteid
133         tlsdata.flags = flags
134         tlsdata.local = local_flags
135         tlsdata.ipproto = ipproto
136         tlsdata.streamid = streamid
137         tlsdata.timeout = timeout
138         return tlsdata
139
140 class Connection:
141         """The tlspool.Connection class wraps around a connection to be protected
142            by the TLS Pool.  It uses the global socket for attaching to the
143            TLS Pool, but the individual instances of this class do represent
144            individual connections managed by the TLS Pool.
145            New instances can already collect a large number of parameters
146            that end up in the tlsdata structure of tlspool_starttls(),
147            but these values may also be created through getters/setters.
148            Some values have reasonable defaults, but some must have been
149            set before invoking the starttls() method on the instance.
150            The tlsdata fields all have defaults, as specified under
151            tlspool.make_tlsdata().
152         """
153
154         def __init__ (self, cryptsocket, plainsocket=None, **tlsdata):
155                 self.cryptsk = cryptsocket
156                 self.cryptfd = cryptsocket.fileno ()
157                 self.plainsk = plainsocket
158                 self.plainfd = plainsocket.fileno () if plainsocket else -1
159                 self.tlsdata = make_tlsdata (**tlsdata)
160
161         def close (self):
162                 assert (self.plainsk is not None)
163                 assert (self.plainfd >= 0)
164                 self.plainsk.close ()
165                 self.plainsk = None
166                 self.plainfd = -1
167
168         def tlsdata_get (self, tlsvar):
169                 return self.tlsdata [tlsvar]
170
171         def tlsdata_set (self, tlsvar, value):
172                 self.tlsdata [tlsvar] = value
173
174         def starttls (self):
175                 """Initiate a TLS connection with the current settings, as
176                    provided during instantiation or through getter/setter
177                    access afterwards.  The variables that are required at
178                    this point are service and, already obliged when making
179                    a new instance, cryptfd.
180                 """
181                 assert (self.cryptsk is not None)
182                 assert (self.cryptfd >= 0)
183                 assert (self.tlsdata.service != '')
184                 try:
185                         af = self.cryptsk.family
186                 except:
187                         af = socket.AF_INET
188                 try:
189                         if   self.cryptsk.proto in [socket.IPPROTO_UDP]:
190                                 socktp = socket.SOCK_DGRAM
191                         elif self.cryptsk.proto in [socket.IPPROTO_SCTP]:
192                                 socktp = socket.SOCK_SEQPACKET
193                         else:
194                                 socktp = socket.SOCK_STREAM
195                 except:
196                         socktp = socket.SOCK_STREAM
197                 plainsockptr = socket_data ()
198                 plainsockptr.unix_socket = self.plainfd
199                 # Provide None for the callback function, SWIG won't support it
200                 # We might at some point desire a library of C routine options?
201                 rv = _starttls (self.cryptfd, self.tlsdata, plainsockptr, None)
202                 self.plainfd = -1
203                 self.cryptfd = -1
204                 self.cryptsk = None
205                 if rv < 0:
206                         _tlspool.raise_errno ()
207                 if self.plainsk is None:
208                         self.plainfd = plainsockptr.unix_socket
209                         self.plainsk = socket.fromfd (self.plainfd, af, socktp)
210                 return self.plainsk
211
212         def prng (self, length, label, ctxvalue=None):
213                 """Produce length bytes of randomness from the master key, after
214                    mixing it with the label and optional context value in ctxvalue.
215                    The procedure has been described in RFC 5705.
216                    #TODO# Find a way to return the random bytes, and use the length
217                 """
218                 buf = prng_data ()
219                 rv = _prng (label, ctxvalue, length, buf, self.tlsdata.ctlkey)
220                 if rv < 0:
221                         _tlspool.raise_errno ()
222                 else:
223                         return buf
224
225         def control_detach (self):
226                 """Detach control of this connection.  Although the connection
227                    itself will still be available, control over it is diminished
228                    and its continuation is no longer dependent on the current
229                    connection.  You may need to pass tlsdata.ctlkey to another
230                    process, or use control_reattach(), before this is reversed
231                    in this process or another.
232                 """
233                 _control_detach (self.tlsdata.ctlkey)
234
235         def control_reattach (self, ctlkey=None):
236                 """Reattach control of this connection.  The connection may have
237                    called control_detach() in this process or another.  To help
238                    with the latter case, its tlsdata.ctlkey must have been moved
239                    into this instance.
240                 """
241                 _control_reattach (self.tlsdata.ctlkey)
242
243 class SecurityMixIn:
244         """The SecurityMixIn class can be added as a subclass before a
245            (subclass of) SocketServer.BaseServer and it adds the facilities
246            of starttls(), startgss() and startssh() which add security through
247            one of the mechanisms.  In addition, have_xxx() can be used to
248            query in advance if startxxx() should be doable with the present
249            combination of TLS Pool and client code.
250            
251            Set a tlsdata field in the subclass, using the tlspool.make_tlsdata()
252            helper function, to bootstrap the same kind of behaviour on all
253            clients for which this class will be instantiated.  Such a tlsdata
254            class variable will automatically be cloned into instances.
255            Example code:
256            
257                 from tlspool import SecurityMixIn
258                 from SocketServer import BaseHandler
259                 
260                 class MyHandler (SecurityMixIn, BaseHandler):
261                         
262                         tlsdata = make_tlsdata (service=...)
263                         
264                         def handle (self):
265                                 ...
266                                 self.starttls ()
267                         
268                         def handle_secure (self):
269                                 ...
270            
271            Alternatively, you can setup the tlsdata structure, or any part of it,
272            at a later time, through the tlsdata variable that will then be
273            instantiated during object initialisation.  Any such changes to fields
274            must be completed before invoking starttls() on this object.
275         """
276
277         _pingdata = None
278         tlsdata = None
279
280         def __init__ (self):
281                 if self.tlsdata is None:
282                         self.tlsdata = make_tlsdata ()
283
284         def have_tls (self):
285                 """Check whether STARTTLS is supported on the current TLS Pool"""
286                 if self._pingdata is None:
287                         self._pingdata = ping ()
288                 return (self._pingdata [1] & PIOF_FACILITY_STARTTLS) != 0
289
290         def have_ssh (self):
291                 """Check whether STARTSSH is supported on the current TLS Pool"""
292                 if self._pingdata is None:
293                         self._pingdata = ping ()
294                 return (self._pingdata [1] & PIOF_FACILITY_STARTSSH) != 0
295
296         def have_gss (self):
297                 """Check whether STARTGSS is supported on the current TLS Pool"""
298                 if self._pingdata is None:
299                         self._pingdata = ping ()
300                 return (self._pingdata [1] & PIOF_FACILITY_STARTGSS) != 0
301
302         def starttls (self):
303                 """Modify the current socket to make it a TLS socket.  Use the
304                    tlsdata as currently setup (see class-level documentation).
305                    Afterwards, call handle_secure() to start from scratch with
306                    a secure connection.  Also see the man page on the underlying
307                    C library call, tlspool_starttls(3).
308                    
309                    Some protocols start TLS immediately, for instance HTTPS;
310                    for such protocols, the handle() method would immediately
311                    call starttls() and the actual handler code would move
312                    into secure_handle().
313                    
314                    Other protocols, such as XMPP and IMAP, start in plaintext
315                    and exchange pleasantries until they agree on running TLS.
316                    This is the point where starttls() can be invoked.
317                    
318                    The methods startssh() and startgss() are place holders for
319                    future alternatives to start other security wrappers than
320                    TLS, after negotiating them in a manner similar to STARTTLS.
321                 """
322                 if type (self.request) == tuple:
323                         sox = self.request [1]
324                 else:
325                         sox = self.request
326                 assert (type (sox) == socket._socketobject)
327                 try:
328                         af = sox.family
329                 except:
330                         af = socket.AF_INET
331                 try:
332                         if   sox.proto in [socket.IPPROTO_UDP]:
333                                 socktp = socket.SOCK_DGRAM
334                         elif sox.proto in [socket.IPPROTO_SCTP]:
335                                 socktp = socket.SOCK_SEQPACKET
336                         else:
337                                 socktp = socket.SOCK_STREAM
338                 except:
339                         socktp = socket.SOCK_STREAM
340                 plainsockptr = socket_data ()
341                 plainsockptr.unix_socket = -1
342                 rv = _starttls (sox.fileno (), self.tlsdata, plainsockptr, None)
343                 if rv < 0:
344                         _tlspool.raise_errno ()
345                 assert (plainsockptr.unix_socket >= 0)
346                 sox2 = socket.fromfd (plainsockptr.unix_socket, af, socktp)
347                 if type (self.request) == tuple:
348                         self.request [1] = sox2
349                 else:
350                         self.request     = sox2
351                 self.handle_secure ()
352
353         def startssh (self):
354                 raise NotImplementedError ("Python wrapper does not implement STARTSSH")
355
356         def startgss (self):
357                 raise NotImplementedError ("Python wrapper does not implement STARTGSS")
358
359         def handle (self):
360                 """When not overridden, the handle() method replaces the one in
361                    later-mentioned classes in the inheritance structure.  This
362                    means that this is the default behaviour when the SecureMixIn
363                    precedes the handler class.  This particular version of handle()
364                    does nothing but invoke starttls(), which in turn invokes
365                    handle_secure() after the TLS handshake has succeeded.
366                 """
367                 print 'STARTTLS.BEGIN'
368                 self.starttls ()
369                 print 'STARTTLS.END'
370
371         def handle_secure (self):
372                 """This method may be overridden to handle the secure part of the
373                    connection, after starttls() has been called from within
374                    handle().  This function is special in the sense that it may
375                    refer to self.tlsdata and rely on the localid and remoteid
376                    as being negotiated over TLS.
377                    
378                    Since any prior actions in handle() are usually unauthenticated,
379                    it is common to start from scratch with the protocol.  The secure
380                    layer however, tends to enable more features, such as blunt
381                    password submission and, perhaps, privileged operations available
382                    to the authenticated self.tlsdata.remoteid user.
383                    
384                    As an example, if the handler class is BaseHTTPRequestHandler,
385                    its handle() method could be invoked on the secured content
386                    (possibly after authorisation) with an override as follows:
387                    
388                         def handle_secure (self):
389                                 BaseHTTPRequestHandler.handle (self)
390                 """
391                 pass
392
393 %}
394
395 %include "defines.h"