1 /* tlspool/ctlkey.c -- Control Key Registry
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.
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.
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
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.
26 * From: Rick van Rein <rick@openfortress.nl>
38 #include <errortable.h>
44 #endif /* WINDOWS_PORT */
46 #include <tlspool/internal.h>
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.
52 static struct ctlkeynode *rootnode = NULL;
54 /* The ctlkey registry must be locked before it is read, or written.
56 static pthread_mutex_t ctlkey_registry_lock = PTHREAD_MUTEX_INITIALIZER;
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.
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.
75 //FORK!=DETACH// int ctlkey_signalling_fd = -1;
78 /* A lock on the signalling_fd variable.
80 //FORK!=DETACH// static pthread_mutex_t signalling_fd_lock = PTHREAD_MUTEX_INITIALIZER;
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
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);
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.
105 int ctlkey_register (uint8_t *ctlkey, struct ctlkeynode *ckn, enum security_layer sec, pool_handle_t ctlfd, int forked) {
108 struct ctlkeynode **nodepp;
109 assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
112 int cmp = memcmp (ctlkey, (*nodepp)->ctlkey, TLSPOOL_CTLKEYLEN);
114 /* Value already known, clash detected */
115 pthread_mutex_unlock (&ctlkey_registry_lock);
117 } else if (cmp < 0) {
118 nodepp = & (*nodepp)->lessnode;
120 nodepp = & (*nodepp)->morenode;
123 ckn->lessnode = NULL;
124 ckn->morenode = NULL;
125 memcpy (ckn->ctlkey, ctlkey, sizeof (ckn->ctlkey));
128 ckn->forked = forked? 1: 0;
130 pthread_mutex_unlock (&ctlkey_registry_lock);
134 /* Remove a registered cltkey value from th registry. This is the most
135 * complex operation, as it needs to merge the subtrees.
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 (...)) {
144 * TODO: Lazy initial implementation, entirely unbalanced; insert the
145 * complete morenode under the highest lessnode that is NULL.
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;
158 nodepp = & (*nodepp)->morenode;
160 *nodepp = subtreemore;
161 //DEBUG// fprintf(stderr,"Node removal succeeded\n");
163 int ctlkey_unregister (uint8_t *ctlkey) {
164 struct ctlkeynode **nodepp;
166 assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
168 while (*nodepp != NULL) {
169 cmp = memcmp (ctlkey, (*nodepp)->ctlkey, TLSPOOL_CTLKEYLEN);
171 /* Found the right node */
172 _ctlkey_unregister_nodepp (nodepp);
174 } else if (cmp < 0) {
175 nodepp = & (*nodepp)->lessnode;
177 nodepp = & (*nodepp)->morenode;
180 /* If not found, simply ignore */
181 pthread_mutex_unlock (&ctlkey_registry_lock);
185 /* Find a ctlkeynode based on a ctlkey. Returns NULL if not found.
187 * The value returned is the registered structure, meaning that any context
188 * to the ctlkeynode returned can be relied upon.
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().
194 struct ctlkeynode *ctlkey_find (uint8_t *ctlkey, enum security_layer sec, pool_handle_t ctlfd) {
195 struct ctlkeynode *ckn;
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);
201 // Search through the tree of registered ctlkeynode structures
203 while (ckn != NULL) {
204 int cmp = memcmp (ctlkey, ckn->ctlkey, TLSPOOL_CTLKEYLEN);
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
214 break; // Found, so terminate loop
216 break; // Final result, so terminate loop
217 } else if (cmp < 0) {
224 // Return the final node in ckn; hold the lock if it is non-NULL
226 pthread_mutex_unlock (&ctlkey_registry_lock);
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.
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
239 void ctlkey_unfind (struct ctlkeynode *ckn) {
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
246 pthread_mutex_unlock (&ctlkey_registry_lock);
251 /* Detach the given ctlkey, assuming it has clientfd as control connection.
253 void ctlkey_detach (struct command *cmd) {
254 uint8_t *ctlkey = cmd->cmd.pio_data.pioc_control.ctlkey;
258 struct ctlkeynode **nodepp;
259 assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
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);
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 ();
276 tlserrno = E_TLSPOOL_CTLKEY_NOT_YOURS;
277 errstr = "TLS Pool does not grant you the control key";
280 } else if (cmp < 0) {
281 nodepp = & (*nodepp)->lessnode;
283 nodepp = & (*nodepp)->morenode;
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);
293 /* Reattach to the given ctlkey, and set the clientfd as control connection.
295 void ctlkey_reattach (struct command *cmd) {
296 uint8_t *ctlkey = cmd->cmd.pio_data.pioc_control.ctlkey;
300 struct ctlkeynode **nodepp;
301 assert (pthread_mutex_lock (&ctlkey_registry_lock) == 0);
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);
308 /* Found the right node */
309 if ((*nodepp)->ctlfd == INVALID_POOL_HANDLE) {
310 (*nodepp)->ctlfd = cmd->clientfd;
311 //FORK!=DETACH// ctlkey_signalling_raise ();
315 tlserrno = E_TLSPOOL_CTLKEY_ATTACHED;
316 errstr = "TLS Pool found the control key already attached";
319 } else if (cmp < 0) {
320 nodepp = & (*nodepp)->lessnode;
322 nodepp = & (*nodepp)->morenode;
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);
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.
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.
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
345 static void _ctlkey_close_ctlfd_recurse (pool_handle_t clisox, struct ctlkeynode **nodepp) {
346 struct ctlkeynode *node = *nodepp;
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");
358 node->ctlfd = INVALID_POOL_HANDLE;
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.
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);
377 /* Setup the ctlkey registry; notably, allocate the ctlkey_signalling_fd.
379 void setup_ctlkey (void) {
380 //FORK!=DETACH// ctlkey_signalling_fd = open ("/dev/null", O_RDWR);
383 /* Cleanup the ctlkey registry.
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);