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