Merge pull request #115 from hfmanson/master
[tlspool] / src / ctlkey.c
1 /* tlspool/ctlkey.c -- Control Key Registry
2  *
3  * The control key registry is a tree-shaped storage structure for ctlkey
4  * values.  Basically, a pthread registers its control key for as long as
5  * it is open to external parties claiming it; a claim will be translated
6  * to a SIGINT to the registering pthread, which will leave its pselect()
7  * and add the newly offered file descriptor.
8  *
9  * This tree remains balanced without doing much for it; it blindly assumes
10  * that a ctlkey is a random binary string, which means that it will be
11  * evenly scattered on average.  This is an explanation of why this registry
12  * is lazy, and makes no extra effort to achieve balancing.
13  *
14  * The caller is expected to allocate (on its stack, presumably) a structure
15  * for the administration of nodes in this tree.  Each node can have up to
16  * 16 references to further details.  Leaf nodes can be NULL, which signals
17  * that the stored pthread_t is the value sought.  Note that there is no such
18  * thing as an invalid pthread_t 
19  *
20  * Finally, ctlkey values must be unique, which is normally the case when
21  * 16 bytes are generated randomly.  The theoretic chance of a clash is
22  * dealt with by reporting back an error.  With 16 bytes or 32 nibbles,
23  * there are at most 32 levels of 16 branches in the ctlkeynode, and the
24  * equality at 32 levels means that a clash was found.
25  *
26  * From: Rick van Rein <rick@openfortress.nl>
27  */
28
29 #include "whoami.h"
30
31 #include <stdlib.h>
32 #include <fcntl.h>
33 #include <assert.h>
34 #include <string.h>
35
36 #include <errno.h>
37 #include <com_err.h>
38 #include <errortable.h>
39
40 #include <pthread.h>
41
42 #ifndef WINDOWS_PORT
43 #include <unistd.h>
44 #endif /* WINDOWS_PORT */
45
46 #include <tlspool/internal.h>
47
48
49 /* The root entry is the initial node for the search tree.  It refers to
50  * itself, but its address is treated as a special value, similar to NULL.
51  */
52 static struct ctlkeynode *rootnode = NULL;
53
54 /* The ctlkey registry must be locked before it is read, or written.
55  */
56 static pthread_mutex_t ctlkey_registry_lock = PTHREAD_MUTEX_INITIALIZER;
57
58 /* The ctlkey_signalling_fd is a file descriptor that can be listened to in
59  * poll() with revents==0; it will signal an error if it is closed.  The file
60  * is in fact an open file descriptor for /dev/null and it will be replaced
61  * by a new one in this variable before it is closed.  The closing however,
62  * ensures that the poll() is interrupted and interrogation of changed
63  * conditions, notably reattahed file descriptors, can be tried.
64  *
65  * FORK!=DETACH marks lines that were defined to signal detaching ctlfd to
66  * poll() statements by closing an fd.  It was inefficient and potentially
67  * subject to race conditions.  The separation of FORK and DETACH made sense
68  * from a conceptual viewpoint, and DETACH need not be notified to the poll()
69  * statements whereas FORK can be done only during the STARTTLS exchange.
70  * So, there is no further need for this signalling facility, as it appears
71  * now; the code is merely here in case we might want to permit FORK later
72  * in the command flow, and perhaps a reverse JOIN statement.  At the moment,
73  * that is not considered helpful. 
74  */
75 //FORK!=DETACH// int ctlkey_signalling_fd = -1;
76
77
78 /* A lock on the signalling_fd variable.
79  */
80 //FORK!=DETACH// static pthread_mutex_t signalling_fd_lock = PTHREAD_MUTEX_INITIALIZER;
81
82
83 /* Raise the signal of the signalling_fd.  This requires the reliant poll()
84  * operations to call ctlkey_signalling_fd() again -- unless of course, they
85  * are supplied with a real controlling file descriptor, for which this is
86  * just a substitute.
87  */
88 //FORK!=DETACH// void ctlkey_signalling_raise (void) {
89 //FORK!=DETACH//        int toclose;
90 //FORK!=DETACH//        assert (pthread_mutex_lock (&signalling_fd_lock) == 0);
91 //FORK!=DETACH//        toclose = ctlkey_signalling_fd;
92 //FORK!=DETACH//        ctlkey_signalling_fd = open ("/dev/null", O_RDWR);
93 //FORK!=DETACH//        close (toclose);        // Sends the "signal" to all interested poll()
94 //FORK!=DETACH//        pthread_mutex_unlock (&signalling_fd_lock);
95 //FORK!=DETACH// }
96         
97
98 /* Register a ctlkey and return 0 if it was successfully registered.  The
99  * only reason for failure would be that the ctlkey is already registered,
100  * which signifies an extremely unlikely clash -- or a program error by
101  * not using properly scattered random sources.  The provided ctlfd may
102  * be -1 to signal it is detached.  The forked flag should be non-zero
103  * to indicate that this is a forked connection.
104  */
105 int ctlkey_register (uint8_t *ctlkey, struct ctlkeynode *ckn, enum security_layer sec, pool_handle_t ctlfd, int forked) {
106         int i;
107         int todo;
108         struct ctlkeynode **nodepp;
109         assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
110         nodepp = &rootnode;
111         while (*nodepp) {
112                 int cmp = memcmp (ctlkey, (*nodepp)->ctlkey, TLSPOOL_CTLKEYLEN);
113                 if (cmp == 0) {
114                         /* Value already known, clash detected */
115                         pthread_mutex_unlock (&ctlkey_registry_lock);
116                         return -1;
117                 } else if (cmp < 0) {
118                         nodepp = & (*nodepp)->lessnode;
119                 } else {
120                         nodepp = & (*nodepp)->morenode;
121                 }
122         }
123         ckn->lessnode = NULL;
124         ckn->morenode = NULL;
125         memcpy (ckn->ctlkey, ctlkey, sizeof (ckn->ctlkey));
126         ckn->security = sec;
127         ckn->ctlfd = ctlfd;
128         ckn->forked = forked? 1: 0;
129         *nodepp = ckn;
130         pthread_mutex_unlock (&ctlkey_registry_lock);
131         return 0;
132 }
133
134 /* Remove a registered cltkey value from th registry.  This is the most
135  * complex operation, as it needs to merge the subtrees.
136  *
137  * This function returns non-zero iff it actually removed a node.  This
138  * is useful because there may be other places from which this function
139  * is called automatically.  Generally, the idea is to use a construct
140  *      if (ctlkey_unregister (...)) {
141  *              free (...);
142  *      }
143  *
144  * TODO: Lazy initial implementation, entirely unbalanced; insert the
145  * complete morenode under the highest lessnode that is NULL.
146  */
147 void _ctlkey_unregister_nodepp (struct ctlkeynode **nodepp) {
148         // This implementation changes *nodepp and may change child nodes
149         // Higher-up nodes are not changed though
150 //DEBUG// fprintf(stderr, "Actually removing node %lx, ctlkey %02x %02x %02x %02x...\n", (long) (intptr_t) *nodepp, (*nodepp)->ctlkey [0], (*nodepp)->ctlkey [1], (*nodepp)->ctlkey [2], (*nodepp)->ctlkey [3]);
151         struct ctlkeynode *subtreeless, *subtreemore;
152         subtreeless = (*nodepp)->lessnode;
153         subtreemore = (*nodepp)->morenode;
154         (*nodepp)->lessnode = NULL;
155         (*nodepp)->morenode = NULL;
156         *nodepp = subtreeless;
157         while (*nodepp) {
158                 nodepp = & (*nodepp)->morenode;
159         }
160         *nodepp = subtreemore;
161 //DEBUG// fprintf(stderr,"Node removal succeeded\n");
162 }
163 int ctlkey_unregister (uint8_t *ctlkey) {
164         struct ctlkeynode **nodepp;
165         int cmp = 1;
166         assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
167         nodepp = &rootnode;
168         while (*nodepp != NULL) {
169                 cmp = memcmp (ctlkey, (*nodepp)->ctlkey, TLSPOOL_CTLKEYLEN);
170                 if (cmp == 0) {
171                         /* Found the right node */
172                         _ctlkey_unregister_nodepp (nodepp);
173                         break;
174                 } else if (cmp < 0) {
175                         nodepp = & (*nodepp)->lessnode;
176                 } else {
177                         nodepp = & (*nodepp)->morenode;
178                 }
179         }
180         /* If not found, simply ignore */
181         pthread_mutex_unlock (&ctlkey_registry_lock);
182         return (cmp == 0);
183 }
184
185 /* Find a ctlkeynode based on a ctlkey.  Returns NULL if not found.
186  * 
187  * The value returned is the registered structure, meaning that any context
188  * to the ctlkeynode returned can be relied upon.
189  *
190  * This also brings a responsibility to lock out other uses of the structure,
191  * which means that a non-NULL return value must later be passed to a function
192  * that unlocks the resource, ctlkey_unfind().
193  */
194 struct ctlkeynode *ctlkey_find (uint8_t *ctlkey, enum security_layer sec, pool_handle_t ctlfd) {
195         struct ctlkeynode *ckn;
196         //
197         // Claim unique access; this lock survives until cltkey_unfind()
198         // if a non-NULL ctlkeynode is returned
199         assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
200         //
201         // Search through the tree of registered ctlkeynode structures
202         ckn = rootnode;
203         while (ckn != NULL) {
204                 int cmp = memcmp (ctlkey, ckn->ctlkey, TLSPOOL_CTLKEYLEN);
205                 if (cmp == 0) {
206                         /* Found the right node */
207                         if (ckn->ctlfd < 0) {
208                                 ckn = NULL;     // Connection not under control
209                         } else if (ckn->ctlfd != ctlfd) {
210                                 ckn = NULL;     // Connection is not yours to find
211                         } else if (ckn->security != sec) {
212                                 ckn = NULL;     // Connection is not of right type
213                         } else {
214                                 break;          // Found, so terminate loop
215                         }
216                         break;           // Final result, so terminate loop
217                 } else if (cmp < 0) {
218                         ckn = ckn->lessnode;
219                 } else {
220                         ckn = ckn->morenode;
221                 }
222         }
223         //
224         // Return the final node in ckn; hold the lock if it is non-NULL
225         if (ckn == NULL) {
226                 pthread_mutex_unlock (&ctlkey_registry_lock);
227         }
228         return ckn;
229 }
230
231 /* Free a ctlkeynode that was returned by ctlkey_find().  This function also
232  * accepts a NULL argument, though those need not be passed through this
233  * function as is the case with the non-NULL return values.
234  *
235  * The need for this function arises from the need to lock the structure, in
236  * avoidance of access to structures that are being unregistered in another
237  * thread.
238  */
239 void ctlkey_unfind (struct ctlkeynode *ckn) {
240         //
241         // Free the lock held after ctlkey_find() -- which is not locked when
242         // the function returned NULL (we explicitly support that return value
243         // here because it can help to simplify code using these functions,
244         // and lead to better readable code with less oversights of unlocking
245         if (ckn != NULL) {
246                 pthread_mutex_unlock (&ctlkey_registry_lock);
247         }
248 }
249
250
251 /* Detach the given ctlkey, assuming it has clientfd as control connection.
252  */
253 void ctlkey_detach (struct command *cmd) {
254         uint8_t *ctlkey = cmd->cmd.pio_data.pioc_control.ctlkey;
255         int todo;
256         int tlserrno;
257         char *errstr;
258         struct ctlkeynode **nodepp;
259         assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
260         nodepp = &rootnode;
261         tlserrno = E_TLSPOOL_CTLKEY_NOT_FOUND;
262         errstr = "TLS Pool cannot find the control key";
263         while (*nodepp != NULL) {
264                 int cmp = memcmp (ctlkey, (*nodepp)->ctlkey, TLSPOOL_CTLKEYLEN);
265                 if (cmp == 0) {
266                         /* Found the right node */
267                         if ((*nodepp)->ctlfd == INVALID_POOL_HANDLE) {
268                                 tlserrno = E_TLSPOOL_CTLKEY_DETACHED;
269                                 errstr = "TLS Pool found the control key already detached";
270                         } else if ((*nodepp)->ctlfd == cmd->clientfd) {
271                                 (*nodepp)->ctlfd = INVALID_POOL_HANDLE;
272                                 //FORK!=DETACH// ctlkey_signalling_raise ();
273                                 tlserrno = 0;
274                                 errstr = NULL;
275                         } else {
276                                 tlserrno = E_TLSPOOL_CTLKEY_NOT_YOURS;
277                                 errstr = "TLS Pool does not grant you the control key";
278                         }
279                         break;
280                 } else if (cmp < 0) {
281                         nodepp = & (*nodepp)->lessnode;
282                 } else {
283                         nodepp = & (*nodepp)->morenode;
284                 }
285         }
286         /* If not found, tlserrno==ENOENT falls through */
287         pthread_mutex_unlock (&ctlkey_registry_lock);
288         // Send the error back -- or success if tlserrno==0
289         send_error (cmd, tlserrno, errstr);
290 }
291
292
293 /* Reattach to the given ctlkey, and set the clientfd as control connection.
294  */
295 void ctlkey_reattach (struct command *cmd) {
296         uint8_t *ctlkey = cmd->cmd.pio_data.pioc_control.ctlkey;
297         int todo;
298         int tlserrno;
299         char *errstr;
300         struct ctlkeynode **nodepp;
301         assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
302         nodepp = &rootnode;
303         tlserrno = E_TLSPOOL_CTLKEY_NOT_FOUND;
304         errstr = "TLS Pool cannot find the control key";
305         while (*nodepp != NULL) {
306                 int cmp = memcmp (ctlkey, (*nodepp)->ctlkey, TLSPOOL_CTLKEYLEN);
307                 if (cmp == 0) {
308                         /* Found the right node */
309                         if ((*nodepp)->ctlfd == INVALID_POOL_HANDLE) {
310                                 (*nodepp)->ctlfd = cmd->clientfd;
311                                 //FORK!=DETACH// ctlkey_signalling_raise ();
312                                 tlserrno = 0;
313                                 errstr = NULL;
314                         } else {
315                                 tlserrno = E_TLSPOOL_CTLKEY_ATTACHED;
316                                 errstr = "TLS Pool found the control key already attached";
317                         }
318                         break;
319                 } else if (cmp < 0) {
320                         nodepp = & (*nodepp)->lessnode;
321                 } else {
322                         nodepp = & (*nodepp)->morenode;
323                 }
324         }
325         /* If not found, tlserrno==ENOENT falls through */
326         pthread_mutex_unlock (&ctlkey_registry_lock);
327         // Send the error back -- or success if tlserrno==0
328         send_error (cmd, tlserrno, errstr);
329 }
330
331 /* Look through the ctlkey registry, to find sessions that depend on a closing
332  * control connection meaning that they cannot survive it being closed;
333  * those entries will be unregistered and deallocated ; this is used when a
334  * client socket closes its link to the TLS Pool.
335  *
336  * This implementation closes all entries whose ctlfd matches; this is needed
337  * for detached nodes that have been reattached.  Nodes that are attached
338  * will usually be removed before they hit this routine, which is also good.
339  *
340  * Note that detached keys are (by definition) protected against this cleanup
341  * procedure; however, when their TLS connection breaks down, they too will
342  * be cleaned up.  Note that detaching is not done before the TLS handshake
343  * is complete.
344  */
345 static void _ctlkey_close_ctlfd_recurse (pool_handle_t clisox, struct ctlkeynode **nodepp) {
346         struct ctlkeynode *node = *nodepp;
347         if (node == NULL) {
348                 return;
349         }
350         _ctlkey_close_ctlfd_recurse (clisox, &node->lessnode);
351         _ctlkey_close_ctlfd_recurse (clisox, &node->morenode);
352         if (node->ctlfd == clisox) {
353                 // At this point, subnodes may be removed and juggled,
354                 // but we can still rely on unchanged (*nodepp) == node
355                 assert (*nodepp == node);
356 //DEBUG// fprintf(stderr,"Unregistering control key (automatically, as controlling fd closes)\n");
357                 if (node->forked) {
358                         node->ctlfd = INVALID_POOL_HANDLE;
359                 } else {
360                         _ctlkey_unregister_nodepp (nodepp);
361                         // Now we know that *nodepp has changed, it is no longer
362                         // pointing to node (so we may remove it).
363                         // No changes have been made higher up though, so
364                         // recursion assumptions are still valid; see
365                         // _ctlkey_unregister_nodepp() for this assumption.
366                         free (node);
367                 }
368         }
369 }
370 void ctlkey_close_ctlfd (pool_handle_t clisox) {
371         assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
372         _ctlkey_close_ctlfd_recurse (clisox, &rootnode);
373         pthread_mutex_unlock (&ctlkey_registry_lock);
374 }
375
376
377 /* Setup the ctlkey registry; notably, allocate the ctlkey_signalling_fd.
378  */
379 void setup_ctlkey (void) {
380         //FORK!=DETACH// ctlkey_signalling_fd = open ("/dev/null", O_RDWR);
381 }
382
383 /* Cleanup the ctlkey registry.
384  */
385 void cleanup_ctlkey (void) {
386         //FORK!=DETACH// int toclose = ctlkey_signalling_fd;
387         //FORK!=DETACH// ctlkey_signalling_fd = -1;
388         //FORK!=DETACH// close (toclose);
389 }