pam/ChangeLog:
[poldi.git] / src / pam / pam_poldi.c
1 /* poldi.c - PAM authentication via OpenPGP smartcards.
2    Copyright (C) 2004, 2005 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 2 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 Lesser General Public
17    License along with this program; if not, write to the Free Software
18    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19    02111-1307, USA.  */
20
21 #include <config.h>
22
23 #include <stdlib.h>
24 #include <string.h>
25 #include <syslog.h>
26 #include <stdarg.h>
27 #include <errno.h>
28 #include <pwd.h>
29
30 #define PAM_SM_AUTH
31 #include <security/pam_modules.h>
32
33 #include <gcrypt.h>
34
35 #include <jnlib/xmalloc.h>
36 #include <jnlib/logging.h>
37 #include <common/support.h>
38 #include <common/options.h>
39 #include <common/card.h>
40 #include <common/defs.h>
41 #include <libscd/scd.h>
42
43 \f
44
45 /* Macros.  */
46
47 \f
48
49 /* Option structure layout.  */
50 struct pam_poldi_opt
51 {
52   unsigned int debug; /* Enable debugging.  */
53   int debug_sc;
54   int verbose;
55   const char *ctapi_driver; /* Library to access the ctAPI. */
56   const char *pcsc_driver;  /* Library to access the PC/SC system. */
57   const char *reader_port;  /* NULL or reder port to use. */
58   int disable_opensc;  /* Disable the use of the OpenSC framework. */
59   int disable_ccid;    /* Disable the use of the internal CCID driver. */
60   int debug_ccid_driver;        /* Debug the internal CCID driver.  */
61   int require_card_switch;
62   const char *logfile;
63   unsigned int wait_timeout;
64 };
65
66 /* Option structure definition.  */
67 struct pam_poldi_opt pam_poldi_opt =
68   {
69     0,
70     0,
71     0,
72     NULL,
73     NULL,
74     NULL,
75     0,
76     0,
77     0,
78     0,
79     NULL,
80     0
81   };
82
83 /* Option IDs.  */
84 enum arg_opt_ids
85   {
86     arg_debug = 500,
87     arg_debug_sc,
88     arg_verbose,
89     arg_ctapi_driver,
90     arg_pcsc_driver,
91     arg_reader_port,
92     arg_disable_opensc,
93     arg_disable_ccid,
94     arg_debug_ccid_driver,
95     arg_require_card_switch,
96     arg_logfile,
97     arg_wait_timeout
98   };
99
100 /* Option specifications. */
101 static ARGPARSE_OPTS arg_opts[] =
102   {
103     { arg_debug,
104       "debug", 256, "Debug PAM-Poldi" },
105     { arg_debug_sc,
106       "debug-sc", 256, "Debug sc FIXME" },
107     { arg_ctapi_driver,
108       "ctapi-driver", 2, "|NAME|use NAME as ct-API driver" },
109     { arg_pcsc_driver,
110       "pcsc-driver", 2,  "|NAME|use NAME as PC/SC driver" },
111     { arg_reader_port,
112       "reader-port", 2, "|N|connect to reader at port N" },
113 #ifdef HAVE_LIBUSB
114     { arg_disable_ccid,
115       "disable-ccid", 0, "do not use the internal CCID driver" },
116     { arg_debug_ccid_driver,
117       "debug-ccid-driver", 0, "debug the  internal CCID driver" },
118 #endif
119 #ifdef HAVE_OPENSC
120     { arg_disable_opensc,
121       "disable-opensc", 0, "do not use the OpenSC layer" },
122 #endif
123     { arg_require_card_switch,
124       "require-card-switch", 0, "Require re-insertion of card" },
125     { arg_logfile,
126       "log-file", 2, "Specify file to use for logging" },
127     { arg_wait_timeout,
128       "wait-timeout", 1, "|SEC|Specify timeout for waiting" },
129     { 0 }
130   };
131
132 /* Option parser callback.  */
133 static gpg_error_t
134 pam_poldi_options_cb (ARGPARSE_ARGS *parg, void *opaque)
135 {
136   gpg_err_code_t err = GPG_ERR_NO_ERROR;
137
138   switch (parg->r_opt)
139     {
140     case arg_debug:
141       pam_poldi_opt.debug = 1;
142       break;
143
144     case arg_debug_sc:
145       pam_poldi_opt.debug_sc = 1;
146       break;
147
148     case arg_ctapi_driver:
149       pam_poldi_opt.ctapi_driver = xstrdup (parg->r.ret_str);
150       break;
151
152     case arg_pcsc_driver:
153       pam_poldi_opt.pcsc_driver = xstrdup (parg->r.ret_str);
154       break;
155
156     case arg_reader_port:
157       pam_poldi_opt.reader_port = xstrdup (parg->r.ret_str);
158       break;
159
160     case arg_disable_ccid:
161       pam_poldi_opt.disable_ccid = 1;
162       break;
163
164     case arg_disable_opensc:
165       pam_poldi_opt.disable_opensc = 1;
166       break;
167
168     case arg_debug_ccid_driver:
169       pam_poldi_opt.debug_ccid_driver = 1;
170       break;
171
172     case arg_require_card_switch:
173       pam_poldi_opt.require_card_switch = 1;
174       break;
175
176     case arg_logfile:
177       pam_poldi_opt.logfile = xstrdup (parg->r.ret_str);
178       break;
179
180     case arg_wait_timeout:
181       pam_poldi_opt.wait_timeout = parg->r.ret_int;
182       break;
183
184     default:
185       err = GPG_ERR_INTERNAL;   /* FIXME?  */
186       break;
187     }
188
189   return gpg_error (err);
190 }
191
192 \f
193
194 /*
195  * PAM user interaction through PAM conversation functions.
196  */
197
198 /* This function queries the PAM user for input through the
199    conversation function CONV; TEXT will be displayed as prompt, the
200    user's response will be stored in *RESPONSE.  Returns proper error
201    code.  */
202 static gpg_error_t
203 ask_user (const struct pam_conv *conv, const char *text, char **response)
204 {
205   struct pam_message messages[1] = { { PAM_PROMPT_ECHO_OFF, text } };
206   const struct pam_message *pmessages[1] = { &messages[0] };
207   struct pam_response *responses = NULL;
208   char *response_new;
209   gpg_error_t err;
210   int ret;
211
212   response_new = NULL;
213
214   ret = (*conv->conv) (sizeof (messages) / (sizeof (*messages)), pmessages,
215                        &responses, conv->appdata_ptr);
216   if (ret != PAM_SUCCESS)
217     {
218       err = gpg_error (GPG_ERR_INTERNAL);
219       goto out;
220     }
221
222   if (response)
223     {
224       response_new = strdup (responses[0].resp);
225       if (! response_new)
226         {
227           err = gpg_error_from_errno (errno);
228           goto out;
229         }
230     }
231
232   err = 0;
233   if (response)
234     *response = response_new;
235
236  out:
237
238   return err;
239 }
240
241 /* This function queries the PAM user for input through the
242    conversation function CONV; TEXT will be displayed as prompt, the
243    user's response will be stored in *RESPONSE.  Returns proper error
244    code.  */
245 static gpg_error_t
246 tell_user (const struct pam_conv *conv, char *fmt, ...)
247 {
248   struct pam_message messages[1] = { { PAM_TEXT_INFO, NULL } };
249   const struct pam_message *pmessages[1] = { &messages[0] };
250   struct pam_response *responses = NULL;
251   gpg_error_t err;
252   char *string;
253   va_list ap;
254   int ret;
255
256   string = NULL;
257
258   va_start (ap, fmt);
259   ret = vasprintf (&string, fmt, ap);
260   if (ret < 0)
261     {
262       err = gpg_error_from_errno (errno);
263       goto out;
264     }
265   va_end (ap);
266
267   messages[0].msg = string;
268   
269   ret = (*conv->conv) (sizeof (messages) / (sizeof (*messages)), pmessages,
270                        &responses, conv->appdata_ptr);
271   if (ret != PAM_SUCCESS)
272     {
273       err = gpg_error (GPG_ERR_INTERNAL);
274       goto out;
275     }
276
277   err = 0;
278
279  out:
280
281   free (string);
282
283   return err;
284 }
285
286 \f
287
288 /*
289  * Helper functions.
290  */
291
292 /* Lookup the key belonging to the user specified by USERNAME.
293    Returns a proper error code.  */
294 static gpg_error_t
295 lookup_key (const char *username, gcry_sexp_t *key)
296 {
297   gcry_sexp_t key_sexp;
298   char *key_string;
299   char *key_path;
300   char *serialno;
301   gpg_error_t err;
302
303   serialno = NULL;
304   key_path = NULL;
305   key_string = NULL;
306
307   err = usersdb_lookup_by_username (username, &serialno);
308   if (err)
309     goto out;
310
311   err = key_filename_construct (&key_path, serialno);
312   if (err)
313     goto out;
314
315   err = file_to_string (key_path, &key_string);
316   if ((! err) && (! key_string))
317     err = gpg_error (GPG_ERR_NO_PUBKEY);
318   if (err)
319     goto out;
320
321   err = string_to_sexp (&key_sexp, key_string);
322   if (err)
323     goto out;
324
325   *key = key_sexp;
326
327  out:
328
329   free (key_path);
330   free (key_string);
331   free (serialno);
332
333   return err;
334 }
335
336 /* Wait for insertion of a card in slot specified by SLOT,
337    communication with the user through the PAM conversation function
338    CONV.  If REQUIRE_CARD_SWITCH is TRUE, require a card switch.  The
339    serial number of the inserted card will be stored in a newly
340    allocated string in **SERIALNO.  Returns proper error code.  */
341 static gpg_error_t
342 wait_for_card (int slot, int require_card_switch,
343                const struct pam_conv *conv, char **serialno)
344 {
345   char *serialno_new;
346   gpg_error_t err;
347
348   err = tell_user (conv, "Insert card ...");
349   if (err)
350     goto out;
351
352   err = card_init (slot, 1, pam_poldi_opt.wait_timeout, require_card_switch);
353   if (err)
354     {
355       if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
356         tell_user (conv, "Timeout inserting card");
357       goto out;
358     }
359
360   err = card_info (slot, &serialno_new, NULL, NULL);
361   if (err)
362     goto out;
363
364   *serialno = serialno_new;
365
366  out:
367
368   return err;
369 }
370
371 /* This function parses the PAM argument vector ARGV of size ARGV */
372 static gpg_error_t
373 parse_argv (int argc, const char **argv)
374 {
375   gpg_error_t err;
376   unsigned int i;
377
378   err = 0;
379   for (i = 0; i < argc; i++)
380     {
381       if (! strcmp (argv[i], "debug"))
382         {
383           /* Handle "debug" option.  */
384           pam_poldi_opt.debug = ~0;
385           pam_poldi_opt.debug_sc = 1;
386           pam_poldi_opt.verbose = 1;
387           pam_poldi_opt.debug_ccid_driver = 1;
388         }
389       else if (! strncmp (argv[i], "timeout=", 8))
390         /* Handle "timeout=" option.  */
391         pam_poldi_opt.wait_timeout = atoi (argv[i] + 8);
392       else
393         {
394           log_error ("Unknown PAM argument: %s", argv[i]);
395           err = gpg_error (GPG_ERR_UNKNOWN_NAME);
396         }
397
398       if (err)
399         break;
400     }
401
402   return err;
403 }
404
405 \f
406
407 /* Core authentication function.  Tries to authenticate the card
408    specified by SLOT against the public key KEY, using CONV as PAM
409    conversation function.  Returns proper error code.  */
410 static gpg_error_t
411 do_auth (int slot, const struct pam_conv *conv, gcry_sexp_t key)
412 {
413   unsigned char *challenge;
414   unsigned char *response;
415   size_t challenge_n;
416   size_t response_n;
417   gpg_error_t err;
418   char *pin;
419
420   challenge = NULL;
421   response = NULL;
422   pin = NULL;
423
424   /* Query user for PIN.  */
425   err = ask_user (conv, POLDI_PIN2_QUERY_MSG, &pin);
426   if (err)
427     goto out;
428
429   /* Send PIN to card.  */
430   err = card_pin_provide (slot, 2, pin);
431   if (err)
432     goto out;
433
434   /* Generate challenge.  */
435   err = challenge_generate (&challenge, &challenge_n);
436   if (err)
437     goto out;
438
439   /* Let card sign the challenge.  */
440   err = card_sign (slot, challenge, challenge_n, &response, &response_n);
441   if (err)
442     goto out;
443
444   /* Verify response.  */
445   err = challenge_verify (key, challenge, challenge_n, response, response_n);
446
447  out:
448
449   /* Release resources.  */
450
451   free (challenge);
452   free (response);
453   free (pin);
454
455   return err;
456 }
457
458 \f
459
460 /* Uaaahahahh, ich will dir einloggen!  */
461 PAM_EXTERN int
462 pam_sm_authenticate (pam_handle_t *pam_handle,
463                      int flags, int argc, const char **argv)
464 {
465   const void *conv_void;
466   const struct pam_conv *conv;
467   gcry_sexp_t key;
468   gpg_error_t err;
469   const void *username_void;
470   const char *username;
471   char *serialno;
472   char *account;
473   int slot;
474   int ret;
475
476   serialno = NULL;
477   account = NULL;
478   slot = -1;
479   key = NULL;
480
481   /* Parse options.  */
482   err = options_parse_conf  (pam_poldi_options_cb, NULL,
483                              arg_opts, POLDI_CONF_FILE);
484   if (err)
485     goto out;
486
487   /* Parse argument vector provided by PAM.  */
488   err = parse_argv (argc, argv);
489   if (err)
490     goto out;
491
492   if (pam_poldi_opt.logfile)
493     {
494       log_set_file (pam_poldi_opt.logfile);
495       if (! strcmp (pam_poldi_opt.logfile, "-"))
496         /* We need to disable bufferring on stderr, since it might
497            have been enabled by log_set_file().  Buffering on stderr
498            will complicate PAM interaction, since e.g. libpam-misc's
499            misc_conv() function does expect stderr to be
500            unbuffered.  */
501         setvbuf (stderr, NULL, _IONBF, 0);
502     }
503   else
504     log_set_syslog ("poldi", LOG_AUTH);
505
506   scd_init (pam_poldi_opt.debug,
507             pam_poldi_opt.debug_sc,
508             pam_poldi_opt.verbose,
509             pam_poldi_opt.ctapi_driver,
510             pam_poldi_opt.reader_port,
511             pam_poldi_opt.pcsc_driver,
512             pam_poldi_opt.disable_opensc,
513             pam_poldi_opt.disable_ccid,
514             pam_poldi_opt.debug_ccid_driver);
515
516   /* Ask PAM for username.  */
517   ret = pam_get_item (pam_handle, PAM_USER, &username_void);
518   if (ret != PAM_SUCCESS)
519     {
520       err = gpg_error (GPG_ERR_INTERNAL);
521       goto out;
522     }
523   username = username_void;
524
525   /* Ask PAM for conv structure.  */
526   ret = pam_get_item (pam_handle, PAM_CONV, &conv_void);
527   if (ret != PAM_SUCCESS)
528     {
529       log_error ("Failed to retrieve conversation structure");
530       err = GPG_ERR_INTERNAL;
531       goto out;
532     }
533   conv = conv_void;
534
535   /* Open card slot.  */
536   err = card_open (NULL, &slot);
537   if (err)
538     goto out;
539
540   if (username)
541     {
542       /* We got a username from PAM, thus we are waiting for a
543          specific card.  */
544
545       /* We do not need the key right now already, but it seems to be
546          a good idea to make the login fail before waiting for card in
547          case no key has been installed for that card.  */
548       err = lookup_key (username, &key);
549       if (err)
550         goto out;
551
552       /* Wait for card.  */
553       err = wait_for_card (slot, pam_poldi_opt.require_card_switch,
554                            conv, &serialno);
555       if (err)
556         goto out;
557
558       /* Lookup account for given card.  */
559       err = usersdb_lookup_by_serialno (serialno, &account);
560       if (err || strcmp (account, username))
561         {
562           /* Either the account could not be found or it is not the
563              expected one -> fail.  */
564           tell_user (conv, "Serial no %s is not associated with %s",
565                      serialno, username);
566           if (! err)
567             err = gpg_error (GPG_ERR_INV_NAME);
568         }
569       else
570         /* Inform user about inserted card.  */
571         err = tell_user (conv, "Serial no: %s", serialno);
572       if (err)
573         goto out;
574
575       /* It is the correct card, try authentication with given
576          key.  */
577       err = do_auth (slot, conv, key);
578       if (err)
579         goto out;
580     }
581   else
582     {
583       /* No username has been provided by PAM, thus we accept any
584          card.  */
585
586       /* Wait for card.  */
587       err = wait_for_card (slot, pam_poldi_opt.require_card_switch,
588                            conv, &serialno);
589       if (err)
590         goto out;
591
592       /* Inform user about inserted card.  */
593       err = tell_user (conv, "Serial no: %s", serialno);
594       if (err)
595         goto out;
596
597       /* Lookup account for inserted card.  */
598       err = usersdb_lookup_by_serialno (serialno, &account);
599       if (err)
600         goto out;
601
602       /* Inform user about looked up account.  */
603       err = tell_user (conv, "Account: %s", account);
604       if (err)
605         goto out;
606
607       /* Lookup key for looked up account.  */
608       err = lookup_key (account, &key);
609       if (err)
610         goto out;
611
612       /* Try authentication with looked up key.  */
613       err = do_auth (slot, conv, key);
614       if (err)
615         goto out;
616
617       /* Make username available to application.  */
618       ret = pam_set_item (pam_handle, PAM_USER, account);
619       if (ret != PAM_SUCCESS)
620         {
621           err = gpg_error (GPG_ERR_INTERNAL);
622           goto out;
623         }
624     }
625
626   /* Done.  */
627
628  out:
629
630   /* Release resources.  */
631   gcry_sexp_release (key);
632   free (serialno);
633   free (account);
634   if (slot != -1)
635     card_close (slot);
636
637   /* Log result.  */
638   if (err)
639     log_error ("Failure: %s\n", gpg_strerror (err));
640   else
641     log_info ("Success\n");
642
643   /* FIXME, should be done by logging.c somehow.  */
644   closelog ();
645
646   /* Return to PAM.  */
647
648   return err ? PAM_AUTH_ERR : PAM_SUCCESS;
649 }
650
651 PAM_EXTERN int
652 pam_sm_setcred (pam_handle_t *pam_handle,
653                 int flags, int argc, const char **argv)
654 {
655   /* FIXME?  */
656   return PAM_SUCCESS;
657 }
658
659 /* END. */