gpg,sm: Implement keybox compression run and release lock in gpgsm
[gnupg.git] / tools / gpgtar-extract.c
1 /* gpgtar-extract.c - Extract from a TAR archive
2  * Copyright (C) 2010 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it 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  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU 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 <https://www.gnu.org/licenses/>.
18  */
19
20 #include <config.h>
21 #include <errno.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <assert.h>
29
30 #include "../common/i18n.h"
31 #include "../common/exectool.h"
32 #include "../common/sysutils.h"
33 #include "../common/ccparray.h"
34 #include "gpgtar.h"
35
36
37 static gpg_error_t
38 extract_regular (estream_t stream, const char *dirname,
39                  tarinfo_t info, tar_header_t hdr)
40 {
41   gpg_error_t err;
42   char record[RECORDSIZE];
43   size_t n, nbytes, nwritten;
44   char *fname;
45   estream_t outfp = NULL;
46
47   fname = strconcat (dirname, "/", hdr->name, NULL);
48   if (!fname)
49     {
50       err = gpg_error_from_syserror ();
51       log_error ("error creating filename: %s\n", gpg_strerror (err));
52       goto leave;
53     }
54   else
55     err = 0;
56
57   if (opt.dry_run)
58     outfp = es_fopenmem (0, "wb");
59   else
60     outfp = es_fopen (fname, "wb");
61   if (!outfp)
62     {
63       err = gpg_error_from_syserror ();
64       log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
65       goto leave;
66     }
67
68   for (n=0; n < hdr->nrecords;)
69     {
70       err = read_record (stream, record);
71       if (err)
72         goto leave;
73       info->nblocks++;
74       n++;
75       if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
76         nbytes = RECORDSIZE;
77       else
78         nbytes = (hdr->size % RECORDSIZE);
79
80       nwritten = es_fwrite (record, 1, nbytes, outfp);
81       if (nwritten != nbytes)
82         {
83           err = gpg_error_from_syserror ();
84           log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
85           goto leave;
86         }
87     }
88   /* Fixme: Set permissions etc.  */
89
90  leave:
91   if (!err && opt.verbose)
92     log_info ("extracted '%s'\n", fname);
93   es_fclose (outfp);
94   if (err && fname && outfp)
95     {
96       if (gnupg_remove (fname))
97         log_error ("error removing incomplete file '%s': %s\n",
98                    fname, gpg_strerror (gpg_error_from_syserror ()));
99     }
100   xfree (fname);
101   return err;
102 }
103
104
105 static gpg_error_t
106 extract_directory (const char *dirname, tar_header_t hdr)
107 {
108   gpg_error_t err;
109   char *fname;
110   size_t prefixlen;
111
112   prefixlen = strlen (dirname) + 1;
113   fname = strconcat (dirname, "/", hdr->name, NULL);
114   if (!fname)
115     {
116       err = gpg_error_from_syserror ();
117       log_error ("error creating filename: %s\n", gpg_strerror (err));
118       goto leave;
119     }
120   else
121     err = 0;
122
123   if (fname[strlen (fname)-1] == '/')
124     fname[strlen (fname)-1] = 0;
125
126   if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------"))
127     {
128       err = gpg_error_from_syserror ();
129       if (gpg_err_code (err) == GPG_ERR_EEXIST)
130         {
131           /* Ignore existing directories while extracting.  */
132           err = 0;
133         }
134
135       if (gpg_err_code (err) == GPG_ERR_ENOENT)
136         {
137           /* Try to create the directory with parents but keep the
138              original error code in case of a failure.  */
139           char *p;
140           int rc = 0;
141
142           for (p = fname+prefixlen; (p = strchr (p, '/')); p++)
143             {
144               *p = 0;
145               rc = gnupg_mkdir (fname, "-rwx------");
146               *p = '/';
147               if (rc)
148                 break;
149             }
150           if (!rc && !gnupg_mkdir (fname, "-rwx------"))
151             err = 0;
152         }
153       if (err)
154         log_error ("error creating directory '%s': %s\n",
155                    fname, gpg_strerror (err));
156     }
157
158  leave:
159   if (!err && opt.verbose)
160     log_info ("created   '%s/'\n", fname);
161   xfree (fname);
162   return err;
163 }
164
165
166 static gpg_error_t
167 extract (estream_t stream, const char *dirname, tarinfo_t info,
168          tar_header_t hdr)
169 {
170   gpg_error_t err;
171   size_t n;
172
173   n = strlen (hdr->name);
174 #ifdef HAVE_DOSISH_SYSTEM
175   if (strchr (hdr->name, '\\'))
176     {
177       log_error ("filename '%s' contains a backslash - "
178                  "can't extract on this system\n", hdr->name);
179       return gpg_error (GPG_ERR_INV_NAME);
180     }
181 #endif /*HAVE_DOSISH_SYSTEM*/
182
183   if (!n
184       || strstr (hdr->name, "//")
185       || strstr (hdr->name, "/../")
186       || !strncmp (hdr->name, "../", 3)
187       || (n >= 3 && !strcmp (hdr->name+n-3, "/.." )))
188     {
189       log_error ("filename '%s' as suspicious parts - not extracting\n",
190                  hdr->name);
191       return gpg_error (GPG_ERR_INV_NAME);
192     }
193
194   if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
195     err = extract_regular (stream, dirname, info, hdr);
196   else if (hdr->typeflag == TF_DIRECTORY)
197     err = extract_directory (dirname, hdr);
198   else
199     {
200       char record[RECORDSIZE];
201
202       log_info ("unsupported file type %d for '%s' - skipped\n",
203                 (int)hdr->typeflag, hdr->name);
204       for (err = 0, n=0; !err && n < hdr->nrecords; n++)
205         {
206           err = read_record (stream, record);
207           if (!err)
208             info->nblocks++;
209         }
210     }
211   return err;
212 }
213
214
215 /* Create a new directory to be used for extracting the tarball.
216    Returns the name of the directory which must be freed by the
217    caller.  In case of an error a diagnostic is printed and NULL
218    returned.  */
219 static char *
220 create_directory (const char *dirprefix)
221 {
222   gpg_error_t err = 0;
223   char *prefix_buffer = NULL;
224   char *dirname = NULL;
225   size_t n;
226   int idx;
227
228   /* Remove common suffixes.  */
229   n = strlen (dirprefix);
230   if (n > 4 && (!compare_filenames    (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG)
231                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp")
232                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc")
233                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem")
234                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m")
235                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e")))
236     {
237       prefix_buffer = xtrystrdup (dirprefix);
238       if (!prefix_buffer)
239         {
240           err = gpg_error_from_syserror ();
241           goto leave;
242         }
243       prefix_buffer[n-4] = 0;
244       dirprefix = prefix_buffer;
245     }
246
247
248
249   for (idx=1; idx < 5000; idx++)
250     {
251       xfree (dirname);
252       dirname = xtryasprintf ("%s_%d_", dirprefix, idx);
253       if (!dirname)
254         {
255           err = gpg_error_from_syserror ();
256           goto leave;
257         }
258       if (!gnupg_mkdir (dirname, "-rwx------"))
259         goto leave; /* Ready.  */
260       if (errno != EEXIST && errno != ENOTDIR)
261         {
262           err = gpg_error_from_syserror ();
263           goto leave;
264         }
265     }
266   err = gpg_error (GPG_ERR_LIMIT_REACHED);
267
268  leave:
269   if (err)
270     {
271       log_error ("error creating an extract directory: %s\n",
272                  gpg_strerror (err));
273       xfree (dirname);
274       dirname = NULL;
275     }
276   xfree (prefix_buffer);
277   return dirname;
278 }
279
280
281 \f
282 gpg_error_t
283 gpgtar_extract (const char *filename, int decrypt)
284 {
285   gpg_error_t err;
286   estream_t stream;
287   estream_t cipher_stream = NULL;
288   tar_header_t header = NULL;
289   const char *dirprefix = NULL;
290   char *dirname = NULL;
291   struct tarinfo_s tarinfo_buffer;
292   tarinfo_t tarinfo = &tarinfo_buffer;
293
294   memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
295
296   if (filename)
297     {
298       if (!strcmp (filename, "-"))
299         stream = es_stdin;
300       else
301         stream = es_fopen (filename, "rb");
302       if (!stream)
303         {
304           err = gpg_error_from_syserror ();
305           log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
306           return err;
307         }
308     }
309   else
310     stream = es_stdin;
311
312   if (stream == es_stdin)
313     es_set_binary (es_stdin);
314
315   if (decrypt)
316     {
317       strlist_t arg;
318       ccparray_t ccp;
319       const char **argv;
320
321       cipher_stream = stream;
322       stream = es_fopenmem (0, "rwb");
323       if (! stream)
324         {
325           err = gpg_error_from_syserror ();
326           goto leave;
327         }
328
329       ccparray_init (&ccp, 0);
330
331       ccparray_put (&ccp, "--decrypt");
332       for (arg = opt.gpg_arguments; arg; arg = arg->next)
333         ccparray_put (&ccp, arg->d);
334
335       ccparray_put (&ccp, NULL);
336       argv = ccparray_get (&ccp, NULL);
337       if (!argv)
338         {
339           err = gpg_error_from_syserror ();
340           goto leave;
341         }
342
343       err = gnupg_exec_tool_stream (opt.gpg_program, argv,
344                                     cipher_stream, NULL, stream, NULL, NULL);
345       xfree (argv);
346       if (err)
347         goto leave;
348
349       err = es_fseek (stream, 0, SEEK_SET);
350       if (err)
351         goto leave;
352     }
353
354   if (opt.directory)
355     dirname = xtrystrdup (opt.directory);
356   else
357     {
358       if (opt.filename)
359         {
360           dirprefix = strrchr (opt.filename, '/');
361           if (dirprefix)
362             dirprefix++;
363           else
364             dirprefix = opt.filename;
365         }
366       else if (filename)
367         {
368           dirprefix = strrchr (filename, '/');
369           if (dirprefix)
370             dirprefix++;
371           else
372             dirprefix = filename;
373         }
374
375       if (!dirprefix || !*dirprefix)
376         dirprefix = "GPGARCH";
377
378       dirname = create_directory (dirprefix);
379       if (!dirname)
380         {
381           err = gpg_error (GPG_ERR_GENERAL);
382           goto leave;
383         }
384     }
385
386   if (opt.verbose)
387     log_info ("extracting to '%s/'\n", dirname);
388
389   for (;;)
390     {
391       err = gpgtar_read_header (stream, tarinfo, &header);
392       if (err || header == NULL)
393         goto leave;
394
395       err = extract (stream, dirname, tarinfo, header);
396       if (err)
397         goto leave;
398       xfree (header);
399       header = NULL;
400     }
401
402
403  leave:
404   xfree (header);
405   xfree (dirname);
406   if (stream != es_stdin)
407     es_fclose (stream);
408   if (stream != cipher_stream)
409     es_fclose (cipher_stream);
410   return err;
411 }