chiark / gitweb /
dirmngr: hkp: Avoid potential race condition when some hosts die.
[gnupg2.git] / dirmngr / ldap-wrapper-ce.c
1 /* ldap-wrapper-ce.c - LDAP access via W32 threads
2  * Copyright (C) 2010 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <https://www.gnu.org/licenses/>.
18  */
19
20 /*
21    Alternative wrapper for use with WindowsCE.  Under WindowsCE the
22    number of processes is strongly limited (32 processes including the
23    kernel processes) and thus we don't use the process approach but
24    implement a wrapper based on native threads.
25
26    See ldap-wrapper.c for  the standard wrapper interface.
27  */
28
29 #include <config.h>
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #include <fcntl.h>
37 #include <time.h>
38 #include <npth.h>
39 #include <assert.h>
40
41 #include "dirmngr.h"
42 #include "misc.h"
43 #include "ldap-wrapper.h"
44
45 #ifdef USE_LDAPWRAPPER
46 # error This module is not expected to be build.
47 #endif
48
49
50
51 /* Read a fixed amount of data from READER into BUFFER.  */
52 static gpg_error_t
53 read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
54 {
55   gpg_error_t err;
56   size_t nread;
57
58   while (count)
59     {
60       err = ksba_reader_read (reader, buffer, count, &nread);
61       if (err)
62         return err;
63       buffer += nread;
64       count -= nread;
65     }
66   return 0;
67 }
68
69
70
71
72 /* Start the reaper thread for this wrapper.  */
73 void
74 ldap_wrapper_launch_thread (void)
75 {
76   /* Not required.  */
77 }
78
79
80
81
82
83 /* Wait until all ldap wrappers have terminated.  We assume that the
84    kill has already been sent to all of them.  */
85 void
86 ldap_wrapper_wait_connections ()
87 {
88   /* Not required.  */
89 }
90
91
92 /* Cleanup all resources held by the connection associated with
93    CTRL.  This is used after a cancel to kill running wrappers.  */
94 void
95 ldap_wrapper_connection_cleanup (ctrl_t ctrl)
96 {
97   (void)ctrl;
98
99   /* Not required.  */
100 }
101
102
103 \f
104 /* The cookie we use to implement the outstream of the wrapper thread.  */
105 struct outstream_cookie_s
106 {
107   int refcount; /* Reference counter - possible values are 1 and 2.  */
108
109   /* We don't need a mutex for the conditions, as npth provides a
110      simpler condition interface that relies on the global lock.  This
111      can be used if we never yield between testing the condition and
112      waiting on it.  */
113   npth_cond_t wait_data; /* Condition that data is available.  */
114   npth_cond_t wait_space; /* Condition that space is available.  */
115
116   int eof_seen;       /* EOF indicator.  */
117   char buffer[4000];  /* Data ring buffer.  */
118   size_t buffer_len;  /* The amount of data in the BUFFER.  */
119   size_t buffer_pos;  /* The next read position of the BUFFER.  */
120 };
121
122 #define BUFFER_EMPTY(c) ((c)->buffer_len == 0)
123 #define BUFFER_FULL(c) ((c)->buffer_len == DIM((c)->buffer))
124 #define BUFFER_DATA_AVAILABLE(c) ((c)->buffer_len)
125 #define BUFFER_SPACE_AVAILABLE(c) (DIM((c)->buffer) - (c)->buffer_len)
126 #define BUFFER_INC_POS(c,n) (c)->buffer_pos = ((c)->buffer_pos + (n)) % DIM((c)->buffer)
127 #define BUFFER_CUR_POS(c) (&(c)->buffer[(c)->buffer_pos])
128
129 static int
130 buffer_get_data (struct outstream_cookie_s *cookie, char *dst, int cnt)
131 {
132   int amount;
133   int left;
134   int chunk;
135
136   amount = cnt;
137   if (BUFFER_DATA_AVAILABLE (cookie) < amount)
138     amount = BUFFER_DATA_AVAILABLE (cookie);
139   left = amount;
140
141   /* How large is the part up to the end of the buffer array?  */
142   chunk = DIM(cookie->buffer) - cookie->buffer_pos;
143   if (chunk > left)
144     chunk = left;
145
146   memcpy (dst, BUFFER_CUR_POS (cookie), chunk);
147   BUFFER_INC_POS (cookie, chunk);
148   left -= chunk;
149   dst += chunk;
150
151   if (left)
152     {
153       memcpy (dst, BUFFER_CUR_POS (cookie), left);
154       BUFFER_INC_POS (cookie, left);
155     }
156
157   return amount;
158 }
159
160
161 static int
162 buffer_put_data (struct outstream_cookie_s *cookie, const char *src, int cnt)
163 {
164   int amount;
165   int remain;
166   int left;
167   int chunk;
168
169   remain = DIM(cookie->buffer) - cookie->buffer_len;
170
171   amount = cnt;
172   if (remain < amount)
173     amount = remain;
174   left = amount;
175
176   /* How large is the part up to the end of the buffer array?  */
177   chunk = DIM(cookie->buffer) - cookie->buffer_pos;
178   if (chunk > left)
179     chunk = left;
180
181   memcpy (BUFFER_CUR_POS (cookie), src, chunk);
182   BUFFER_INC_POS (cookie, chunk);
183   left -= chunk;
184   src += chunk;
185
186   if (left)
187     {
188       memcpy (BUFFER_CUR_POS (cookie), src, left);
189       BUFFER_INC_POS (cookie, left);
190     }
191
192   cookie->buffer_len -= amount;
193   return amount;
194 }
195
196
197 /* The writer function for the outstream.  This is used to transfer
198    the output of the ldap wrapper thread to the ksba reader object.  */
199 static ssize_t
200 outstream_cookie_writer (void *cookie_arg, const void *buffer, size_t size)
201 {
202   struct outstream_cookie_s *cookie = cookie_arg;
203   const char *src;
204   ssize_t nwritten = 0;
205   int res;
206   ssize_t amount = 0;
207
208   src = buffer;
209   do
210     {
211       int was_empty = 0;
212
213       /* Wait for free space.  */
214       while (BUFFER_FULL(cookie))
215         {
216           /* Buffer is full:  Wait for space.  */
217           res = npth_cond_wait (&cookie->wait_space, NULL);
218           if (res)
219             {
220               gpg_err_set_errno (res);
221               return -1;
222             }
223         }
224
225       if (BUFFER_EMPTY(cookie))
226         was_empty = 1;
227
228       /* Copy data.  */
229       nwritten = buffer_put_data (cookie, buffer, size);
230       size -= nwritten;
231       src += nwritten;
232       amount += nwritten;
233
234       if (was_empty)
235         npth_cond_signal (&cookie->wait_data);
236     }
237   while (size);  /* Until done.  */
238
239   return amount;
240 }
241
242
243 static void
244 outstream_release_cookie (struct outstream_cookie_s *cookie)
245 {
246   cookie->refcount--;
247   if (!cookie->refcount)
248     {
249       npth_cond_destroy (&cookie->wait_data);
250       npth_cond_destroy (&cookie->wait_space);
251       xfree (cookie);
252     }
253 }
254
255
256 /* Closer function for the outstream.  This deallocates the cookie if
257    it won't be used anymore.  */
258 static int
259 outstream_cookie_closer (void *cookie_arg)
260 {
261   struct outstream_cookie_s *cookie = cookie_arg;
262
263   if (!cookie)
264     return 0;  /* Nothing to do.  */
265
266   cookie->eof_seen = 1; /* (only useful if refcount > 1)  */
267
268   assert (cookie->refcount > 0);
269   outstream_release_cookie (cookie);
270   return 0;
271 }
272
273
274 /* The KSBA reader callback which takes the output of the ldap thread
275    form the outstream_cookie_writer and make it available to the ksba
276    reader.  */
277 static int
278 outstream_reader_cb (void *cb_value, char *buffer, size_t count,
279                      size_t *r_nread)
280 {
281   struct outstream_cookie_s *cookie = cb_value;
282   size_t nread = 0;
283   int was_full = 0;
284
285   if (!buffer && !count && !r_nread)
286     return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Rewind is not supported.  */
287
288   *r_nread = 0;
289
290   while (BUFFER_EMPTY(cookie))
291     {
292       if (cookie->eof_seen)
293         return gpg_error (GPG_ERR_EOF);
294
295       /* Wait for data to become available.  */
296       npth_cond_wait (&cookie->wait_data, NULL);
297     }
298
299   if (BUFFER_FULL(cookie))
300     was_full = 1;
301
302   nread = buffer_get_data (cookie, buffer, count);
303
304   if (was_full)
305     {
306       npth_cond_signal (&cookie->wait_space);
307     }
308
309   *r_nread = nread;
310   return 0; /* Success.  */
311 }
312
313
314 /* This function is called by ksba_reader_release.  */
315 static void
316 outstream_reader_released (void *cb_value, ksba_reader_t r)
317 {
318   struct outstream_cookie_s *cookie = cb_value;
319
320   (void)r;
321
322   assert (cookie->refcount > 0);
323   outstream_release_cookie (cookie);
324 }
325
326
327
328 /* This function is to be used to release a context associated with the
329    given reader object.  This does not release the reader object, though. */
330 void
331 ldap_wrapper_release_context (ksba_reader_t reader)
332 {
333   (void)reader;
334   /* Nothing to do.  */
335 }
336
337
338 \f
339 /* Free a NULL terminated array of malloced strings and the array
340    itself.  */
341 static void
342 free_arg_list (char **arg_list)
343 {
344   int i;
345
346   if (arg_list)
347     {
348       for (i=0; arg_list[i]; i++)
349         xfree (arg_list[i]);
350       xfree (arg_list);
351     }
352 }
353
354
355 /* Copy ARGV into a new array and prepend one element as name of the
356    program (which is more or less a stub).  We need to allocate all
357    the strings to get ownership of them.  */
358 static gpg_error_t
359 create_arg_list (const char *argv[], char ***r_arg_list)
360 {
361   gpg_error_t err;
362   char **arg_list;
363   int i, j;
364
365   for (i = 0; argv[i]; i++)
366     ;
367   arg_list = xtrycalloc (i + 2, sizeof *arg_list);
368   if (!arg_list)
369     goto outofcore;
370
371   i = 0;
372   arg_list[i] = xtrystrdup ("<ldap-wrapper-thread>");
373   if (!arg_list[i])
374     goto outofcore;
375   i++;
376   for (j=0; argv[j]; j++)
377     {
378       arg_list[i] = xtrystrdup (argv[j]);
379       if (!arg_list[i])
380         goto outofcore;
381       i++;
382     }
383   arg_list[i] = NULL;
384   *r_arg_list = arg_list;
385   return 0;
386
387  outofcore:
388   err = gpg_error_from_syserror ();
389   log_error (_("error allocating memory: %s\n"), strerror (errno));
390   free_arg_list (arg_list);
391   *r_arg_list = NULL;
392   return err;
393
394 }
395
396
397 /* Parameters passed to the wrapper thread. */
398 struct ldap_wrapper_thread_parms
399 {
400   char **arg_list;
401   estream_t outstream;
402 };
403
404 /* The thread which runs the LDAP wrapper.  */
405 static void *
406 ldap_wrapper_thread (void *opaque)
407 {
408   struct ldap_wrapper_thread_parms *parms = opaque;
409
410   /*err =*/ ldap_wrapper_main (parms->arg_list, parms->outstream);
411
412   /* FIXME: Do we need to return ERR?  */
413
414   free_arg_list (parms->arg_list);
415   es_fclose (parms->outstream);
416   xfree (parms);
417   return NULL;
418 }
419
420
421
422 /* Start a new LDAP thread and returns a new libksba reader
423    object at READER.  ARGV is a NULL terminated list of arguments for
424    the wrapper.  The function returns 0 on success or an error code.  */
425 gpg_error_t
426 ldap_wrapper (ctrl_t ctrl, ksba_reader_t *r_reader, const char *argv[])
427 {
428   gpg_error_t err;
429   struct ldap_wrapper_thread_parms *parms;
430   npth_attr_t tattr;
431   es_cookie_io_functions_t outstream_func = { NULL };
432   struct outstream_cookie_s *outstream_cookie;
433   ksba_reader_t reader;
434   int res;
435   npth_t thread;
436
437   (void)ctrl;
438
439   *r_reader = NULL;
440
441   parms = xtrycalloc (1, sizeof *parms);
442   if (!parms)
443     return gpg_error_from_syserror ();
444
445   err = create_arg_list (argv, &parms->arg_list);
446   if (err)
447     {
448       xfree (parms);
449       return err;
450     }
451
452   outstream_cookie = xtrycalloc (1, sizeof *outstream_cookie);
453   if (!outstream_cookie)
454     {
455       err = gpg_error_from_syserror ();
456       free_arg_list (parms->arg_list);
457       xfree (parms);
458       return err;
459     }
460   outstream_cookie->refcount++;
461
462   res = npth_cond_init (&outstream_cookie->wait_data, NULL);
463   if (res)
464     {
465       free_arg_list (parms->arg_list);
466       xfree (parms);
467       return gpg_error_from_errno (res);
468     }
469   res = npth_cond_init (&outstream_cookie->wait_space, NULL);
470   if (res)
471     {
472       npth_cond_destroy (&outstream_cookie->wait_data);
473       free_arg_list (parms->arg_list);
474       xfree (parms);
475       return gpg_error_from_errno (res);
476     }
477
478   err = ksba_reader_new (&reader);
479   if (!err)
480     err = ksba_reader_set_release_notify (reader,
481                                           outstream_reader_released,
482                                           outstream_cookie);
483   if (!err)
484     err = ksba_reader_set_cb (reader,
485                               outstream_reader_cb, outstream_cookie);
486   if (err)
487     {
488       log_error (_("error initializing reader object: %s\n"),
489                  gpg_strerror (err));
490       ksba_reader_release (reader);
491       outstream_release_cookie (outstream_cookie);
492       free_arg_list (parms->arg_list);
493       xfree (parms);
494       return err;
495     }
496
497
498   outstream_func.func_write = outstream_cookie_writer;
499   outstream_func.func_close = outstream_cookie_closer;
500   parms->outstream = es_fopencookie (outstream_cookie, "wb", outstream_func);
501   if (!parms->outstream)
502     {
503       err = gpg_error_from_syserror ();
504       ksba_reader_release (reader);
505       outstream_release_cookie (outstream_cookie);
506       free_arg_list (parms->arg_list);
507       xfree (parms);
508       return err;
509     }
510   outstream_cookie->refcount++;
511
512   res = npth_attr_init(&tattr);
513   if (res)
514     {
515       err = gpg_error_from_errno (res);
516       ksba_reader_release (reader);
517       free_arg_list (parms->arg_list);
518       es_fclose (parms->outstream);
519       xfree (parms);
520       return err;
521     }
522   npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
523
524   res = npth_create (&thread, &tattr, ldap_wrapper_thread, parms);
525   npth_attr_destroy (&tattr);
526   if (res)
527     {
528       err = gpg_error_from_errno (res);
529       log_error ("error spawning ldap wrapper thread: %s\n",
530                  strerror (res) );
531     }
532   else
533     parms = NULL; /* Now owned by the thread.  */
534
535   if (parms)
536     {
537       free_arg_list (parms->arg_list);
538       es_fclose (parms->outstream);
539       xfree (parms);
540     }
541   if (err)
542     {
543       ksba_reader_release (reader);
544       return err;
545     }
546
547   /* Need to wait for the first byte so we are able to detect an empty
548      output and not let the consumer see an EOF without further error
549      indications.  The CRL loading logic assumes that after return
550      from this function, a failed search (e.g. host not found ) is
551      indicated right away. */
552   {
553     unsigned char c;
554
555     err = read_buffer (reader, &c, 1);
556     if (err)
557       {
558         ksba_reader_release (reader);
559         reader = NULL;
560         if (gpg_err_code (err) == GPG_ERR_EOF)
561           return gpg_error (GPG_ERR_NO_DATA);
562         else
563           return err;
564       }
565     ksba_reader_unread (reader, &c, 1);
566   }
567
568   *r_reader = reader;
569
570   return 0;
571 }