Some work on porting dirmngr (unfinished)
[gnupg.git] / dirmngr / dirmngr_ldap.c
1 /* dirmngr-ldap.c  -  The LDAP helper for dirmngr.
2  *      Copyright (C) 2004 g10 Code GmbH
3  *
4  * This file is part of DirMngr.
5  *
6  * DirMngr 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 2 of the License, or
9  * (at your option) any later version.
10  *
11  * DirMngr 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <stddef.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #ifdef HAVE_SIGNAL_H
29 # include <signal.h>
30 #endif
31 #include <errno.h>
32 #include <assert.h>
33 #include <sys/time.h>
34 #include <unistd.h>
35
36 #ifdef HAVE_W32_SYSTEM
37 #include <winsock2.h>
38 #include <winldap.h>
39 #include <fcntl.h>
40 #include "ldap-url.h"
41 #else
42 /* For OpenLDAP, to enable the API that we're using. */
43 #define LDAP_DEPRECATED 1
44 #include <ldap.h>
45 #endif
46
47
48 #define JNLIB_NEED_LOG_LOGV
49 #include "../common/logging.h"
50 #include "../common/argparse.h"
51 #include "../common/stringhelp.h"
52 #include "../common/mischelp.h"
53 #include "../common/strlist.h"
54
55 #include "i18n.h"
56 #include "util.h"
57
58 #define DEFAULT_LDAP_TIMEOUT 100 /* Arbitrary long timeout. */
59
60
61 /* Constants for the options.  */
62 enum 
63   {
64     oQuiet        = 'q',
65     oVerbose      = 'v',
66
67     oTimeout      = 500,
68     oMulti,
69     oProxy,
70     oHost,
71     oPort,
72     oUser,
73     oPass,
74     oEnvPass,
75     oDN,
76     oFilter,
77     oAttr,
78
79     oOnlySearchTimeout,
80     oLogWithPID
81   };
82
83
84 /* The list of options as used by the argparse.c code.  */
85 static ARGPARSE_OPTS opts[] = {
86   { oVerbose,  "verbose",   0, N_("verbose") },
87   { oQuiet,    "quiet",     0, N_("be somewhat more quiet") },
88   { oTimeout,  "timeout",   1, N_("|N|set LDAP timeout to N seconds")},
89   { oMulti,    "multi",     0, N_("return all values in"
90                                   " a record oriented format")},
91   { oProxy,    "proxy",     2,
92     N_("|NAME|ignore host part and connect through NAME")},
93   { oHost,     "host",      2, N_("|NAME|connect to host NAME")},
94   { oPort,     "port",      1, N_("|N|connect to port N")},
95   { oUser,     "user",      2, N_("|NAME|use user NAME for authentication")},
96   { oPass,     "pass",      2, N_("|PASS|use password PASS"
97                                   " for authentication")},
98   { oEnvPass,  "env-pass",  0, N_("take password from $DIRMNGR_LDAP_PASS")},
99   { oDN,       "dn",        2, N_("|STRING|query DN STRING")},
100   { oFilter,   "filter",    2, N_("|STRING|use STRING as filter expression")},
101   { oAttr,     "attr",      2, N_("|STRING|return the attribute STRING")},
102   { oOnlySearchTimeout, "only-search-timeout", 0, "@"},
103   { oLogWithPID,"log-with-pid", 0, "@"},
104   { 0, NULL, 0, NULL }
105 };
106
107
108 /* The usual structure for the program flags.  */
109 static struct
110 {
111   int quiet;
112   int verbose;
113   struct timeval timeout; /* Timeout for the LDAP search functions.  */
114   unsigned int alarm_timeout; /* And for the alarm based timeout.  */
115   int multi;
116
117   /* Note that we can't use const for the strings because ldap_* are
118      not defined that way.  */
119   char *proxy; /* Host and Port override.  */
120   char *user;  /* Authentication user.  */
121   char *pass;  /* Authentication password.  */
122   char *host;  /* Override host.  */
123   int port;    /* Override port.  */
124   char *dn;    /* Override DN.  */
125   char *filter;/* Override filter.  */
126   char *attr;  /* Override attribute.  */
127 } opt;
128
129
130 /* Prototypes.  */
131 static void catch_alarm (int dummy);
132 static int process_url (const char *url);
133
134
135
136 /* Function called by argparse.c to display information.  */
137 static const char *
138 my_strusage (int level)
139 {
140   const char *p;
141     
142   switch(level)
143     {
144     case 11: p = "dirmngr_ldap (GnuPG)";
145       break;
146     case 13: p = VERSION; break;
147     case 17: p = PRINTABLE_OS_NAME; break;
148     case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
149     case 49: p = PACKAGE_BUGREPORT; break;
150     case 1:
151     case 40: p =
152                _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n");
153       break;
154     case 41: p =
155           _("Syntax: dirmngr_ldap [options] [URL]\n"
156             "Internal LDAP helper for Dirmngr.\n"
157             "Interface and options may change without notice.\n");
158       break;
159
160     default: p = NULL;
161     }
162   return p;
163 }
164
165
166 int
167 main (int argc, char **argv )
168 {
169   ARGPARSE_ARGS pargs;
170   int any_err = 0;
171   char *p;
172   int only_search_timeout = 0;
173
174 #ifdef HAVE_W32_SYSTEM
175   /* Yeah, right.  Sigh.  */
176 #error  FIXME
177   _setmode (_fileno (stdout), _O_BINARY);
178 #endif
179
180   set_strusage (my_strusage);
181   log_set_prefix ("dirmngr_ldap", JNLIB_LOG_WITH_PREFIX); 
182   
183   /* Setup I18N. */
184   i18n_init();
185
186   /* LDAP defaults */
187   opt.timeout.tv_sec = DEFAULT_LDAP_TIMEOUT;
188   opt.timeout.tv_usec = 0;
189   opt.alarm_timeout = 0;
190
191   /* Parse the command line.  */
192   pargs.argc = &argc;
193   pargs.argv = &argv;
194   pargs.flags= 1;  /* Do not remove the args. */
195   while (arg_parse (&pargs, opts) )
196     {
197       switch (pargs.r_opt)
198         {
199         case oVerbose: opt.verbose++; break;
200         case oQuiet: opt.quiet++; break;
201         case oTimeout: 
202           opt.timeout.tv_sec = pargs.r.ret_int; 
203           opt.timeout.tv_usec = 0;
204           opt.alarm_timeout = pargs.r.ret_int;
205           break;
206         case oOnlySearchTimeout: only_search_timeout = 1; break;
207         case oMulti: opt.multi = 1; break;
208         case oUser: opt.user = pargs.r.ret_str; break;
209         case oPass: opt.pass = pargs.r.ret_str; break;
210         case oEnvPass:
211           opt.pass = getenv ("DIRMNGR_LDAP_PASS");
212           break;
213         case oProxy: opt.proxy = pargs.r.ret_str; break;
214         case oHost: opt.host = pargs.r.ret_str; break;
215         case oPort: opt.port = pargs.r.ret_int; break;
216         case oDN:   opt.dn = pargs.r.ret_str; break;
217         case oFilter: opt.filter = pargs.r.ret_str; break;
218         case oAttr: opt.attr = pargs.r.ret_str; break;
219         case oLogWithPID:
220           {
221             unsigned int oldflags;
222             log_get_prefix (&oldflags);
223             log_set_prefix (NULL, oldflags | JNLIB_LOG_WITH_PID);
224           }
225           break;
226
227         default : pargs.err = 2; break;
228         }
229     }
230
231   if (only_search_timeout)
232     opt.alarm_timeout = 0;
233
234   if (opt.proxy)
235     {
236       opt.host = xstrdup (opt.proxy);
237       p = strchr (opt.host, ':');
238       if (p)
239         {
240           *p++ = 0;
241           opt.port = atoi (p);
242         }
243       if (!opt.port)
244         opt.port = 389;  /* make sure ports gets overridden.  */
245     }
246         
247   if (opt.port < 0 || opt.port > 65535)
248     log_error (_("invalid port number %d\n"), opt.port);
249
250   if (log_get_errorcount (0))
251     exit (2);
252
253   if (argc < 1)
254     usage (1);
255
256   if (opt.alarm_timeout)
257     {
258 #ifndef HAVE_W32_SYSTEM
259 # if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION)
260       struct sigaction act;
261       
262       act.sa_handler = catch_alarm;
263       sigemptyset (&act.sa_mask);
264       act.sa_flags = 0;
265       if (sigaction (SIGALRM,&act,NULL))
266 # else 
267       if (signal (SIGALRM, catch_alarm) == SIG_ERR)
268 # endif
269           log_fatal ("unable to register timeout handler\n");
270 #endif
271     }
272
273   for (; argc; argc--, argv++)
274     if (process_url (*argv))
275       any_err = 1;
276
277   return any_err;
278 }
279
280
281 static void
282 catch_alarm (int dummy)
283 {
284   (void)dummy;
285   _exit (10);
286 }
287
288
289 static void
290 set_timeout (void)
291 {
292 #ifndef HAVE_W32_SYSTEM
293   /* FIXME for W32.  */
294   if (opt.alarm_timeout)
295     alarm (opt.alarm_timeout);
296 #endif
297 }
298
299
300 /* Helper for fetch_ldap().  */
301 static int
302 print_ldap_entries (LDAP *ld, LDAPMessage *msg, char *want_attr)
303 {
304   LDAPMessage *item;
305   int any = 0;
306
307   for (item = ldap_first_entry (ld, msg); item;
308        item = ldap_next_entry (ld, item))
309     {
310       BerElement *berctx;
311       char *attr;
312
313       if (opt.verbose > 1)
314         log_info (_("scanning result for attribute `%s'\n"),
315                   want_attr? want_attr : "[all]");
316
317       if (opt.multi)
318         { /*  Write item marker. */
319           if (es_fwrite ("I\0\0\0\0", 5, 1, es_stdout) != 1)
320             {
321               log_error (_("error writing to stdout: %s\n"),
322                          strerror (errno));
323               return -1;
324             }
325         }
326
327           
328       for (attr = ldap_first_attribute (ld, item, &berctx); attr;
329            attr = ldap_next_attribute (ld, item, berctx))
330         {
331           struct berval **values;
332           int idx;
333
334           if (opt.verbose > 1)
335             log_info (_("          available attribute `%s'\n"), attr);
336           
337           set_timeout ();
338
339           /* I case we want only one attribute we do a case
340              insensitive compare without the optional extension
341              (i.e. ";binary").  Case insensitive is not really correct
342              but the best we can do.  */
343           if (want_attr)
344             {
345               char *cp1, *cp2;
346               int cmpres;
347
348               cp1 = strchr (want_attr, ';');
349               if (cp1)
350                 *cp1 = 0;
351               cp2 = strchr (attr, ';');
352               if (cp2)
353                 *cp2 = 0;
354               cmpres = ascii_strcasecmp (want_attr, attr);
355               if (cp1)
356                 *cp1 = ';';
357               if (cp2)
358                 *cp2 = ';';
359               if (cmpres)
360                 {
361                   ldap_memfree (attr);
362                   continue; /* Not found:  Try next attribute.  */
363                 }
364             }
365
366           values = ldap_get_values_len (ld, item, attr);
367   
368           if (!values)
369             {
370               if (opt.verbose)
371                 log_info (_("attribute `%s' not found\n"), attr);
372               ldap_memfree (attr);
373               continue;
374             }
375
376           if (opt.verbose)
377             {
378               log_info (_("found attribute `%s'\n"), attr);
379               if (opt.verbose > 1)
380                 for (idx=0; values[idx]; idx++)
381                   log_info ("         length[%d]=%d\n",
382                             idx, (int)values[0]->bv_len);
383               
384             }
385
386           if (opt.multi)
387             { /*  Write attribute marker. */
388               unsigned char tmp[5];
389               size_t n = strlen (attr);
390
391               tmp[0] = 'A';
392               tmp[1] = (n >> 24);
393               tmp[2] = (n >> 16);
394               tmp[3] = (n >> 8);
395               tmp[4] = (n);
396               if (es_fwrite (tmp, 5, 1, es_stdout) != 1 
397                   || es_fwrite (attr, n, 1, es_stdout) != 1)
398                 {
399                   log_error (_("error writing to stdout: %s\n"),
400                              strerror (errno));
401                   ldap_value_free_len (values);
402                   ldap_memfree (attr);
403                   ber_free (berctx, 0);
404                   return -1;
405                 }
406             }
407
408           for (idx=0; values[idx]; idx++)
409             {
410               if (opt.multi)
411                 { /* Write value marker.  */
412                   unsigned char tmp[5];
413                   size_t n = values[0]->bv_len;
414
415                   tmp[0] = 'V';
416                   tmp[1] = (n >> 24);
417                   tmp[2] = (n >> 16);
418                   tmp[3] = (n >> 8);
419                   tmp[4] = (n);
420
421                   if (es_fwrite (tmp, 5, 1, es_stdout) != 1)
422                     {
423                       log_error (_("error writing to stdout: %s\n"),
424                                  strerror (errno));
425                       ldap_value_free_len (values);
426                       ldap_memfree (attr);
427                       ber_free (berctx, 0);
428                       return -1;
429                     }
430                 }
431 #if 1
432               /* Note: this does not work for STDOUT on a Windows
433                  console, where it fails with "Not enough space" for
434                  CRLs which are 52 KB or larger.  */
435 #warning still true - implement in estream
436               if (es_fwrite (values[0]->bv_val, values[0]->bv_len,
437                              1, es_stdout) != 1)
438                 {
439                   log_error (_("error writing to stdout: %s\n"),
440                              strerror (errno));
441                   ldap_value_free_len (values);
442                   ldap_memfree (attr);
443                   ber_free (berctx, 0);
444                   return -1;
445                 }
446 #else
447               /* On Windows console STDOUT, we have to break up the
448                  writes into small parts.  */
449               {
450                 int n = 0;
451                 while (n < values[0]->bv_len)
452                   {
453                     int cnt = values[0]->bv_len - n;
454                     /* The actual limit is (52 * 1024 - 1) on Windows XP SP2.  */
455 #define MAX_CNT (32*1024)
456                     if (cnt > MAX_CNT)
457                       cnt = MAX_CNT;
458                     
459                     if (es_fwrite (((char *) values[0]->bv_val) + n, cnt, 1,
460                                    es_stdout) != 1)
461                       {
462                         log_error (_("error writing to stdout: %s\n"),
463                                    strerror (errno));
464                         ldap_value_free_len (values);
465                         ldap_memfree (attr);
466                         ber_free (berctx, 0);
467                         return -1;
468                       }
469                     n += cnt;
470                   }
471               }
472 #endif
473               any = 1;
474               if (!opt.multi)
475                 break; /* Print only the first value.  */
476             }
477           ldap_value_free_len (values);
478           ldap_memfree (attr);
479           if (want_attr || !opt.multi)
480             break; /* We only want to return the first attribute.  */
481         }
482       ber_free (berctx, 0);
483     } 
484
485   if (opt.verbose > 1 && any)
486     log_info ("result has been printed\n");
487
488   return any?0:-1;
489 }
490
491
492
493 /* Helper for the URL based LDAP query. */
494 static int
495 fetch_ldap (const char *url, const LDAPURLDesc *ludp)
496 {
497   LDAP *ld;
498   LDAPMessage *msg;
499   int rc = 0;
500   char *host, *dn, *filter, *attrs[2], *attr;
501   int port;
502
503   host     = opt.host?   opt.host   : ludp->lud_host;
504   port     = opt.port?   opt.port   : ludp->lud_port;
505   dn       = opt.dn?     opt.dn     : ludp->lud_dn;
506   filter   = opt.filter? opt.filter : ludp->lud_filter;
507   attrs[0] = opt.attr?   opt.attr   : ludp->lud_attrs? ludp->lud_attrs[0]:NULL;
508   attrs[1] = NULL;
509   attr = attrs[0];
510
511   if (!port)
512     port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389;
513
514   if (opt.verbose)
515     {
516       log_info (_("processing url `%s'\n"), url);
517       if (opt.user)
518         log_info (_("          user `%s'\n"), opt.user);
519       if (opt.pass)
520         log_info (_("          pass `%s'\n"), *opt.pass?"*****":"");
521       if (host)
522         log_info (_("          host `%s'\n"), host);
523       log_info (_("          port %d\n"), port);
524       if (dn)
525         log_info (_("            DN `%s'\n"), dn);
526       if (filter)
527         log_info (_("        filter `%s'\n"), filter);
528       if (opt.multi && !opt.attr && ludp->lud_attrs)
529         {
530           int i;
531           for (i=0; ludp->lud_attrs[i]; i++)
532             log_info (_("          attr `%s'\n"), ludp->lud_attrs[i]);
533         }
534       else if (attr)
535         log_info (_("          attr `%s'\n"), attr);
536     }
537
538
539   if (!host || !*host)
540     {
541       log_error (_("no host name in `%s'\n"), url);
542       return -1;
543     }
544   if (!opt.multi && !attr)
545     {
546       log_error (_("no attribute given for query `%s'\n"), url);
547       return -1;
548     }
549
550   if (!opt.multi && !opt.attr
551       && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1])
552     log_info (_("WARNING: using first attribute only\n"));
553
554
555   set_timeout ();
556   ld = ldap_init (host, port);
557   if (!ld)
558     {
559       log_error (_("LDAP init to `%s:%d' failed: %s\n"), 
560                  host, port, strerror (errno));
561       return -1;
562     }
563   if (ldap_simple_bind_s (ld, opt.user, opt.pass))
564     {
565       log_error (_("binding to `%s:%d' failed: %s\n"), 
566                  host, port, strerror (errno));
567       /* FIXME: Need deinit (ld)?  */
568       return -1;
569     }
570
571   set_timeout ();
572   rc = ldap_search_st (ld, dn, ludp->lud_scope, filter,
573                        opt.multi && !opt.attr && ludp->lud_attrs?
574                        ludp->lud_attrs:attrs,
575                        0,
576                        &opt.timeout, &msg);
577   if (rc == LDAP_SIZELIMIT_EXCEEDED && opt.multi)
578     {
579       if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, es_stdout) != 1)
580         {
581           log_error (_("error writing to stdout: %s\n"),
582                      strerror (errno));
583           return -1;
584         }
585     }
586   else if (rc)
587     {
588       log_error (_("searching `%s' failed: %s\n"), 
589                  url, ldap_err2string (rc));
590       if (rc != LDAP_NO_SUCH_OBJECT)
591         {
592           /* FIXME: Need deinit (ld)?  */
593           /* Hmmm: Do we need to released MSG in case of an error? */
594           return -1;
595         }
596     }
597
598   rc = print_ldap_entries (ld, msg, opt.multi? NULL:attr);
599
600   ldap_msgfree (msg);
601   /* FIXME: Need deinit (ld)?  */
602   return rc;
603 }
604
605
606
607
608 /* Main processing.  Take the URL and run the LDAP query. The result
609    is printed to stdout, errors are logged to the log stream. */
610 static int
611 process_url (const char *url)
612 {
613   int rc;
614   LDAPURLDesc *ludp = NULL;
615
616
617   if (!ldap_is_ldap_url (url))
618     {
619       log_error (_("`%s' is not an LDAP URL\n"), url);
620       return -1;
621     }
622
623   if (ldap_url_parse (url, &ludp))
624     {
625       log_error (_("`%s' is an invalid LDAP URL\n"), url);
626       return -1;
627     }
628
629   rc = fetch_ldap (url, ludp);
630
631   ldap_free_urldesc (ludp);
632   return rc;
633 }
634