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 /* We need this wrapper type, since PAM's CONV function is declared
199    const, but Poldi's conversation callback interface includes a
200    non-const "void *opaque" argument.  */
201 typedef struct conv_opaque
202 {
203   const struct pam_conv *conv;
204 } conv_opaque_t;
205
206 /* This function queries the PAM user for input through the
207    conversation function CONV; TEXT will be displayed as prompt, the
208    user's response will be stored in *RESPONSE.  Returns proper error
209    code.  */
210 static gpg_error_t
211 ask_user (const struct pam_conv *conv, const char *text, char **response)
212 {
213   struct pam_message messages[1] = { { PAM_PROMPT_ECHO_OFF, text } };
214   const struct pam_message *pmessages[1] = { &messages[0] };
215   struct pam_response *responses = NULL;
216   char *response_new;
217   gpg_error_t err;
218   int ret;
219
220   response_new = NULL;
221
222   ret = (*conv->conv) (sizeof (messages) / (sizeof (*messages)), pmessages,
223                        &responses, conv->appdata_ptr);
224   if (ret != PAM_SUCCESS)
225     {
226       err = gpg_error (GPG_ERR_INTERNAL);
227       goto out;
228     }
229
230   if (response)
231     {
232       response_new = strdup (responses[0].resp);
233       if (! response_new)
234         {
235           err = gpg_error_from_errno (errno);
236           goto out;
237         }
238     }
239
240   err = 0;
241   if (response)
242     *response = response_new;
243
244  out:
245
246   return err;
247 }
248
249 /* This function queries the PAM user for input through the
250    conversation function CONV; TEXT will be displayed as prompt, the
251    user's response will be stored in *RESPONSE.  Returns proper error
252    code.  */
253 static gpg_error_t
254 tell_user (const struct pam_conv *conv, const char *fmt, ...)
255 {
256   struct pam_message messages[1] = { { PAM_TEXT_INFO, NULL } };
257   const struct pam_message *pmessages[1] = { &messages[0] };
258   struct pam_response *responses = NULL;
259   gpg_error_t err;
260   char *string;
261   va_list ap;
262   int ret;
263
264   string = NULL;
265
266   va_start (ap, fmt);
267   ret = vasprintf (&string, fmt, ap);
268   if (ret < 0)
269     {
270       err = gpg_error_from_errno (errno);
271       goto out;
272     }
273   va_end (ap);
274
275   messages[0].msg = string;
276   
277   ret = (*conv->conv) (sizeof (messages) / (sizeof (*messages)), pmessages,
278                        &responses, conv->appdata_ptr);
279   if (ret != PAM_SUCCESS)
280     {
281       err = gpg_error (GPG_ERR_INTERNAL);
282       goto out;
283     }
284
285   err = 0;
286
287  out:
288
289   free (string);
290
291   return err;
292 }
293
294 static gpg_error_t
295 pam_conversation (conversation_type_t type, void *opaque,
296                   const char *info, char **response)
297 {
298   conv_opaque_t *conv_opaque = opaque;
299   gpg_error_t err;
300
301   switch (type)
302     {
303     case CONVERSATION_TELL:
304       err = tell_user (conv_opaque->conv, info, response);
305       break;
306
307     case CONVERSATION_ASK_SECRET:
308       err = ask_user (conv_opaque->conv, info, response);
309       break;
310
311     default:
312       /* This CANNOT happen.  */
313       abort ();
314     }
315
316   return err;
317 }
318
319 \f
320
321 /*
322  * Helper functions.
323  */
324
325 /* This function parses the PAM argument vector ARGV of size ARGV */
326 static gpg_error_t
327 parse_argv (int argc, const char **argv)
328 {
329   gpg_error_t err;
330   unsigned int i;
331
332   err = 0;
333   for (i = 0; i < argc; i++)
334     {
335       if (! strcmp (argv[i], "debug"))
336         {
337           /* Handle "debug" option.  */
338           pam_poldi_opt.debug = ~0;
339           pam_poldi_opt.debug_sc = 1;
340           pam_poldi_opt.verbose = 1;
341           pam_poldi_opt.debug_ccid_driver = 1;
342         }
343       else if (! strncmp (argv[i], "timeout=", 8))
344         /* Handle "timeout=" option.  */
345         pam_poldi_opt.wait_timeout = atoi (argv[i] + 8);
346       else
347         {
348           log_error ("Error: Unknown PAM argument: %s", argv[i]);
349           err = gpg_error (GPG_ERR_UNKNOWN_NAME);
350         }
351
352       if (err)
353         break;
354     }
355
356   return err;
357 }
358
359 \f
360
361 /*
362  * PAM interface.
363  */
364
365 /* Uaaahahahh, ich will dir einloggen!  PAM authentication entry
366    point.  */
367 PAM_EXTERN int
368 pam_sm_authenticate (pam_handle_t *pam_handle,
369                      int flags, int argc, const char **argv)
370 {
371   const void *conv_void;
372   conv_opaque_t conv_opaque;
373   const struct pam_conv *conv;
374   gcry_sexp_t key;
375   gpg_error_t err;
376   const void *username_void;
377   const char *username;
378   char *serialno;
379   char *account;
380   int slot;
381   int ret;
382
383   serialno = NULL;
384   account = NULL;
385   slot = -1;
386   key = NULL;
387
388   /* Parse options.  */
389   err = options_parse_conf  (pam_poldi_options_cb, NULL,
390                              arg_opts, POLDI_CONF_FILE);
391   if (err)
392     {
393       log_error ("Error: failed to parse configuration file: %s\n",
394                  gpg_strerror (err));
395       goto out;
396     }
397
398   /* Parse argument vector provided by PAM.  */
399   err = parse_argv (argc, argv);
400   if (err)
401     {
402       log_error ("Error: failed to parse PAM argument vector: %s\n",
403                  gpg_strerror (err));
404       goto out;
405     }
406
407   /* Initialize logging: in case `logfile' has been set in the
408      configuration file, initialize jnlib-logging the traditional
409      file, loggin to the file (or socket special file) specified in
410      the configuration file; in case `logfile' has NOT been set in the
411      configuration file, log through Syslog.  */
412
413   if (pam_poldi_opt.logfile)
414     {
415       log_set_file (pam_poldi_opt.logfile);
416       if (! strcmp (pam_poldi_opt.logfile, "-"))
417         /* We need to disable bufferring on stderr, since it might
418            have been enabled by log_set_file().  Buffering on stderr
419            will complicate PAM interaction, since e.g. libpam-misc's
420            misc_conv() function does expect stderr to be
421            unbuffered.  */
422         setvbuf (stderr, NULL, _IONBF, 0);
423     }
424   else
425     log_set_syslog ("poldi", LOG_AUTH);
426
427   /* Initialize libscd.  */
428   scd_init (pam_poldi_opt.debug,
429             pam_poldi_opt.debug_sc,
430             pam_poldi_opt.verbose,
431             pam_poldi_opt.ctapi_driver,
432             pam_poldi_opt.reader_port,
433             pam_poldi_opt.pcsc_driver,
434             pam_poldi_opt.disable_opensc,
435             pam_poldi_opt.disable_ccid,
436             pam_poldi_opt.debug_ccid_driver);
437
438   /*
439    * Retrieve information from PAM.
440    */
441
442   /* Ask PAM for username.  */
443   ret = pam_get_item (pam_handle, PAM_USER, &username_void);
444   if (ret != PAM_SUCCESS)
445     {
446       err = gpg_error (GPG_ERR_INTERNAL);
447       goto out;
448     }
449   username = username_void;
450
451   /* Ask PAM for conv structure.  */
452   ret = pam_get_item (pam_handle, PAM_CONV, &conv_void);
453   if (ret != PAM_SUCCESS)
454     {
455       log_error ("Failed to retrieve conversation structure");
456       err = GPG_ERR_INTERNAL;
457       goto out;
458     }
459   conv = conv_void;
460   conv_opaque.conv = conv;
461
462   /* Open card slot.  */
463   err = card_open (NULL, &slot);
464   if (err)
465     goto out;
466
467   /*
468    * Process authentication request.
469    */
470
471   if (username)
472     {
473       /* We got a username from PAM, thus we are waiting for a
474          specific card.  */
475
476       /* We do not need the key right now already, but it seems to be
477          a good idea to make the login fail before waiting for card in
478          case no key has been installed for that card.  */
479       err = key_lookup_by_username (username, &key);
480       if (err)
481         goto out;
482
483       /* Wait for card.  */
484       err = wait_for_card (slot, pam_poldi_opt.require_card_switch,
485                            pam_poldi_opt.wait_timeout,
486                            pam_conversation, &conv_opaque, &serialno,
487                            NULL, NULL);
488       if (err)
489         goto out;
490
491       /* Lookup account for given card.  */
492       err = usersdb_lookup_by_serialno (serialno, &account);
493       if (err || strcmp (account, username))
494         {
495           /* Either the account could not be found or it is not the
496              expected one -> fail.  */
497
498           if (! err)
499             {
500               tell_user (conv, "Serial no %s is not associated with %s",
501                          serialno, username);
502               err = gpg_error (GPG_ERR_INV_NAME);
503             }
504           else
505             log_error ("Error: failed to lookup username for "
506                        "serial number `%s': %s\n",
507                        serialno, gpg_strerror (err));
508         }
509       else
510         {
511           /* Inform user about inserted card.  */
512         
513           err = tell_user (conv, "Serial no: %s", serialno);
514           if (err)
515             log_error ("Error: failed to inform user about inserted card: %s\n",
516                        gpg_strerror (err));
517         }
518
519       if (err)
520         goto out;
521
522       /* It is the correct card, try authentication with given
523          key.  */
524       err = authenticate (slot, key, pam_conversation, &conv_opaque);
525       if (err)
526         goto out;
527     }
528   else
529     {
530       /* No username has been provided by PAM, thus we accept any
531          card.  */
532
533       /* Wait for card.  */
534       err = wait_for_card (slot, pam_poldi_opt.require_card_switch,
535                            pam_poldi_opt.wait_timeout,
536                            pam_conversation, &conv_opaque, &serialno,
537                            NULL, NULL);
538       if (err)
539         goto out;
540
541       /* Inform user about inserted card.  */
542       err = tell_user (conv, "Serial no: %s", serialno);
543       if (err)
544         {
545           log_error ("Error: failed to inform user about inserted card: %s\n",
546                      gpg_strerror (err));
547           goto out;
548         }
549
550       /* Lookup account for inserted card.  */
551       err = usersdb_lookup_by_serialno (serialno, &account);
552       if (err)
553         {
554           log_error ("Error: failed to lookup username for "
555                      "serial number `%s': %s\n",
556                      serialno, gpg_strerror (err));
557           goto out;
558         }
559
560       /* Inform user about looked up account.  */
561       err = tell_user (conv, "Account: %s", account);
562       if (err)
563         {
564           log_error ("Error: failed to inform user about inserted card: %s\n",
565                      gpg_strerror (err));
566           goto out;
567         }
568
569       /* Lookup key for looked up account.  */
570       err = key_lookup_by_username (account, &key);
571       if (err)
572         goto out;
573
574       /* Try authentication with looked up key.  */
575       err = authenticate (slot, key, pam_conversation, &conv_opaque);
576       if (err)
577         goto out;
578
579       /* Make username available to application.  */
580       ret = pam_set_item (pam_handle, PAM_USER, account);
581       if (ret != PAM_SUCCESS)
582         {
583           err = gpg_error (GPG_ERR_INTERNAL);
584           goto out;
585         }
586     }
587
588   /* Done.  */
589
590  out:
591
592   /* Release resources.  */
593   gcry_sexp_release (key);
594   free (serialno);
595   free (account);
596   if (slot != -1)
597     card_close (slot);
598
599   /* Log result.  */
600   if (err)
601     log_error ("Failure: %s\n", gpg_strerror (err));
602   else
603     log_info ("Success\n");
604
605   /* FIXME, should be done by logging.c somehow.  */
606   closelog ();
607
608   /* Return to PAM.  */
609
610   return err ? PAM_AUTH_ERR : PAM_SUCCESS;
611 }
612
613
614 /* PAM's `set-credentials' interface.  */
615 PAM_EXTERN int
616 pam_sm_setcred (pam_handle_t *pam_handle,
617                 int flags, int argc, const char **argv)
618 {
619   /* FIXME?  */
620   return PAM_SUCCESS;
621 }
622
623 /* END */