Add new LDAP utility functions.
[gnupg.git] / dirmngr / ldap-parse-uri.c
1 /* ldap-parse-uri.c - Parse an LDAP URI.
2  * Copyright (C) 2015  g10 Code GmbH
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 <http://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21
22 #include <gpg-error.h>
23
24 #ifdef HAVE_W32_SYSTEM
25 # include "ldap-url.h"
26 #else
27 # include <ldap.h>
28 #endif
29
30 #include "util.h"
31 #include "http.h"
32
33 /* Returns 1 if the string is an LDAP URL (begins with ldap:, ldaps:
34    or ldapi:).  */
35 int
36 ldap_uri_p (const char *url)
37 {
38   char *colon = strchr (url, ':');
39   if (! colon)
40     return 0;
41   else
42     {
43       int offset = (uintptr_t) colon - (uintptr_t) url;
44
45       if (/* All lower case.  */
46           (offset == 4 && memcmp (url, "ldap", 4) == 0)
47           || (offset == 5
48               && (memcmp (url, "ldaps", 5) == 0
49                   && memcmp (url, "ldapi", 5) == 0))
50           /* Mixed case.  */
51           || ((url[0] == 'l' || url[0] == 'L')
52               && (url[1] == 'd' || url[1] == 'D')
53               && (url[2] == 'a' || url[2] == 'A')
54               && (url[3] == 'p' || url[3] == 'P')
55               && (url[4] == ':'
56                   || ((url[4] == 's' || url[4] == 'S'
57                        || url[4] == 'i' || url[4] == 'i')
58                       && url[5] == ':'))))
59         return 1;
60       return 0;
61     }
62 }
63
64 /* Parse a URI and put the result into *purip.  On success the
65    caller must use http_release_parsed_uri() to releases the resources.
66
67    uri->path is the base DN (or NULL for the default).
68    uri->auth is the bindname (or NULL for none).
69    The uri->query variable "password" is the password.
70
71    Note: any specified scope, any attributes, any filter and any
72    unknown extensions are simply ignored.  */
73 gpg_error_t
74 ldap_parse_uri (parsed_uri_t *purip, const char *uri)
75 {
76   gpg_err_code_t err = 0;
77   parsed_uri_t puri = NULL;
78
79   int result;
80   LDAPURLDesc *lud = NULL;
81
82   char *scheme = NULL;
83   char *host = NULL;
84   char *dn = NULL;
85   char *bindname = NULL;
86   char *password = NULL;
87
88   char **s;
89
90   char *buffer;
91   int len;
92
93   result = ldap_url_parse (uri, &lud);
94   if (result != 0)
95     {
96       log_error ("Unable to parse LDAP uri '%s'\n", uri);
97       err = GPG_ERR_ASS_GENERAL;
98       goto out;
99     }
100
101   scheme = lud->lud_scheme;
102   host = lud->lud_host;
103   dn = lud->lud_dn;
104
105   for (s = lud->lud_exts; s && *s; s ++)
106     {
107       if (strncmp (*s, "bindname=", 9) == 0)
108         {
109           if (bindname)
110             log_error ("bindname given multiple times in URL '%s', ignoring.\n",
111                        uri);
112           else
113             bindname = *s + 9;
114         }
115       else if (strncmp (*s, "password=", 9) == 0)
116         {
117           if (password)
118             log_error ("password given multiple times in URL '%s', ignoring.\n",
119                        uri);
120           else
121             password = *s + 9;
122         }
123       else
124         log_error ("Unhandled extension (%s) in URL '%s', ignoring.",
125                    *s, uri);
126     }
127
128   len = 0;
129
130 #define add(s) ({ if (s) len += strlen (s) + 1; })
131
132   add (scheme);
133   add (host);
134   add (dn);
135   add (bindname);
136   add (password);
137
138   puri = xtrycalloc (1, sizeof *puri + len);
139   if (! puri)
140     {
141       err = gpg_err_code_from_syserror ();
142       goto out;
143     }
144
145   buffer = puri->buffer;
146
147 #define copy(s)                                 \
148   ({                                            \
149     char *copy_result = NULL;                   \
150     if (s)                                      \
151       {                                         \
152         copy_result = buffer;                   \
153         buffer = stpcpy (buffer, s) + 1;        \
154       }                                         \
155     copy_result;                                \
156   })
157
158   puri->scheme = ascii_strlwr (copy (scheme));
159   puri->host = copy (host);
160   puri->path = copy (dn);
161   puri->auth = copy (bindname);
162
163   if (password)
164     {
165       puri->query = calloc (sizeof (*puri->query), 1);
166       puri->query->name = "password";
167       puri->query->value = copy (password);
168       puri->query->valuelen = strlen (password) + 1;
169     }
170
171   puri->use_tls = strcmp (puri->scheme, "ldaps") == 0;
172   puri->port = lud->lud_port;
173
174  out:
175   if (lud)
176     ldap_free_urldesc (lud);
177
178   if (err)
179     {
180       if (puri)
181         http_release_parsed_uri (puri);
182     }
183   else
184     *purip = puri;
185
186   return gpg_err_make (default_errsource, err);
187 }
188
189 /* The following characters need to be escaped to be part of an LDAP
190    filter: *, (, ), \, NUL and /.  Note: we don't handle NUL, since a
191    NUL can't be part of a C string.
192
193    This function always allocates a new string on success.  It is the
194    caller's responsibility to free it.
195 */
196 char *
197 ldap_escape_filter (const char *filter)
198 {
199   int l = strcspn (filter, "*()\\/");
200   if (l == strlen (filter))
201     /* Nothing to escape.  */
202     return xstrdup (filter);
203
204   {
205     /* In the worst case we need to escape every letter.  */
206     char *escaped = xmalloc (1 + 3 * strlen (filter));
207
208     /* Indices into filter and escaped.  */
209     int filter_i = 0;
210     int escaped_i = 0;
211
212     for (filter_i = 0; filter_i < strlen (filter); filter_i ++)
213       {
214         switch (filter[filter_i])
215           {
216           case '*':
217           case '(':
218           case ')':
219           case '\\':
220           case '/':
221             sprintf (&escaped[escaped_i], "%%%02x", filter[filter_i]);
222             escaped_i += 3;
223             break;
224
225           default:
226             escaped[escaped_i ++] = filter[filter_i];
227             break;
228           }
229       }
230     /* NUL terminate it.  */
231     escaped[escaped_i] = 0;
232
233     /* We could shrink escaped to be just escaped_i bytes, but the
234        result will probably be freed very quickly anyways.  */
235     return escaped;
236   }
237 }