common: Add an assuan logging monitor.
[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_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) do { if (s) len += strlen (s) + 1; } while (0)
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(to, s)                             \
148   do                                            \
149     {                                           \
150       if (s)                                    \
151         {                                       \
152           to = buffer;                          \
153           buffer = stpcpy (buffer, s) + 1;      \
154         }                                       \
155     }                                           \
156   while (0)
157
158   copy (puri->scheme, scheme);
159   /* Make sure the scheme is lower case.  */
160   ascii_strlwr (puri->scheme);
161
162   copy (puri->host, host);
163   copy (puri->path, dn);
164   copy (puri->auth, bindname);
165
166   if (password)
167     {
168       puri->query = calloc (sizeof (*puri->query), 1);
169       if (!puri->query)
170         {
171           err = gpg_err_code_from_syserror ();
172           goto out;
173         }
174       puri->query->name = "password";
175       copy (puri->query->value, password);
176       puri->query->valuelen = strlen (password) + 1;
177     }
178
179   puri->use_tls = strcmp (puri->scheme, "ldaps") == 0;
180   puri->port = lud->lud_port;
181
182  out:
183   if (lud)
184     ldap_free_urldesc (lud);
185
186   if (err)
187     {
188       if (puri)
189         http_release_parsed_uri (puri);
190     }
191   else
192     *purip = puri;
193
194   return gpg_err_make (default_errsource, err);
195 }
196
197 /* The following characters need to be escaped to be part of an LDAP
198    filter: *, (, ), \, NUL and /.  Note: we don't handle NUL, since a
199    NUL can't be part of a C string.
200
201    This function always allocates a new string on success.  It is the
202    caller's responsibility to free it.
203 */
204 char *
205 ldap_escape_filter (const char *filter)
206 {
207   int l = strcspn (filter, "*()\\/");
208   if (l == strlen (filter))
209     /* Nothing to escape.  */
210     return xstrdup (filter);
211
212   {
213     /* In the worst case we need to escape every letter.  */
214     char *escaped = xmalloc (1 + 3 * strlen (filter));
215
216     /* Indices into filter and escaped.  */
217     int filter_i = 0;
218     int escaped_i = 0;
219
220     for (filter_i = 0; filter_i < strlen (filter); filter_i ++)
221       {
222         switch (filter[filter_i])
223           {
224           case '*':
225           case '(':
226           case ')':
227           case '\\':
228           case '/':
229             snprintf (&escaped[escaped_i], 4, "%%%02x",
230                      ((const unsigned char *)filter)[filter_i]);
231             escaped_i += 3;
232             break;
233
234           default:
235             escaped[escaped_i ++] = filter[filter_i];
236             break;
237           }
238       }
239     /* NUL terminate it.  */
240     escaped[escaped_i] = 0;
241
242     /* We could shrink escaped to be just escaped_i bytes, but the
243        result will probably be freed very quickly anyways.  */
244     return escaped;
245   }
246 }