aboutsummaryrefslogtreecommitdiffstats
path: root/dirmngr/ldap-wrapper-ce.c
blob: b7a61ad8dfccd6f78d34c61ab8d00a5270ed3141 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/* ldap-wrapper-ce.c - LDAP access via W32 threads
 * Copyright (C) 2010 Free Software Foundation, Inc.
 *
 * This file is part of GnuPG.
 *
 * GnuPG is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * GnuPG is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/* 
   Alternative wrapper for use with WindowsCE.  Under WindowsCE the
   number of processes is strongly limited (32 processes including the
   kernel processes) and thus we don't use the process approach but
   implement a wrapper based on native threads.

   See ldap-wrapper.c for  the standard wrapper interface.
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <pth.h>

#include "dirmngr.h"
#include "misc.h"
#include "ldap-wrapper.h"

#ifdef USE_LDAPWRAPPER
# error This module is not expected to be build.
#endif


/* To keep track of the LDAP wrapper state we use this structure.  */
struct wrapper_context_s
{
  struct wrapper_context_s *next;

  pid_t pid;    /* The pid of the wrapper process. */
  int printable_pid; /* Helper to print diagnostics after the process has
                        been cleaned up. */
  int fd;       /* Connected with stdout of the ldap wrapper.  */
  gpg_error_t fd_error; /* Set to the gpg_error of the last read error
                           if any.  */
  int log_fd;   /* Connected with stderr of the ldap wrapper.  */
  pth_event_t log_ev;
  ctrl_t ctrl;  /* Connection data. */
  int ready;    /* Internally used to mark to be removed contexts. */
  ksba_reader_t reader; /* The ksba reader object or NULL. */
  char *line;     /* Used to print the log lines (malloced). */
  size_t linesize;/* Allocated size of LINE.  */
  size_t linelen; /* Use size of LINE.  */
  time_t stamp;   /* The last time we noticed ativity.  */
};



/* We keep a global list of spawed wrapper process.  A separate thread
   makes use of this list to log error messages and to watch out for
   finished processes. */
static struct wrapper_context_s *wrapper_list;

/* We need to know whether we are shutting down the process.  */
static int shutting_down;



/* Start the reaper thread for this wrapper.  */
void
ldap_wrapper_launch_thread (void)
{
  static int done;
  pth_attr_t tattr;

  if (done)
    return;
  done = 1;

  tattr = pth_attr_new();
  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
  pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper");

  if (!pth_spawn (tattr, ldap_wrapper_thread, NULL))
    {
      log_error (_("error spawning ldap wrapper reaper thread: %s\n"),
                 strerror (errno) );
      dirmngr_exit (1);
    }
  pth_attr_destroy (tattr);
}





/* Wait until all ldap wrappers have terminated.  We assume that the
   kill has already been sent to all of them.  */
void
ldap_wrapper_wait_connections ()
{
  shutting_down = 1;
  while (wrapper_list)
    pth_yield (NULL);
}


/* This function is to be used to release a context associated with the
   given reader object. */
void
ldap_wrapper_release_context (ksba_reader_t reader)
{
  if (!reader )
    return;
    
  for (ctx=wrapper_list; ctx; ctx=ctx->next)
    if (ctx->reader == reader)
      {
        if (DBG_LOOKUP)
          log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n",
                    ctx, 
                    (int)ctx->pid, (int)ctx->printable_pid,
                    ctx->reader,
                    ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0);

        ctx->reader = NULL;
        SAFE_PTH_CLOSE (ctx->fd);
        if (ctx->ctrl)
          {
            ctx->ctrl->refcount--;
            ctx->ctrl = NULL;
          }
        if (ctx->fd_error)
          log_info (_("reading from ldap wrapper %d failed: %s\n"),
                    ctx->printable_pid, gpg_strerror (ctx->fd_error));
        break;
      }
}

/* Cleanup all resources held by the connection associated with
   CTRL.  This is used after a cancel to kill running wrappers.  */
void
ldap_wrapper_connection_cleanup (ctrl_t ctrl)
{
  struct wrapper_context_s *ctx;

  for (ctx=wrapper_list; ctx; ctx=ctx->next)
    if (ctx->ctrl && ctx->ctrl == ctrl)
      {
        ctx->ctrl->refcount--;
        ctx->ctrl = NULL;
        if (ctx->pid != (pid_t)(-1))
          gnupg_kill_process (ctx->pid);
        if (ctx->fd_error)
          log_info (_("reading from ldap wrapper %d failed: %s\n"),
                    ctx->printable_pid, gpg_strerror (ctx->fd_error));
      }
}

/* Start a new LDAP thread and returns a new libksba reader
   object at READER.  ARGV is a NULL terminated list of arguments for
   the wrapper.  The function returns 0 on success or an error code.  */
gpg_error_t
ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
{
  gpg_error_t err;
  pid_t pid;
  struct wrapper_context_s *ctx;
  int i;
  int j;
  const char **arg_list;
  const char *pgmname;
  int outpipe[2], errpipe[2];

  /* It would be too simple to connect stderr just to our logging
     stream.  The problem is that if we are running multi-threaded
     everything gets intermixed.  Clearly we don't want this.  So the
     only viable solutions are either to have another thread
     responsible for logging the messages or to add an option to the
     wrapper module to do the logging on its own.  Given that we anyway
     need a way to rip the child process and this is best done using a
     general ripping thread, that thread can do the logging too. */

  *reader = NULL;

  /* Files: We need to prepare stdin and stdout.  We get stderr from
     the function.  */
  if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program)
    pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP);
  else
    pgmname = opt.ldap_wrapper_program;

  /* Create command line argument array.  */
  for (i = 0; argv[i]; i++)
    ;
  arg_list = xtrycalloc (i + 2, sizeof *arg_list);
  if (!arg_list)
    {
      err = gpg_error_from_syserror ();
      log_error (_("error allocating memory: %s\n"), strerror (errno));
      return err;
    }
  for (i = j = 0; argv[i]; i++, j++)
    if (!i && argv[i + 1] && !strcmp (*argv, "--pass"))
      {
	arg_list[j] = "--env-pass";
	setenv ("DIRMNGR_LDAP_PASS", argv[1], 1);
	i++;
      }
    else
      arg_list[j] = (char*) argv[i];

  ctx = xtrycalloc (1, sizeof *ctx);
  if (!ctx)
    {
      err = gpg_error_from_syserror ();
      log_error (_("error allocating memory: %s\n"), strerror (errno));
      xfree (arg_list);
      return err;
    }

  err = gnupg_create_inbound_pipe (outpipe);
  if (!err)
    {
      err = gnupg_create_inbound_pipe (errpipe);
      if (err)
        {
          close (outpipe[0]);
          close (outpipe[1]);
        }
    }
  if (err)
    {
      log_error (_("error creating pipe: %s\n"), gpg_strerror (err));
      xfree (arg_list);
      xfree (ctx);
      return err;
    }

  err = gnupg_spawn_process_fd (pgmname, arg_list,
                                -1, outpipe[1], errpipe[1], &pid);
  xfree (arg_list);
  close (outpipe[1]);
  close (errpipe[1]);
  if (err)
    {
      close (outpipe[0]);
      close (errpipe[0]);
      xfree (ctx);
      return err;
    }

  ctx->pid = pid;
  ctx->printable_pid = (int) pid;
  ctx->fd = outpipe[0];
  ctx->log_fd = errpipe[0];
  ctx->log_ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, ctx->log_fd);
  if (! ctx->log_ev)
    {
      xfree (ctx);
      return gpg_error_from_syserror ();
    }
  ctx->ctrl = ctrl;
  ctrl->refcount++;
  ctx->stamp = time (NULL);

  err = ksba_reader_new (reader);
  if (!err)
    err = ksba_reader_set_cb (*reader, reader_callback, ctx);
  if (err)
    {
      log_error (_("error initializing reader object: %s\n"),
                 gpg_strerror (err));
      destroy_wrapper (ctx);
      ksba_reader_release (*reader);
      *reader = NULL;
      return err;
    }

  /* Hook the context into our list of running wrappers.  */
  ctx->reader = *reader;
  ctx->next = wrapper_list;
  wrapper_list = ctx;
  if (opt.verbose)
    log_info ("ldap wrapper %d started (reader %p)\n",
              (int)ctx->pid, ctx->reader);

  /* Need to wait for the first byte so we are able to detect an empty
     output and not let the consumer see an EOF without further error
     indications.  The CRL loading logic assumes that after return
     from this function, a failed search (e.g. host not found ) is
     indicated right away. */
  {
    unsigned char c;

    err = read_buffer (*reader, &c, 1);
    if (err)
      {
        ldap_wrapper_release_context (*reader);
        ksba_reader_release (*reader);
        *reader = NULL;
        if (gpg_err_code (err) == GPG_ERR_EOF)
          return gpg_error (GPG_ERR_NO_DATA);
        else
          return err;
      }
    ksba_reader_unread (*reader, &c, 1);
  }

  return 0;
}