chiark / gitweb /
dimrngr: Avoid need for hkp housekeeping.
[gnupg2.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 "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                  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       n++;
74       if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
75         nbytes = RECORDSIZE;
76       else
77         nbytes = (hdr->size % RECORDSIZE);
78
79       nwritten = es_fwrite (record, 1, nbytes, outfp);
80       if (nwritten != nbytes)
81         {
82           err = gpg_error_from_syserror ();
83           log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
84           goto leave;
85         }
86     }
87   /* Fixme: Set permissions etc.  */
88
89  leave:
90   if (!err && opt.verbose)
91     log_info ("extracted '%s'\n", fname);
92   es_fclose (outfp);
93   if (err && fname && outfp)
94     {
95       if (gnupg_remove (fname))
96         log_error ("error removing incomplete file '%s': %s\n",
97                    fname, gpg_strerror (gpg_error_from_syserror ()));
98     }
99   xfree (fname);
100   return err;
101 }
102
103
104 static gpg_error_t
105 extract_directory (const char *dirname, tar_header_t hdr)
106 {
107   gpg_error_t err;
108   char *fname;
109   size_t prefixlen;
110
111   prefixlen = strlen (dirname) + 1;
112   fname = strconcat (dirname, "/", hdr->name, NULL);
113   if (!fname)
114     {
115       err = gpg_error_from_syserror ();
116       log_error ("error creating filename: %s\n", gpg_strerror (err));
117       goto leave;
118     }
119   else
120     err = 0;
121
122   if (fname[strlen (fname)-1] == '/')
123     fname[strlen (fname)-1] = 0;
124
125   if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------"))
126     {
127       err = gpg_error_from_syserror ();
128       if (gpg_err_code (err) == GPG_ERR_EEXIST)
129         {
130           /* Ignore existing directories while extracting.  */
131           err = 0;
132         }
133
134       if (gpg_err_code (err) == GPG_ERR_ENOENT)
135         {
136           /* Try to create the directory with parents but keep the
137              original error code in case of a failure.  */
138           char *p;
139           int rc = 0;
140
141           for (p = fname+prefixlen; (p = strchr (p, '/')); p++)
142             {
143               *p = 0;
144               rc = gnupg_mkdir (fname, "-rwx------");
145               *p = '/';
146               if (rc)
147                 break;
148             }
149           if (!rc && !gnupg_mkdir (fname, "-rwx------"))
150             err = 0;
151         }
152       if (err)
153         log_error ("error creating directory '%s': %s\n",
154                    fname, gpg_strerror (err));
155     }
156
157  leave:
158   if (!err && opt.verbose)
159     log_info ("created   '%s/'\n", fname);
160   xfree (fname);
161   return err;
162 }
163
164
165 static gpg_error_t
166 extract (estream_t stream, const char *dirname, tar_header_t hdr)
167 {
168   gpg_error_t err;
169   size_t n;
170
171   n = strlen (hdr->name);
172 #ifdef HAVE_DOSISH_SYSTEM
173   if (strchr (hdr->name, '\\'))
174     {
175       log_error ("filename '%s' contains a backslash - "
176                  "can't extract on this system\n", hdr->name);
177       return gpg_error (GPG_ERR_INV_NAME);
178     }
179 #endif /*HAVE_DOSISH_SYSTEM*/
180
181   if (!n
182       || strstr (hdr->name, "//")
183       || strstr (hdr->name, "/../")
184       || !strncmp (hdr->name, "../", 3)
185       || (n >= 3 && !strcmp (hdr->name+n-3, "/.." )))
186     {
187       log_error ("filename '%s' as suspicious parts - not extracting\n",
188                  hdr->name);
189       return gpg_error (GPG_ERR_INV_NAME);
190     }
191
192   if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
193     err = extract_regular (stream, dirname, hdr);
194   else if (hdr->typeflag == TF_DIRECTORY)
195     err = extract_directory (dirname, hdr);
196   else
197     {
198       char record[RECORDSIZE];
199
200       log_info ("unsupported file type %d for '%s' - skipped\n",
201                 (int)hdr->typeflag, hdr->name);
202       for (err = 0, n=0; !err && n < hdr->nrecords; n++)
203         err = read_record (stream, record);
204     }
205   return err;
206 }
207
208
209 /* Create a new directory to be used for extracting the tarball.
210    Returns the name of the directory which must be freed by the
211    caller.  In case of an error a diagnostic is printed and NULL
212    returned.  */
213 static char *
214 create_directory (const char *dirprefix)
215 {
216   gpg_error_t err = 0;
217   char *prefix_buffer = NULL;
218   char *dirname = NULL;
219   size_t n;
220   int idx;
221
222   /* Remove common suffixes.  */
223   n = strlen (dirprefix);
224   if (n > 4 && (!compare_filenames    (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG)
225                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp")
226                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc")
227                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem")
228                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m")
229                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e")))
230     {
231       prefix_buffer = xtrystrdup (dirprefix);
232       if (!prefix_buffer)
233         {
234           err = gpg_error_from_syserror ();
235           goto leave;
236         }
237       prefix_buffer[n-4] = 0;
238       dirprefix = prefix_buffer;
239     }
240
241
242
243   for (idx=1; idx < 5000; idx++)
244     {
245       xfree (dirname);
246       dirname = xtryasprintf ("%s_%d_", dirprefix, idx);
247       if (!dirname)
248         {
249           err = gpg_error_from_syserror ();
250           goto leave;
251         }
252       if (!gnupg_mkdir (dirname, "-rwx------"))
253         goto leave; /* Ready.  */
254       if (errno != EEXIST && errno != ENOTDIR)
255         {
256           err = gpg_error_from_syserror ();
257           goto leave;
258         }
259     }
260   err = gpg_error (GPG_ERR_LIMIT_REACHED);
261
262  leave:
263   if (err)
264     {
265       log_error ("error creating an extract directory: %s\n",
266                  gpg_strerror (err));
267       xfree (dirname);
268       dirname = NULL;
269     }
270   xfree (prefix_buffer);
271   return dirname;
272 }
273
274
275 \f
276 gpg_error_t
277 gpgtar_extract (const char *filename, int decrypt)
278 {
279   gpg_error_t err;
280   estream_t stream;
281   estream_t cipher_stream = NULL;
282   tar_header_t header = NULL;
283   const char *dirprefix = NULL;
284   char *dirname = NULL;
285
286   if (filename)
287     {
288       if (!strcmp (filename, "-"))
289         stream = es_stdin;
290       else
291         stream = es_fopen (filename, "rb");
292       if (!stream)
293         {
294           err = gpg_error_from_syserror ();
295           log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
296           return err;
297         }
298     }
299   else
300     stream = es_stdin;
301
302   if (stream == es_stdin)
303     es_set_binary (es_stdin);
304
305   if (decrypt)
306     {
307       strlist_t arg;
308       ccparray_t ccp;
309       const char **argv;
310
311       cipher_stream = stream;
312       stream = es_fopenmem (0, "rwb");
313       if (! stream)
314         {
315           err = gpg_error_from_syserror ();
316           goto leave;
317         }
318
319       ccparray_init (&ccp, 0);
320
321       ccparray_put (&ccp, "--decrypt");
322       for (arg = opt.gpg_arguments; arg; arg = arg->next)
323         ccparray_put (&ccp, arg->d);
324
325       ccparray_put (&ccp, NULL);
326       argv = ccparray_get (&ccp, NULL);
327       if (!argv)
328         {
329           err = gpg_error_from_syserror ();
330           goto leave;
331         }
332
333       err = gnupg_exec_tool_stream (opt.gpg_program, argv,
334                                     cipher_stream, NULL, stream, NULL, NULL);
335       xfree (argv);
336       if (err)
337         goto leave;
338
339       err = es_fseek (stream, 0, SEEK_SET);
340       if (err)
341         goto leave;
342     }
343
344   if (opt.directory)
345     dirname = xtrystrdup (opt.directory);
346   else
347     {
348       if (filename)
349         {
350           dirprefix = strrchr (filename, '/');
351           if (dirprefix)
352             dirprefix++;
353           else
354             dirprefix = filename;
355         }
356       else if (opt.filename)
357         {
358           dirprefix = strrchr (opt.filename, '/');
359           if (dirprefix)
360             dirprefix++;
361           else
362             dirprefix = opt.filename;
363         }
364
365       if (!dirprefix || !*dirprefix)
366         dirprefix = "GPGARCH";
367
368       dirname = create_directory (dirprefix);
369       if (!dirname)
370         {
371           err = gpg_error (GPG_ERR_GENERAL);
372           goto leave;
373         }
374     }
375
376   if (opt.verbose)
377     log_info ("extracting to '%s/'\n", dirname);
378
379   for (;;)
380     {
381       err = gpgtar_read_header (stream, &header);
382       if (err || header == NULL)
383         goto leave;
384
385       err = extract (stream, dirname, header);
386       if (err)
387         goto leave;
388       xfree (header);
389       header = NULL;
390     }
391
392
393  leave:
394   xfree (header);
395   xfree (dirname);
396   if (stream != es_stdin)
397     es_fclose (stream);
398   if (stream != cipher_stream)
399     es_fclose (cipher_stream);
400   return err;
401 }