7ab54baa244dfc18bf50131b3deabe6334a06edc
[poldi.git] / src / common / support.c
1 /* support.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 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
19    02111-1307, USA.  */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <assert.h>
26 #include <sys/mman.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 #include <sys/stat.h>
30 #include <errno.h>
31 #include <stdarg.h>
32
33 #include <gcrypt.h>
34
35 #include "support.h"
36 #include "defs.h"
37
38 #include <jnlib/xmalloc.h>
39 #include <jnlib/stringhelp.h>
40
41 #define CHALLENGE_MD_ALGORITHM GCRY_MD_SHA1
42
43 gpg_error_t
44 challenge_generate (unsigned char **challenge, size_t *challenge_n)
45 {
46   gpg_error_t err = GPG_ERR_NO_ERROR;
47   unsigned char *challenge_new = NULL;
48   size_t challenge_new_n = gcry_md_get_algo_dlen (CHALLENGE_MD_ALGORITHM);
49
50   challenge_new = malloc (challenge_new_n);
51   if (! challenge_new)
52     err = gpg_err_code_from_errno (errno);
53   else
54     {
55       gcry_create_nonce (challenge_new, challenge_new_n);
56       *challenge = challenge_new;
57       *challenge_n = challenge_new_n;
58     }
59
60   return err;
61 }
62
63 static gpg_error_t
64 challenge_verify_sexp (gcry_sexp_t sexp_key,
65                        unsigned char *challenge, size_t challenge_n,
66                        unsigned char *response, size_t response_n)
67 {
68   gpg_error_t err = GPG_ERR_NO_ERROR;
69   gcry_sexp_t sexp_signature = NULL;
70   gcry_sexp_t sexp_data = NULL;
71   gcry_mpi_t mpi_signature = NULL;
72
73   /* Convert buffers into MPIs.  */
74   if (! err)
75     {
76       if (gcry_mpi_scan (&mpi_signature, GCRYMPI_FMT_USG, response, response_n,
77                          NULL))
78         err = GPG_ERR_INTERNAL; /* FIXME.  */
79     }
80
81   /* Create according S-Expressions.  */
82   if (! err)
83     err = gcry_sexp_build (&sexp_data, NULL,
84                            "(data (flags pkcs1) (hash sha1 %b))",
85                            challenge_n, challenge);
86   if (! err)
87     err = gcry_sexp_build (&sexp_signature, NULL, "(sig-val (rsa (s %m)))",
88                            mpi_signature);
89
90   /* Verify.  */
91   if (! err)
92     err = gcry_pk_verify (sexp_signature, sexp_data, sexp_key);
93
94   if (sexp_data)
95     gcry_sexp_release (sexp_data);
96   if (sexp_signature)
97     gcry_sexp_release (sexp_signature);
98   if (mpi_signature)
99     gcry_mpi_release (mpi_signature);
100
101   return err;
102 }
103
104 gpg_error_t
105 challenge_verify (gcry_sexp_t key,
106                   unsigned char *challenge, size_t challenge_n,
107                   unsigned char *response, size_t response_n)
108 {
109   gpg_error_t err = GPG_ERR_NO_ERROR;
110
111   err = challenge_verify_sexp (key,
112                                challenge, challenge_n, response, response_n);
113
114   return err;
115 }
116
117 static gpg_error_t
118 usersdb_translate (const char *serialno, const char *username, char **found)
119 {
120   const char *delimiters = "\t\n ";
121   gpg_error_t err;
122   FILE *usersdb;
123   char *line;
124   char *line_serialno;
125   char *line_username;
126   char *token_found;
127   size_t line_n;
128   ssize_t ret;
129
130   err = 0;
131   line = NULL;
132   token_found = NULL;
133   line_serialno = NULL;
134   line_username = NULL;
135
136   usersdb = fopen (POLDI_USERS_DB_FILE, "r");
137   if (! usersdb)
138     {
139       err = gpg_error_from_errno (errno);
140       goto out;
141     }
142
143   while (1)
144     {
145       /* Get next line.  */
146       line = NULL;
147       line_n = 0;
148       ret = getline (&line, &line_n, usersdb);
149       if (ret == -1)
150         {
151           if (ferror (usersdb))
152             err = gpg_error_from_errno (errno);
153           else
154             err = gpg_error (GPG_ERR_NOT_FOUND);
155           break;
156         }
157
158       line_serialno = strtok (line, delimiters);
159       if (! line_serialno)
160         {
161           err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
162           break;
163         }
164
165       line_username = strtok (NULL, delimiters);
166       if (! line_username)
167         {
168           err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
169           break;
170         }
171
172       if (serialno)
173         {
174           if (! strcmp (serialno, line_serialno))
175             {
176               if (found)
177                 {
178                   token_found = strdup (line_username);
179                   if (! token_found)
180                     err = gpg_error_from_errno (errno);
181                 }
182               break;
183             }
184         }
185       else
186         {
187           if (! strcmp (username, line_username))
188             {
189               if (found)
190                 {
191                   token_found = strdup (line_serialno);
192                   if (! token_found)
193                     err = gpg_error_from_errno (errno);
194                 }
195               break;
196             }
197         }
198
199       free (line);
200     }
201   if (err)
202     goto out;
203
204   if (found)
205     *found = token_found;
206
207  out:
208
209   if (usersdb)
210     fclose (usersdb);
211   free (line);
212
213   return err;
214 }
215
216 gpg_error_t
217 usersdb_lookup_by_serialno (const char *serialno, char **username)
218 {
219   return usersdb_translate (serialno, NULL, username);
220 }
221
222 gpg_error_t
223 usersdb_lookup_by_username (const char *username, char **serialno)
224 {
225   return usersdb_translate (NULL, username, serialno);
226 }
227
228 gpg_error_t
229 usersdb_add_entry (const char *username, const char *serialno)
230 {
231   char users_file[] = POLDI_USERS_DB_FILE;
232   FILE *users_file_fp;
233   gpg_error_t err;
234   int ret;
235
236   users_file_fp = NULL;
237   
238   users_file_fp = fopen (users_file, "a");
239   if (! users_file_fp)
240     {
241       err = gpg_error_from_errno (errno);
242       goto out;
243     }
244
245   fprintf (users_file_fp, "%s\t%s\n", serialno, username);
246   if (ferror (users_file_fp))
247     {
248       err = gpg_error_from_errno (errno);
249       goto out;
250     }
251   
252   ret = fclose (users_file_fp);
253   users_file_fp = NULL;
254   if (ret)
255     {
256       err = gpg_error_from_errno (errno);
257       goto out;
258     }
259   
260   err = 0;
261
262  out:
263
264   if (users_file_fp)
265     fclose (users_file_fp);
266
267   return err;
268 }
269
270 gpg_error_t
271 usersdb_remove_entry (const char *username, const char *serialno)
272 {
273   char users_file_old[] = POLDI_USERS_DB_FILE;
274   char users_file_new[] = POLDI_USERS_DB_FILE ".new";
275   char delimiters[] = "\t\n ";
276   FILE *users_file_old_fp;
277   FILE *users_file_new_fp;
278   char *line;
279   char *line_serialno;
280   char *line_username;
281   size_t line_n;
282   ssize_t ret;
283   gpg_error_t err;
284
285   line_n = 0;
286   line = NULL;
287   users_file_old_fp = NULL;
288   users_file_new_fp = NULL;
289
290   users_file_old_fp = fopen (users_file_old, "r");
291   if (! users_file_old_fp)
292     {
293       err = gpg_error_from_errno (errno);
294       goto out;
295     }
296   users_file_new_fp = fopen (users_file_new, "w");
297   if (! users_file_new_fp)
298     {
299       err = gpg_error_from_errno (errno);
300       goto out;
301     }
302
303   err = 0;
304   while (1)
305     {
306       ret = getline (&line, &line_n, users_file_old_fp);
307       if (ret == -1)
308         {
309           if (ferror (users_file_old_fp))
310             err = gpg_error_from_errno (errno);
311           break;
312         }
313
314       line_serialno = strtok (line, delimiters);
315       if (! line_serialno)
316         {
317           err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
318           break;
319         }
320
321       line_username = strtok (NULL, delimiters);
322       if (! line_username)
323         {
324           err = gpg_error (GPG_ERR_INTERNAL); /* FIXME?  */
325           break;
326         }
327
328       if ((username && strcmp (username, line_username))
329           || (serialno && strcmp (serialno, line_serialno)))
330         fprintf (users_file_new_fp, "%s\t%s\n", line_serialno, line_username);
331
332       free (line);
333       line = NULL;
334       line_n = 0;
335     }
336
337   fclose (users_file_old_fp);   /* FIXME: it's alright to ignore
338                                    errors here, right?  */
339   users_file_old_fp = NULL;
340   
341   ret = fclose (users_file_new_fp);
342   users_file_new_fp = NULL;
343   if (ret)
344     {
345       err = gpg_error_from_errno (errno);
346       goto out;
347     }
348
349   ret = rename (users_file_new, users_file_old);
350   if (ret == -1)
351     err = gpg_error_from_errno (errno);
352
353  out:
354
355   free (line);
356   if (users_file_old_fp)
357     fclose (users_file_old_fp);
358   if (users_file_new_fp)
359     fclose (users_file_new_fp);
360
361   return err;
362 }
363
364 gpg_error_t
365 sexp_to_string (gcry_sexp_t sexp, char **sexp_string)
366 {
367   gpg_error_t err;
368   char *buffer;
369   size_t buffer_n;
370   int fmt;
371
372   buffer = NULL;
373   fmt = GCRYSEXP_FMT_ADVANCED;
374
375   buffer_n = gcry_sexp_sprint (sexp, fmt, NULL, 0);
376   if (! buffer_n)
377     {
378       err = gpg_error (GPG_ERR_INTERNAL); /* ? */
379       goto out;
380     }
381
382   buffer = malloc (buffer_n);
383   if (! buffer)
384     {
385       err = gpg_error_from_errno (errno);
386       goto out;
387     }
388
389   buffer_n = gcry_sexp_sprint (sexp, fmt, buffer, buffer_n);
390   if (! buffer_n)
391     {
392       err = gpg_error (GPG_ERR_INTERNAL);
393       goto out;
394     }
395
396   *sexp_string = buffer;
397   err = 0;
398   
399  out:
400
401   if (err)
402     free (buffer);
403
404   return err;
405 }
406
407 gpg_error_t
408 string_to_sexp (gcry_sexp_t *sexp, char *string)
409 {
410   gpg_error_t err;
411
412   err = gcry_sexp_sscan (sexp, NULL, string, strlen (string));
413
414   return err;
415 }
416
417 gpg_error_t
418 file_to_string (const char *filename, char **string)
419 {
420   gpg_error_t err;
421   struct stat statbuf;
422   char *string_new;
423   FILE *fp;
424   int ret;
425
426   fp = NULL;
427   string_new = NULL;
428
429   ret = stat (filename, &statbuf);
430   if (ret)
431     {
432       err = gpg_error_from_errno (errno);
433       goto out;
434     }
435
436   if (statbuf.st_size)
437     {
438       fp = fopen (filename, "r");
439       if (! fp)
440         {
441           err = gpg_error_from_errno (errno);
442           goto out;
443         }
444       string_new = malloc (statbuf.st_size + 1);
445       if (! string_new)
446         {
447           err = gpg_error_from_errno (errno);
448           goto out;
449         }
450       ret = fread (string_new, statbuf.st_size, 1, fp);
451       if (ret != 1)
452         {
453           err = gpg_error_from_errno (errno);
454           goto out;
455         }
456       string_new[statbuf.st_size] = 0;
457       fclose (fp);              /* FIXME?  */
458       fp = NULL;
459     }
460
461   err = 0;
462   *string = string_new;
463
464  out:
465
466   if (fp)
467     fclose (fp);
468
469   if (err)
470     free (string_new);
471
472   return err;
473 }
474
475 \f
476
477 gpg_error_t
478 key_filename_construct (char **filename, const char *serialno)
479 {
480   char *path;
481
482   path = make_filename (POLDI_KEY_DIRECTORY, serialno, NULL);
483   *filename = path;
484
485   return 0;
486 }