3fca1e7510130b5e56df6e5e4c5300d8e0e2085c
[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/stringhelp.h>
37 #include <jnlib/logging.h>
38 #include <common/support.h>
39 #include <common/options.h>
40 #include <common/card.h>
41 #include <common/defs.h>
42 #include <libscd/scd.h>
43
44 #define POLDI_LOG_FACILITY AUTH
45
46 #define STR_CONCAT(a, b) a ## b
47
48 #define POLDI_LOG_DO(facility, priority, format, args ...) \
49   syslog (LOG_MAKEPRI (STR_CONCAT (LOG_, facility), LOG_ ## priority), \
50           format, ## args)
51 #define POLDI_LOG(priority, format, args ...) \
52   POLDI_LOG_DO (POLDI_LOG_FACILITY, priority, format, ## args)
53
54 /* Global flags.  */
55 struct pam_poldi_opt
56 {
57   unsigned int debug; /* Enable debugging.  */
58   int debug_sc;
59   int verbose;
60   const char *ctapi_driver; /* Library to access the ctAPI. */
61   const char *pcsc_driver;  /* Library to access the PC/SC system. */
62   const char *reader_port;  /* NULL or reder port to use. */
63   int disable_opensc;  /* Disable the use of the OpenSC framework. */
64   int disable_ccid;    /* Disable the use of the internal CCID driver. */
65   int debug_ccid_driver;        /* Debug the internal CCID driver.  */
66   int require_card_switch;
67   const char *logfile;
68   unsigned int wait_timeout;
69 } pam_poldi_opt;
70
71 /* Set defaults.  */
72 struct pam_poldi_opt pam_poldi_opt =
73   {
74     0,
75     0,
76     0,
77     NULL,
78     NULL,
79     NULL,
80     0,
81     0,
82     0,
83     0,
84     NULL,
85     0
86   };
87
88
89 /* Option IDs.  */
90 enum arg_opt_ids
91   {
92     arg_debug = 500,
93     arg_debug_sc,
94     arg_verbose,
95     arg_ctapi_driver,
96     arg_pcsc_driver,
97     arg_reader_port,
98     arg_disable_opensc,
99     arg_disable_ccid,
100     arg_debug_ccid_driver,
101     arg_require_card_switch,
102     arg_logfile,
103     arg_wait_timeout
104   };
105
106 /* Option specifications. */
107 static ARGPARSE_OPTS arg_opts[] =
108   {
109     { arg_debug,
110       "debug", 256, "Debug PAM-Poldi" },
111     { arg_debug_sc,
112       "debug-sc", 256, "Debug sc FIXME" },
113     { arg_ctapi_driver,
114       "ctapi-driver", 2, "|NAME|use NAME as ct-API driver" },
115     { arg_pcsc_driver,
116       "pcsc-driver", 2,  "|NAME|use NAME as PC/SC driver" },
117     { arg_reader_port,
118       "reader-port", 2, "|N|connect to reader at port N" },
119 #ifdef HAVE_LIBUSB
120     { arg_disable_ccid,
121       "disable-ccid", 0, "do not use the internal CCID driver" },
122     { arg_debug_ccid_driver,
123       "debug-ccid-driver", 0, "debug the  internal CCID driver" },
124 #endif
125 #ifdef HAVE_OPENSC
126     { arg_disable_opensc,
127       "disable-opensc", 0, "do not use the OpenSC layer" },
128 #endif
129     { arg_require_card_switch,
130       "require-card-switch", 0, "Require re-insertion of card" },
131     { arg_logfile,
132       "log-file", 2, "Specify file to use for logging" },
133     { arg_wait_timeout,
134       "wait-timeout", 1, "|SEC|Specify timeout for waiting" },
135     { 0 }
136   };
137
138 static gpg_error_t
139 pam_poldi_options_cb (ARGPARSE_ARGS *parg, void *opaque)
140 {
141   gpg_err_code_t err = GPG_ERR_NO_ERROR;
142
143   switch (parg->r_opt)
144     {
145     case arg_debug:
146       pam_poldi_opt.debug = 1;
147       break;
148
149     case arg_debug_sc:
150       pam_poldi_opt.debug_sc = 1;
151       break;
152
153     case arg_ctapi_driver:
154       pam_poldi_opt.ctapi_driver = xstrdup (parg->r.ret_str);
155       break;
156
157     case arg_pcsc_driver:
158       pam_poldi_opt.pcsc_driver = xstrdup (parg->r.ret_str);
159       break;
160
161     case arg_reader_port:
162       pam_poldi_opt.reader_port = xstrdup (parg->r.ret_str);
163       break;
164
165     case arg_disable_ccid:
166       pam_poldi_opt.disable_ccid = 1;
167       break;
168
169     case arg_disable_opensc:
170       pam_poldi_opt.disable_opensc = 1;
171       break;
172
173     case arg_debug_ccid_driver:
174       pam_poldi_opt.debug_ccid_driver = 1;
175       break;
176
177     case arg_require_card_switch:
178       pam_poldi_opt.require_card_switch = 1;
179       break;
180
181     case arg_logfile:
182       pam_poldi_opt.logfile = xstrdup (parg->r.ret_str);
183       break;
184
185     case arg_wait_timeout:
186       pam_poldi_opt.wait_timeout = parg->r.ret_int;
187       break;
188
189     default:
190       err = GPG_ERR_INTERNAL;   /* FIXME?  */
191       break;
192     }
193
194   return gpg_error (err);
195 }
196
197 static gpg_error_t
198 ask_user (const struct pam_conv *conv, const char *text, char **response)
199 {
200   struct pam_message messages[1] = { { PAM_PROMPT_ECHO_OFF, text } };
201   const struct pam_message *pmessages[1] = { &messages[0] };
202   struct pam_response *responses = NULL;
203   char *response_new;
204   gpg_error_t err;
205   int ret;
206
207   response_new = NULL;
208
209   ret = (*conv->conv) (sizeof (messages) / (sizeof (*messages)), pmessages,
210                        &responses, conv->appdata_ptr);
211   if (ret != PAM_SUCCESS)
212     {
213       err = gpg_error (GPG_ERR_INTERNAL);
214       goto out;
215     }
216
217   if (response)
218     {
219       response_new = strdup (responses[0].resp);
220       if (! response_new)
221         {
222           err = gpg_error_from_errno (errno);
223           goto out;
224         }
225     }
226
227   err = 0;
228   if (response)
229     *response = response_new;
230
231  out:
232
233   return err;
234 }
235
236 static gpg_error_t
237 tell_user (const struct pam_conv *conv, char *fmt, ...)
238 {
239   struct pam_message messages[1] = { { PAM_TEXT_INFO, NULL } };
240   const struct pam_message *pmessages[1] = { &messages[0] };
241   struct pam_response *responses = NULL;
242   gpg_error_t err;
243   char *string;
244   va_list ap;
245   int ret;
246
247   string = NULL;
248
249   va_start (ap, fmt);
250   ret = vasprintf (&string, fmt, ap);
251   if (ret < 0)
252     {
253       err = gpg_error_from_errno (errno);
254       goto out;
255     }
256   va_end (ap);
257
258   messages[0].msg = string;
259   
260   ret = (*conv->conv) (sizeof (messages) / (sizeof (*messages)), pmessages,
261                        &responses, conv->appdata_ptr);
262   if (ret != PAM_SUCCESS)
263     {
264       err = gpg_error (GPG_ERR_INTERNAL);
265       goto out;
266     }
267
268   err = 0;
269
270  out:
271
272   free (string);
273
274   return err;
275 }
276
277 static gpg_error_t
278 do_auth (int slot, const struct pam_conv *conv, gcry_sexp_t key)
279 {
280   unsigned char *challenge;
281   unsigned char *response;
282   size_t challenge_n;
283   size_t response_n;
284   gpg_error_t err;
285   char *pin;
286
287   challenge = NULL;
288   response = NULL;
289   pin = NULL;
290
291   err = ask_user (conv, POLDI_PIN2_QUERY_MSG, &pin);
292   if (err)
293     goto out;
294
295   err = card_pin_provide (slot, 2, pin);
296   if (err)
297     goto out;
298
299   err = challenge_generate (&challenge, &challenge_n);
300   if (err)
301     goto out;
302
303   err = card_sign (slot, challenge, challenge_n, &response, &response_n);
304   if (err)
305     goto out;
306
307   /* Verify response.  */
308   err = challenge_verify (key, challenge, challenge_n, response, response_n);
309
310  out:
311
312   free (challenge);
313   free (response);
314   free (pin);
315
316   return err;
317 }
318
319 static gpg_error_t
320 lookup_key (const char *username, gcry_sexp_t *key)
321 {
322   gcry_sexp_t key_sexp;
323   char *key_string;
324   char *key_path;
325   char *serialno;
326   gpg_error_t err;
327
328   serialno = NULL;
329   key_path = NULL;
330   key_string = NULL;
331
332   err = usersdb_lookup_by_username (username, &serialno);
333   if (err)
334     goto out;
335
336   key_path = make_filename (POLDI_KEY_DIRECTORY, serialno, NULL);
337   err = file_to_string (key_path, &key_string);
338   if ((! err) && (! key_string))
339     err = gpg_error (GPG_ERR_NO_PUBKEY);
340   if (err)
341     goto out;
342
343   err = string_to_sexp (&key_sexp, key_string);
344   if (err)
345     goto out;
346
347   *key = key_sexp;
348
349  out:
350
351   free (key_path);
352   free (key_string);
353   free (serialno);
354
355   return err;
356 }
357
358 static gpg_error_t
359 wait_for_card (int slot, int require_card_switch,
360                const struct pam_conv *conv, char **serialno)
361 {
362   char *serialno_new;
363   gpg_error_t err;
364
365   err = tell_user (conv, "Insert card ...");
366   if (err)
367     goto out;
368
369   err = card_init (slot, 1, pam_poldi_opt.wait_timeout, require_card_switch);
370   if (err)
371     {
372       if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
373         tell_user (conv, "Timeout inserting card");
374       goto out;
375     }
376
377   err = card_info (slot, &serialno_new, NULL, NULL);
378   if (err)
379     goto out;
380
381   *serialno = serialno_new;
382
383  out:
384
385   return err;
386 }
387
388 static gpg_error_t
389 parse_argv (int argc, const char **argv)
390 {
391   gpg_error_t err;
392   unsigned int i;
393
394   err = 0;
395   for (i = 0; i < argc; i++)
396     {
397       if (! strcmp (argv[i], "debug"))
398         {
399           pam_poldi_opt.debug = ~0;
400           pam_poldi_opt.debug_sc = 1;
401           pam_poldi_opt.verbose = 1;
402           pam_poldi_opt.debug_ccid_driver = 1;
403         }
404       else if (! strncmp (argv[i], "timeout=", 8))
405         pam_poldi_opt.wait_timeout = atoi (argv[i] + 8);
406       else
407         {
408           err = gpg_error (GPG_ERR_INTERNAL);
409           break;
410         }
411     }
412
413   return err;
414 }
415
416 /* Uaaahahahh, ich will dir einloggen!  */
417 PAM_EXTERN int
418 pam_sm_authenticate (pam_handle_t *pam_handle, int flags, int argc, const char **argv)
419 {
420   const struct pam_conv *conv;
421   gcry_sexp_t key;
422   gpg_error_t err;
423   char *username;
424   char *serialno;
425   char *account;
426   int slot;
427   int ret;
428
429   serialno = NULL;
430   account = NULL;
431   slot = -1;
432   key = NULL;
433   
434   openlog ("poldi", LOG_PID, LOG_USER);
435
436   /* Parse options.  */
437   err = options_parse_conf  (pam_poldi_options_cb, NULL,
438                              arg_opts, POLDI_CONF_FILE);
439   if (err)
440     goto out;
441
442   err = parse_argv (argc, argv);
443   if (err)
444     goto out;
445
446   log_set_file (pam_poldi_opt.logfile);
447
448   /* We need to disable bufferring on stderr, since it might have been
449      enabled by log_set_file().  Buffering on stderr will complicate
450      PAM interaction, since e.g. libpam-misc's misc_conv() function
451      does expect stderr to be unbuffered.  */
452   if ((! pam_poldi_opt.logfile) || (! strcmp (pam_poldi_opt.logfile, "-")))
453     setvbuf (stderr, NULL, _IONBF, 0);
454
455   scd_init (pam_poldi_opt.debug,
456             pam_poldi_opt.debug_sc,
457             pam_poldi_opt.verbose,
458             pam_poldi_opt.ctapi_driver,
459             pam_poldi_opt.reader_port,
460             pam_poldi_opt.pcsc_driver,
461             pam_poldi_opt.disable_opensc,
462             pam_poldi_opt.disable_ccid,
463             pam_poldi_opt.debug_ccid_driver);
464
465   /* Ask PAM for username.  */
466   ret = pam_get_item (pam_handle, PAM_USER, (const void **) &username);
467   if (ret != PAM_SUCCESS)
468     {
469       err = gpg_error (GPG_ERR_INTERNAL);
470       goto out;
471     }
472
473   /* Ask PAM for conv structure.  */
474   ret = pam_get_item (pam_handle, PAM_CONV, (const void **) &conv);
475   if (ret != PAM_SUCCESS)
476     {
477       POLDI_LOG (ERR, "Failed to retrieve conversation structure");
478       err = GPG_ERR_INTERNAL;
479       goto out;
480     }
481
482   /* Open card slot.  */
483   err = card_open (NULL, &slot);
484   if (err)
485     goto out;
486
487   if (username)
488     {
489       /* Got a username from PAM.  */
490       
491       err = lookup_key (username, &key);
492       if (err)
493         goto out;
494
495       /* Got key.  */
496
497       err = wait_for_card (slot, pam_poldi_opt.require_card_switch,
498                            conv, &serialno);
499       if (err)
500         goto out;
501
502       err = usersdb_lookup_by_serialno (serialno, &account);
503
504       if (err || strcmp (account, username))
505         {
506           tell_user (conv, "Serial no %s is not associated with %s",
507                      serialno, username);
508           if (! err)
509             err = gpg_error (GPG_ERR_INTERNAL); /* FIXME */
510         }
511       else
512         err = tell_user (conv, "Serial no: %s", serialno);
513       
514       if (err)
515         goto out;
516
517       err = do_auth (slot, conv, key);
518       if (err)
519         goto out;
520     }
521   else
522     {
523       err = wait_for_card (slot, pam_poldi_opt.require_card_switch,
524                            conv, &serialno);
525       if (err)
526         goto out;
527
528       err = tell_user (conv, "Serial no: %s", serialno);
529       if (err)
530         goto out;
531
532       err = usersdb_lookup_by_serialno (serialno, &account);
533       if (err)
534         goto out;
535
536       err = tell_user (conv, "Account: %s", account);
537       if (err)
538         goto out;
539
540       err = lookup_key (account, &key);
541       if (err)
542         goto out;
543
544       err = do_auth (slot, conv, key);
545       if (err)
546         goto out;
547   
548       /* Make username available to application.  */
549       ret = pam_set_item (pam_handle, PAM_USER, account);
550       if (ret != PAM_SUCCESS)
551         {
552           err = gpg_error (GPG_ERR_INTERNAL);
553           goto out;
554         }
555     }
556
557   /* Done.  */
558
559  out:
560   
561   gcry_sexp_release (key);
562   free (serialno);
563   free (account);
564   if (slot != -1)
565     card_close (slot);
566
567   if (err)
568     POLDI_LOG (ERR, "Failure: %s\n", gpg_strerror (err));
569   else
570     POLDI_LOG (INFO, "Success\n");
571
572   closelog ();
573
574   return err ? PAM_AUTH_ERR : PAM_SUCCESS;
575 }
576
577 PAM_EXTERN int
578 pam_sm_setcred (pam_handle_t *pam_handle, int flags, int argc, const char **argv)
579 {
580   /* FIXME?  */
581   return PAM_SUCCESS;
582 }