/* 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 . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #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; }