340ee02180e96bf7a49c876e580df4e07d18203a
[poldi.git] / src / pam / pam_poldi.c
1 /* pam_poldi.c - PAM authentication via OpenPGP smartcards.
2    Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
3  
4    This file is part of Poldi.
5  
6    Poldi is free software; you can redistribute it and/or modify it
7    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    Poldi is distributed in the hope that it will be useful, but
12    WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    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
18    <http://www.gnu.org/licenses/>.  */
19
20 #include <poldi.h>
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <syslog.h>
25 #include <stdarg.h>
26 #include <errno.h>
27 #include <pwd.h>
28 #include <assert.h>
29
30 #define PAM_SM_AUTH
31 #include <security/pam_modules.h>
32
33 #include "util/simplelog.h"
34 #include "util/simpleparse.h"
35 #include "util/defs.h"
36 #include "scd/scd.h"
37
38 #include "auth-support/wait-for-card.h"
39 #include "auth-support/pam-util.h"
40 #include "auth-support/conv.h"
41 #include "auth-support/getpin-cb.h"
42 #include "auth-methods.h"
43
44 \f
45
46 /*** Auth methods declarations. ***/
47
48 /* Declare authentication methods.  */
49 extern struct auth_method_s auth_method_localdb;
50 extern struct auth_method_s auth_method_x509;
51
52 /* List element type for AUTH_METHODS list below.  */
53 struct auth_method
54 {
55   const char *name;
56   auth_method_t method;
57 };
58
59 /* List associating authenting method definitions with their
60    names.  */
61 static struct auth_method auth_methods[] =
62   {
63 #ifdef ENABLE_AUTH_METHOD_LOCALDB
64     { "localdb", &auth_method_localdb },
65 #endif
66 #ifdef ENABLE_AUTH_METHOD_X509
67     { "x509", &auth_method_x509 },
68 #endif
69     { NULL }
70   };
71
72 \f
73
74 /*** Option parsing. ***/
75
76 /* IDs for supported options. */
77 enum opt_ids
78   {
79     opt_none,
80     opt_logfile,
81     opt_auth_method,
82     opt_debug,
83     opt_scdaemon_socket,
84     opt_scdaemon_program
85   };
86
87 /* Full specifications for options. */
88 static simpleparse_opt_spec_t opt_specs[] =
89   {
90     { opt_logfile, "log-file",
91       0, SIMPLEPARSE_ARG_REQUIRED, 0, "Specify file to user for logging" },
92     { opt_auth_method, "auth-method",
93       0, SIMPLEPARSE_ARG_REQUIRED, 0, "Specify authentication method" },
94     { opt_debug, "debug",
95       0, SIMPLEPARSE_ARG_NONE,     0, "Enable debugging mode" },
96     { opt_scdaemon_socket, "scdaemon-socket",
97       0, SIMPLEPARSE_ARG_REQUIRED, 0, "Specify socket of system scdaemon" },
98     { opt_scdaemon_program, "scdaemon-program",
99       0, SIMPLEPARSE_ARG_REQUIRED, 0, "Specify scdaemon executable to use" },
100     { 0 }
101   };
102
103 /* Lookup an auth_method struct by it's NAME, return it's index in
104    AUTH_METHODS list or -1 if lookup failed.  */
105 static int
106 auth_method_lookup (const char *name)
107 {
108   int i;
109
110   for (i = 0; auth_methods[i].name; i++)
111     if (strcmp (auth_methods[i].name, name) == 0)
112       break;
113
114   if (auth_methods[i].name)
115     return i;
116   else
117     return -1;
118 }
119
120 /* Callback for authentication method independent option parsing. */
121 static gpg_error_t
122 pam_poldi_options_cb (void *cookie, simpleparse_opt_spec_t spec, const char *arg)
123 {
124   gpg_err_code_t err = GPG_ERR_NO_ERROR;
125   poldi_ctx_t ctx = cookie;
126
127   if (!strcmp (spec.long_opt, "log-file"))
128     {
129       /* LOG-FILE.  */
130       ctx->logfile = xtrystrdup (arg);
131       if (!ctx->logfile)
132         {
133           err = gpg_error_from_errno (errno);
134           log_msg_error (ctx->loghandle,
135                          _("failed to duplicate %s: %s"),
136                          "logfile name", gpg_strerror (err));
137         }
138     }
139   else if (!strcmp (spec.long_opt, "scdaemon-socket"))
140     {
141       /* SCDAEMON-SOCKET.  */
142
143       ctx->scdaemon_socket = xtrystrdup (arg);
144       if (!ctx->scdaemon_socket)
145         {
146           err = gpg_error_from_errno (errno);
147           log_msg_error (ctx->loghandle,
148                          _("failed to duplicate %s: %s"),
149                          "scdaemon socket name",
150                          gpg_strerror (err));
151         }
152     }
153   else if (!strcmp (spec.long_opt, "scdaemon-program"))
154     {
155       /* SCDAEMON-PROGRAM.  */
156
157       ctx->scdaemon_program = strdup (arg);
158       if (!ctx->scdaemon_program)
159         {
160           err = gpg_error_from_errno (errno);
161           log_msg_error (ctx->loghandle,
162                          _("failed to duplicate %s: %s"),
163                          "scdaemon program name",
164                          gpg_strerror (err));
165         }
166     }
167   else if (!strcmp (spec.long_opt, "auth-method"))
168     {
169       /* AUTH-METHOD.  */
170
171       int method = auth_method_lookup (arg);
172       if (method >= 0)
173         ctx->auth_method = method;
174       else
175         {
176           log_msg_error (ctx->loghandle,
177                          _("unknown authentication method '%s'"),
178                          arg);
179           err = GPG_ERR_INV_VALUE;
180         }
181     }
182   else if (!strcmp (spec.long_opt, "debug"))
183     {
184       /* DEBUG.  */
185       ctx->debug = 1;
186       log_set_min_level (ctx->loghandle, LOG_LEVEL_DEBUG);
187     }
188
189   return gpg_error (err);
190 }
191
192 /* This callback is used for simpleparse. */
193 static const char *
194 i18n_cb (void *cookie, const char *msg)
195 {
196   return _(msg);
197 }
198
199 \f
200
201 static struct poldi_ctx_s poldi_ctx_NULL; /* For initialization
202                                              purpose. */
203
204 /* Create new, empty Poldi context.  Return proper error code.   */
205 static gpg_error_t
206 create_context (poldi_ctx_t *context, pam_handle_t *pam_handle)
207 {
208   gpg_error_t err;
209   poldi_ctx_t ctx;
210
211   err = 0;
212
213   /* Allocate. */
214   ctx = xtrymalloc (sizeof (*ctx));
215   if (!ctx)
216     {
217       err = gpg_error_from_errno (errno);
218       goto out;
219     }
220
221   /* Initialize. */
222
223   *ctx = poldi_ctx_NULL;
224
225   ctx->auth_method = -1;
226   ctx->cardinfo = scd_cardinfo_null;
227   ctx->pam_handle = pam_handle;
228
229   err = log_create (&ctx->loghandle);
230   if (err)
231     goto out;
232
233   err = simpleparse_create (&ctx->parsehandle);
234   if (err)
235     goto out;
236
237   simpleparse_set_loghandle (ctx->parsehandle, ctx->loghandle);
238   simpleparse_set_parse_cb (ctx->parsehandle, pam_poldi_options_cb, ctx);
239   simpleparse_set_specs (ctx->parsehandle, opt_specs);
240   simpleparse_set_i18n_cb (ctx->parsehandle, i18n_cb, NULL);
241
242   *context = ctx;
243
244  out:
245
246   if (err)
247     {
248       if (ctx)
249         {
250           simpleparse_destroy (ctx->parsehandle);
251           log_destroy (ctx->loghandle);
252           xfree (ctx);
253         }
254     }
255
256   return err;
257 }
258
259 /* Deallocates resources associated with context CTX. */
260 static void
261 destroy_context (poldi_ctx_t ctx)
262 {
263   if (ctx)
264     {
265       xfree (ctx->logfile);
266       simpleparse_destroy (ctx->parsehandle);
267       log_destroy (ctx->loghandle);
268       xfree (ctx->scdaemon_socket);
269       xfree (ctx->scdaemon_program);
270       scd_disconnect (ctx->scd);
271       scd_release_cardinfo (ctx->cardinfo);
272       /* FIXME: not very consistent: conv is (de-)allocated by caller. -mo */
273       xfree (ctx);
274     }
275 }
276
277 \f
278
279 /*
280  * PAM interface.
281  */
282
283 /* Uaaahahahh, ich will dir einloggen!  PAM authentication entry
284    point.  */
285 PAM_EXTERN int
286 pam_sm_authenticate (pam_handle_t *pam_handle,
287                      int flags, int argc, const char **argv)
288 {
289   const void *conv_void;
290   gpg_error_t err; 
291   poldi_ctx_t ctx;
292   conv_t conv;
293   scd_context_t scd_ctx;
294   int ret;
295   const char *pam_username;
296   struct auth_method_parse_cookie method_parse_cookie = { NULL, NULL };
297   simpleparse_handle_t method_parse;
298   struct getpin_cb_data getpin_cb_data;
299
300   pam_username = NULL;
301   scd_ctx = NULL;
302   conv = NULL;
303   ctx = NULL;
304   method_parse = NULL;
305   err = 0;
306
307   /*** Basic initialization. ***/
308
309   bindtextdomain (PACKAGE, LOCALEDIR);
310
311   /* Initialize Libgcrypt.  Disable secure memory for now; because of
312      the implicit priviledge dropping, having secure memory enabled
313      causes the following error:
314
315      su: Authentication service cannot retrieve authentication
316      info. */
317   gcry_control (GCRYCTL_DISABLE_SECMEM);
318
319   /*** Setup main context.  ***/
320
321   err = create_context (&ctx, pam_handle);
322   if (err)
323     goto out;
324
325   /* Setup logging prefix.  */
326   log_set_flags (ctx->loghandle,
327                  LOG_FLAG_WITH_PREFIX | LOG_FLAG_WITH_TIME | LOG_FLAG_WITH_PID);
328   log_set_prefix (ctx->loghandle, "Poldi");
329   log_set_backend_syslog (ctx->loghandle);
330
331   /*** Parse auth-method independent options.  ***/
332
333   /* ... from configuration file:  */
334   err = simpleparse_parse_file (ctx->parsehandle, 0, POLDI_CONF_FILE);
335   if (err)
336     {
337       log_msg_error (ctx->loghandle,
338                      _("failed to parse configuration file: %s"),
339                      gpg_strerror (err));
340       goto out;
341     }
342
343   /* ... and from argument vector provided by PAM: */
344   if (argc)
345     {
346       err = simpleparse_parse (ctx->parsehandle, 0, argc, argv, NULL);
347       if (err)
348         {
349           log_msg_error (ctx->loghandle,
350                          _("failed to parse PAM argument vector: %s"),
351                          gpg_strerror (err));
352           goto out;
353         }
354     }
355
356   /*** Initialize logging. ***/
357
358   /* In case `logfile' has been set in the configuration file,
359      initialize jnlib-logging the traditional file, loggin to the file
360      (or socket special file) specified in the configuration file; in
361      case `logfile' has NOT been set in the configuration file, log
362      through Syslog.  */
363   if (ctx->logfile)
364     {
365       gpg_error_t rc;
366
367       rc = log_set_backend_file (ctx->loghandle, ctx->logfile);
368       if (rc != 0)
369         /* Last try...  */
370         log_set_backend_syslog (ctx->loghandle);
371     }
372
373   /*** Sanity checks. ***/
374
375   /* Authentication method to use must be specified.  */
376   if (ctx->auth_method < 0)
377     {
378       log_msg_error (ctx->loghandle,
379                      _("no authentication method specified"));
380       err = GPG_ERR_CONFIGURATION;
381       goto out;
382     }
383
384   /* Authentication methods must provide a parser callback in case
385      they have specific a configuration file.  */
386   assert ((!auth_methods[ctx->auth_method].method->config)
387           || (auth_methods[ctx->auth_method].method->parsecb
388               && auth_methods[ctx->auth_method].method->opt_specs));
389
390   if (ctx->debug)
391     {
392       log_msg_debug (ctx->loghandle,
393                      _("using authentication method `%s'"),
394                      auth_methods[ctx->auth_method].name);
395       if (ctx->scdaemon_socket)
396         log_msg_debug (ctx->loghandle,
397                        _("using system scdaemon; socket is '%s'"),
398                        ctx->scdaemon_socket);
399     }
400
401   /*** Init authentication method.  ***/
402   
403   if (auth_methods[ctx->auth_method].method->func_init)
404     {
405       err = (*auth_methods[ctx->auth_method].method->func_init) (&ctx->cookie);
406       if (err)
407         {
408           log_msg_error (ctx->loghandle,
409                          _("failed to initialize authentication method %i: %s"),
410                          -1, gpg_strerror (err));
411           goto out;
412         }
413     }
414
415   if (auth_methods[ctx->auth_method].method->config)
416     {
417       /* Do auth-method specific parsing. */
418
419       err = simpleparse_create (&method_parse);
420       if (err)
421         {
422           log_msg_error (ctx->loghandle,
423                          _("failed to initialize parsing of configuration file for authentication method %s: %s"),
424                          auth_methods[ctx->auth_method].name, gpg_strerror (err));
425           goto out_parsing;
426         }
427
428       method_parse_cookie.poldi_ctx = ctx;
429       method_parse_cookie.method_ctx = ctx->cookie;
430
431       simpleparse_set_loghandle (method_parse, ctx->loghandle);
432       simpleparse_set_parse_cb (method_parse,
433                                 auth_methods[ctx->auth_method].method->parsecb,
434                                 &method_parse_cookie);
435       simpleparse_set_i18n_cb (method_parse, i18n_cb, NULL);
436       simpleparse_set_specs (method_parse,
437                              auth_methods[ctx->auth_method].method->opt_specs);
438
439       err = simpleparse_parse_file (method_parse, 0, 
440                                     auth_methods[ctx->auth_method].method->config);
441       if (err)
442         {
443           log_msg_error (ctx->loghandle,
444                          _("failed to parse configuration for authentication method %i: %s"),
445                          auth_methods[ctx->auth_method].name, gpg_strerror (err));
446           goto out_parsing;
447         }
448
449     out_parsing:
450
451       simpleparse_destroy (method_parse);
452       if (err)
453         goto out;
454     }
455
456   /*** Prepare PAM interaction.  ***/
457
458   /* Ask PAM for conv structure.  */
459   ret = pam_get_item (ctx->pam_handle, PAM_CONV, &conv_void);
460   if (ret != PAM_SUCCESS)
461     {
462       log_msg_error (ctx->loghandle,
463                      _("failed to retrieve PAM conversation structure"));
464       err = GPG_ERR_INTERNAL;
465       goto out;
466     }
467
468   /* Init conv subsystem by creating a conv_t object.  */
469   err = conv_create (&conv, conv_void);
470   if (err)
471     goto out;
472
473   ctx->conv = conv;
474
475   /*** Retrieve username from PAM.  ***/
476
477   err = retrieve_username_from_pam (ctx->pam_handle, &pam_username);
478   if (err)
479     {
480       log_msg_error (ctx->loghandle,
481                      _("failed to retrieve username from PAM: %s"),
482                      gpg_strerror (err));
483     }
484
485   /*** Connect to Scdaemon. ***/
486
487   err = scd_connect (&scd_ctx,
488                      ctx->scdaemon_socket, getenv ("GPG_AGENT_INFO"),
489                      ctx->scdaemon_program, 0, ctx->loghandle);
490   if (err)
491     goto out;
492
493   ctx->scd = scd_ctx;
494
495   /* Install PIN retrival callback. */
496   getpin_cb_data.poldi_ctx = ctx;
497   scd_set_pincb (ctx->scd, getpin_cb, &getpin_cb_data);
498
499   /*** Wait for card insertion.  ***/
500
501   if (pam_username)
502     conv_tell (ctx->conv, _("Waiting for card for user `%s'..."), pam_username);
503   else
504     conv_tell (ctx->conv, _("Waiting for card..."));
505
506   err = wait_for_card (ctx->scd, 0);
507   if (err)
508     {
509       log_msg_error (ctx->loghandle,
510                      _("failed to wait for card insertion: %s"),
511                      gpg_strerror (err));
512       goto out;
513     }
514
515   /*** Receive card info. ***/
516
517   err = scd_learn (ctx->scd, &ctx->cardinfo);
518   if (err)
519     goto out;
520
521   if (ctx->debug)
522     log_msg_debug (ctx->loghandle,
523                    _("connected to card; serial number is: %s"),
524                    ctx->cardinfo.serialno);
525
526   /*** Authenticate.  ***/
527
528   if (pam_username)
529     {
530       /* Try to authenticate user as PAM_USERNAME.  */
531
532       if (!(*auth_methods[ctx->auth_method].method->func_auth_as) (ctx, ctx->cookie,
533                                                                    pam_username))
534         /* Authentication failed.  */
535         err = GPG_ERR_GENERAL;
536     }
537   else
538     {
539       /* Try to authenticate user, choosing an identity is up to the
540          user.  */
541
542       char *username_authenticated = NULL;
543
544       if (!(*auth_methods[ctx->auth_method].method->func_auth) (ctx, ctx->cookie,
545                                                                 &username_authenticated))
546         /* Authentication failed.  */
547         err = GPG_ERR_GENERAL;
548       else
549         {
550           /* Send username received during authentication process back
551              to PAM.  */
552           err = send_username_to_pam (ctx->pam_handle, username_authenticated);
553           xfree (username_authenticated);
554         }
555     }
556
557  out:
558
559   /* Log result.  */
560   if (err)
561     log_msg_error (ctx->loghandle, _("authentication failed: %s"), gpg_strerror (err));
562   else if (ctx->debug)
563     log_msg_debug (ctx->loghandle, _("authentication succeeded"));
564
565   /* Call authentication method's deinit callback. */
566   if ((ctx->auth_method >= 0)
567       && auth_methods[ctx->auth_method].method->func_deinit)
568     (*auth_methods[ctx->auth_method].method->func_deinit) (ctx->cookie);
569
570   /* FIXME, cosmetics? */
571   conv_destroy (conv);
572   destroy_context (ctx);
573
574   /* Return to PAM.  */
575
576   return err ? PAM_AUTH_ERR : PAM_SUCCESS;
577 }
578
579 /* PAM's `set-credentials' interface.  */
580 PAM_EXTERN int
581 pam_sm_setcred (pam_handle_t *pam_handle,
582                 int flags, int argc, const char **argv)
583 {
584   /* FIXME: do we need this?  -mo */
585   return PAM_SUCCESS;
586 }
587
588 /* END */