summaryrefslogtreecommitdiffstats
path: root/dirmngr/ldap-misc.c
blob: c3a659d5c1d216aa1d1990e6768f1c8994e96c29 (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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/* ldap-misc.c - Miscellaneous helpers for LDAP functions
 * Copyright (C) 2015, 2021 g10 Code GmbH
 *
 * 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 <https://www.gnu.org/licenses/>.
 * SPDX-License-Identifier: GPL-3.0-or-later
 */

#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "dirmngr-err.h"
#include "../common/util.h"
#include "ldap-misc.h"


/* Convert an LDAP error to a GPG error.  */
gpg_err_code_t
ldap_err_to_gpg_err (int code)
{
  gpg_err_code_t ec;

  switch (code)
    {
#ifdef LDAP_X_CONNECTING
    case LDAP_X_CONNECTING: ec = GPG_ERR_LDAP_X_CONNECTING; break;
#endif

    case LDAP_REFERRAL_LIMIT_EXCEEDED: ec = GPG_ERR_LDAP_REFERRAL_LIMIT; break;
    case LDAP_CLIENT_LOOP: ec = GPG_ERR_LDAP_CLIENT_LOOP; break;
    case LDAP_NO_RESULTS_RETURNED: ec = GPG_ERR_LDAP_NO_RESULTS; break;
    case LDAP_CONTROL_NOT_FOUND: ec = GPG_ERR_LDAP_CONTROL_NOT_FOUND; break;
    case LDAP_NOT_SUPPORTED: ec = GPG_ERR_LDAP_NOT_SUPPORTED; break;
    case LDAP_CONNECT_ERROR: ec = GPG_ERR_LDAP_CONNECT; break;
    case LDAP_NO_MEMORY: ec = GPG_ERR_LDAP_NO_MEMORY; break;
    case LDAP_PARAM_ERROR: ec = GPG_ERR_LDAP_PARAM; break;
    case LDAP_USER_CANCELLED: ec = GPG_ERR_LDAP_USER_CANCELLED; break;
    case LDAP_FILTER_ERROR: ec = GPG_ERR_LDAP_FILTER; break;
    case LDAP_AUTH_UNKNOWN: ec = GPG_ERR_LDAP_AUTH_UNKNOWN; break;
    case LDAP_TIMEOUT: ec = GPG_ERR_LDAP_TIMEOUT; break;
    case LDAP_DECODING_ERROR: ec = GPG_ERR_LDAP_DECODING; break;
    case LDAP_ENCODING_ERROR: ec = GPG_ERR_LDAP_ENCODING; break;
    case LDAP_LOCAL_ERROR: ec = GPG_ERR_LDAP_LOCAL; break;
    case LDAP_SERVER_DOWN: ec = GPG_ERR_LDAP_SERVER_DOWN; break;

    case LDAP_SUCCESS: ec = GPG_ERR_LDAP_SUCCESS; break;

    case LDAP_OPERATIONS_ERROR: ec = GPG_ERR_LDAP_OPERATIONS; break;
    case LDAP_PROTOCOL_ERROR: ec = GPG_ERR_LDAP_PROTOCOL; break;
    case LDAP_TIMELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_TIMELIMIT; break;
    case LDAP_SIZELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_SIZELIMIT; break;
    case LDAP_COMPARE_FALSE: ec = GPG_ERR_LDAP_COMPARE_FALSE; break;
    case LDAP_COMPARE_TRUE: ec = GPG_ERR_LDAP_COMPARE_TRUE; break;
    case LDAP_AUTH_METHOD_NOT_SUPPORTED: ec=GPG_ERR_LDAP_UNSUPPORTED_AUTH;break;
    case LDAP_STRONG_AUTH_REQUIRED: ec = GPG_ERR_LDAP_STRONG_AUTH_RQRD; break;
    case LDAP_PARTIAL_RESULTS: ec = GPG_ERR_LDAP_PARTIAL_RESULTS; break;
    case LDAP_REFERRAL: ec = GPG_ERR_LDAP_REFERRAL; break;

#ifdef LDAP_ADMINLIMIT_EXCEEDED
    case LDAP_ADMINLIMIT_EXCEEDED: ec = GPG_ERR_LDAP_ADMINLIMIT; break;
#endif

#ifdef LDAP_UNAVAILABLE_CRITICAL_EXTENSION
    case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
                               ec = GPG_ERR_LDAP_UNAVAIL_CRIT_EXTN; break;
#endif

    case LDAP_CONFIDENTIALITY_REQUIRED: ec = GPG_ERR_LDAP_CONFIDENT_RQRD; break;
    case LDAP_SASL_BIND_IN_PROGRESS: ec = GPG_ERR_LDAP_SASL_BIND_INPROG; break;
    case LDAP_NO_SUCH_ATTRIBUTE: ec = GPG_ERR_LDAP_NO_SUCH_ATTRIBUTE; break;
    case LDAP_UNDEFINED_TYPE: ec = GPG_ERR_LDAP_UNDEFINED_TYPE; break;
    case LDAP_INAPPROPRIATE_MATCHING: ec = GPG_ERR_LDAP_BAD_MATCHING; break;
    case LDAP_CONSTRAINT_VIOLATION: ec = GPG_ERR_LDAP_CONST_VIOLATION; break;

#ifdef LDAP_TYPE_OR_VALUE_EXISTS
    case LDAP_TYPE_OR_VALUE_EXISTS: ec = GPG_ERR_LDAP_TYPE_VALUE_EXISTS; break;
#endif

    case LDAP_INVALID_SYNTAX: ec = GPG_ERR_LDAP_INV_SYNTAX; break;
    case LDAP_NO_SUCH_OBJECT: ec = GPG_ERR_LDAP_NO_SUCH_OBJ; break;
    case LDAP_ALIAS_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_PROBLEM; break;
    case LDAP_INVALID_DN_SYNTAX: ec = GPG_ERR_LDAP_INV_DN_SYNTAX; break;
    case LDAP_IS_LEAF: ec = GPG_ERR_LDAP_IS_LEAF; break;
    case LDAP_ALIAS_DEREF_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_DEREF; break;

#ifdef LDAP_X_PROXY_AUTHZ_FAILURE
    case LDAP_X_PROXY_AUTHZ_FAILURE: ec = GPG_ERR_LDAP_X_PROXY_AUTH_FAIL; break;
#endif

    case LDAP_INAPPROPRIATE_AUTH: ec = GPG_ERR_LDAP_BAD_AUTH; break;
    case LDAP_INVALID_CREDENTIALS: ec = GPG_ERR_LDAP_INV_CREDENTIALS; break;

#ifdef LDAP_INSUFFICIENT_ACCESS
    case LDAP_INSUFFICIENT_ACCESS: ec = GPG_ERR_LDAP_INSUFFICIENT_ACC; break;
#endif

    case LDAP_BUSY: ec = GPG_ERR_LDAP_BUSY; break;
    case LDAP_UNAVAILABLE: ec = GPG_ERR_LDAP_UNAVAILABLE; break;
    case LDAP_UNWILLING_TO_PERFORM: ec = GPG_ERR_LDAP_UNWILL_TO_PERFORM; break;
    case LDAP_LOOP_DETECT: ec = GPG_ERR_LDAP_LOOP_DETECT; break;
    case LDAP_NAMING_VIOLATION: ec = GPG_ERR_LDAP_NAMING_VIOLATION; break;
    case LDAP_OBJECT_CLASS_VIOLATION: ec = GPG_ERR_LDAP_OBJ_CLS_VIOLATION; break;
    case LDAP_NOT_ALLOWED_ON_NONLEAF: ec=GPG_ERR_LDAP_NOT_ALLOW_NONLEAF;break;
    case LDAP_NOT_ALLOWED_ON_RDN: ec = GPG_ERR_LDAP_NOT_ALLOW_ON_RDN; break;
    case LDAP_ALREADY_EXISTS: ec = GPG_ERR_LDAP_ALREADY_EXISTS; break;
    case LDAP_NO_OBJECT_CLASS_MODS: ec = GPG_ERR_LDAP_NO_OBJ_CLASS_MODS; break;
    case LDAP_RESULTS_TOO_LARGE: ec = GPG_ERR_LDAP_RESULTS_TOO_LARGE; break;
    case LDAP_AFFECTS_MULTIPLE_DSAS: ec = GPG_ERR_LDAP_AFFECTS_MULT_DSAS; break;

#ifdef LDAP_VLV_ERROR
    case LDAP_VLV_ERROR: ec = GPG_ERR_LDAP_VLV; break;
#endif

    case LDAP_OTHER: ec = GPG_ERR_LDAP_OTHER; break;

#ifdef LDAP_CUP_RESOURCES_EXHAUSTED
    case LDAP_CUP_RESOURCES_EXHAUSTED: ec=GPG_ERR_LDAP_CUP_RESOURCE_LIMIT;break;
    case LDAP_CUP_SECURITY_VIOLATION: ec=GPG_ERR_LDAP_CUP_SEC_VIOLATION; break;
    case LDAP_CUP_INVALID_DATA: ec = GPG_ERR_LDAP_CUP_INV_DATA; break;
    case LDAP_CUP_UNSUPPORTED_SCHEME: ec = GPG_ERR_LDAP_CUP_UNSUP_SCHEME; break;
    case LDAP_CUP_RELOAD_REQUIRED: ec = GPG_ERR_LDAP_CUP_RELOAD; break;
#endif

#ifdef LDAP_CANCELLED
    case LDAP_CANCELLED: ec = GPG_ERR_LDAP_CANCELLED; break;
#endif

#ifdef LDAP_NO_SUCH_OPERATION
    case LDAP_NO_SUCH_OPERATION: ec = GPG_ERR_LDAP_NO_SUCH_OPERATION; break;
#endif

#ifdef LDAP_TOO_LATE
    case LDAP_TOO_LATE: ec = GPG_ERR_LDAP_TOO_LATE; break;
#endif

#ifdef LDAP_CANNOT_CANCEL
    case LDAP_CANNOT_CANCEL: ec = GPG_ERR_LDAP_CANNOT_CANCEL; break;
#endif

#ifdef LDAP_ASSERTION_FAILED
    case LDAP_ASSERTION_FAILED: ec = GPG_ERR_LDAP_ASSERTION_FAILED; break;
#endif

#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED
    case LDAP_PROXIED_AUTHORIZATION_DENIED:
                                      ec = GPG_ERR_LDAP_PROX_AUTH_DENIED; break;
#endif

    default:
#if defined(LDAP_E_ERROR) && defined(LDAP_X_ERROR)
      if (LDAP_E_ERROR (code))
        ec = GPG_ERR_LDAP_E_GENERAL;
      else if (LDAP_X_ERROR (code))
        ec = GPG_ERR_LDAP_X_GENERAL;
      else
#endif
        ec = GPG_ERR_LDAP_GENERAL;
      break;
    }

  return ec;
}


/* Retrieve an LDAP error and return it's GPG equivalent.  */
gpg_err_code_t
ldap_to_gpg_err (LDAP *ld)
{
#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
  int err;

  if (ldap_get_option (ld, LDAP_OPT_ERROR_NUMBER, &err) == 0)
    return ldap_err_to_gpg_err (err);
  else
    return GPG_ERR_GENERAL;
#elif defined(HAVE_LDAP_LD_ERRNO)
  return ldap_err_to_gpg_err (ld->ld_errno);
#else
  /* We should never get here since the LDAP library should always
     have either ldap_get_option or ld_errno, but just in case... */
  return GPG_ERR_INTERNAL;
#endif
}



/* Parse an extended filter syntax as used by dirmngr_ldap.c
 * For example:
 *
 *   ^CN=foo, OU=My Users&(objectClasses=*)
 *
 * Uses "CN=foo, OU=My Users" as base DN and "(objectClasses=*)" as
 * filter.  If the base prefix includes an ampersand, it needs to be
 * doubled.  The usual escaping rules for DNs (for the base) and
 * filters apply.  Other examples:
 *
 *  ^CN=foo, OU=My Users&
 *
 * Use just the base DN.
 *
 *  ^CN=foo, OU=My Users&SCOPE&
 *
 * Specify the scope which is "base", "one", or "sub".  May of course
 * also be followed by a filter.
 *
 *  ^&SCOPE&(objectClasses=*)
 *
 * Give a scope and a filter.  Note that R_SCOPE is only changed if a
 * STRING has scope parameter.  Setting this initally to -1 allows to
 * detect this case.
 */
gpg_error_t
ldap_parse_extfilter (const char *string, int silent,
                      char **r_base, int *r_scope, char **r_filter)
{
  gpg_error_t err = 0;
  char *base = NULL;
  char *filter = NULL;
  const char *s;
  char *p;

  if (r_base)
    *r_base = NULL;
  if (r_filter)
    *r_filter = NULL;

  if (*string == '^')
    {
      string++;
      base = xtrymalloc (strlen (string)+1);
      if (!base)
        {
          err = gpg_error_from_syserror ();
          goto leave;
        }
      for (s=string, p=base; *s; s++)
        {
          *p++ = *s;
          if (*s == '&' && s[1] == '&')
            s++;  /* Skip quoted ampersand.  */
          else if (*s == '&')
            {
              p--;
              break;
            }
        }
      *p = 0;
      if (!*s)
        {
          if (!silent)
            log_info ("LDAP extended filter is not terminated\n");
          err = gpg_error (GPG_ERR_SYNTAX);
          goto leave;
        }
      string = s + 1;
    }

  if (!*string)
    goto leave; /* ready.  */

  if (!strncmp (string, "base&", 5))
    {
      string += 5;
      if (r_scope)
        *r_scope = LDAP_SCOPE_BASE;
    }
  else if (!strncmp (string, "one&", 4))
    {
      string += 4;
      if (r_scope)
        *r_scope = LDAP_SCOPE_ONELEVEL;
    }
  else if (!strncmp (string, "sub&", 4))
    {
      string += 4;
      if (r_scope)
        *r_scope = LDAP_SCOPE_SUBTREE;
    }

  if (!*string)
    goto leave; /* ready.  */

  if (*string != '(')
    {
      if (!silent)
        log_info ("LDAP filter does not start with a left parentheses\n");
      err = gpg_error (GPG_ERR_SYNTAX);
      goto leave;
    }
  if (string[strlen(string)-1] != ')')
    {
      if (!silent)
        log_info ("LDAP filter does not end with a right parentheses\n");
      err = gpg_error (GPG_ERR_SYNTAX);
      goto leave;
    }

  filter = xtrystrdup (string);
  if (!filter)
    err = gpg_error_from_syserror ();

 leave:
  if (err)
    {
      xfree (base);
      xfree (filter);
    }
  else
    {
      if (r_base)
        *r_base = base;
      else
        xfree (base);
      if (r_filter)
        *r_filter = filter;
      else
        xfree (filter);
    }
  return err;
}



/* Scan an ISO timestamp and return a Generalized Time according to
 * RFC-4517.  The only supported format is "yyyymmddThhmmss[Z]"
 * delimited by white space, nul, a colon or a comma.  Returns a
 * malloced string or NULL for an invalid string or on memory
 * error.  */
char *
isotime2rfc4517 (const char *string)
{
  int year, month, day, hour, minu, sec;

  if (!isotime_p (string))
    {
      errno = 0;
      return NULL;
    }

  year  = atoi_4 (string);
  month = atoi_2 (string + 4);
  day   = atoi_2 (string + 6);
  hour  = atoi_2 (string + 9);
  minu  = atoi_2 (string + 11);
  sec   = atoi_2 (string + 13);

  /* Basic checks (1600 due to the LDAP time format base)  */
  if (year < 1600 || month < 1 || month > 12 || day < 1 || day > 31
      || hour > 23 || minu > 59 || sec > 61 )
    {
      errno = 0;
      return NULL;
    }

  return gpgrt_bsprintf ("%04d%02d%02d%02d%02d%02d.0Z",
                         year, month, day, hour, minu, sec);
}


/* Parse an LDAP Generalized Time string and update the provided
 * isotime buffer.  On error return and error code.  */
gpg_error_t
rfc4517toisotime (gnupg_isotime_t timebuf, const char *string)
{
  int i;
  int year, month, day, hour, minu, sec;
  const char *s;

  /* Sample value: "20230823141623Z";  */
  for (i=0, s=string; i < 10; i++, s++)  /* Need yyyymmddhh  */
    if (!digitp (s))
      return gpg_error (GPG_ERR_INV_TIME);
  year  = atoi_4 (string);
  month = atoi_2 (string + 4);
  day   = atoi_2 (string + 6);
  hour  = atoi_2 (string + 8);
  minu = 0;
  sec = 0;
  if (digitp (s) && digitp (s+1))
    {
      minu = atoi_2 (s);
      s += 2;
      if (digitp (s) && digitp (s+1))
        {
          sec = atoi_2 (s);
          s += 2;
        }
    }
  if (*s == '.' || *s == ',')
    {
      s++;
      if (!digitp (s))  /* At least one digit of the fraction required.  */
        return gpg_error (GPG_ERR_INV_TIME);
      s++;
      while (digitp (s))
        s++;
    }
  if (*s == 'Z' && (!s[1] || spacep (s+1)))
    ; /* stop here.  */
  else if (*s == '-' || *s == '+')
    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);  /* FIXME */
  else
    return gpg_error (GPG_ERR_INV_TIME);

  snprintf (timebuf, sizeof (gnupg_isotime_t), "%04d%02d%02dT%02d%02d%02d",
            year, month, day, hour, minu, sec);
  return 0;
}