scd: Force key attribute change for writekey.
[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 <http://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/sysutils.h"
32 #include "gpgtar.h"
33
34
35 static gpg_error_t
36 extract_regular (estream_t stream, const char *dirname,
37                  tar_header_t hdr)
38 {
39   gpg_error_t err;
40   char record[RECORDSIZE];
41   size_t n, nbytes, nwritten;
42   char *fname;
43   estream_t outfp = NULL;
44
45   fname = strconcat (dirname, "/", hdr->name, NULL);
46   if (!fname)
47     {
48       err = gpg_error_from_syserror ();
49       log_error ("error creating filename: %s\n", gpg_strerror (err));
50       goto leave;
51     }
52   else
53     err = 0;
54
55   outfp = es_fopen (fname, "wb");
56   if (!outfp)
57     {
58       err = gpg_error_from_syserror ();
59       log_error ("error creating '%s': %s\n", fname, gpg_strerror (err));
60       goto leave;
61     }
62
63   for (n=0; n < hdr->nrecords;)
64     {
65       err = read_record (stream, record);
66       if (err)
67         goto leave;
68       n++;
69       if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
70         nbytes = RECORDSIZE;
71       else
72         nbytes = (hdr->size % RECORDSIZE);
73
74       nwritten = es_fwrite (record, 1, nbytes, outfp);
75       if (nwritten != nbytes)
76         {
77           err = gpg_error_from_syserror ();
78           log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
79           goto leave;
80         }
81     }
82   /* Fixme: Set permissions etc.  */
83
84  leave:
85   if (!err && opt.verbose)
86     log_info ("extracted '%s'\n", fname);
87   es_fclose (outfp);
88   if (err && fname && outfp)
89     {
90       if (gnupg_remove (fname))
91         log_error ("error removing incomplete file '%s': %s\n",
92                    fname, gpg_strerror (gpg_error_from_syserror ()));
93     }
94   xfree (fname);
95   return err;
96 }
97
98
99 static gpg_error_t
100 extract_directory (const char *dirname, tar_header_t hdr)
101 {
102   gpg_error_t err;
103   char *fname;
104   size_t prefixlen;
105
106   prefixlen = strlen (dirname) + 1;
107   fname = strconcat (dirname, "/", hdr->name, NULL);
108   if (!fname)
109     {
110       err = gpg_error_from_syserror ();
111       log_error ("error creating filename: %s\n", gpg_strerror (err));
112       goto leave;
113     }
114   else
115     err = 0;
116
117   if (fname[strlen (fname)-1] == '/')
118     fname[strlen (fname)-1] = 0;
119
120  /* Note that we don't need to care about EEXIST because we always
121      extract into a new hierarchy.  */
122   if (gnupg_mkdir (fname, "-rwx------"))
123     {
124       err = gpg_error_from_syserror ();
125       if (gpg_err_code (err) == GPG_ERR_ENOENT)
126         {
127           /* Try to create the directory with parents but keep the
128              original error code in case of a failure.  */
129           char *p;
130           int rc = 0;
131
132           for (p = fname+prefixlen; (p = strchr (p, '/')); p++)
133             {
134               *p = 0;
135               rc = gnupg_mkdir (fname, "-rwx------");
136               *p = '/';
137               if (rc)
138                 break;
139             }
140           if (!rc && !gnupg_mkdir (fname, "-rwx------"))
141             err = 0;
142         }
143       if (err)
144         log_error ("error creating directory '%s': %s\n",
145                    fname, gpg_strerror (err));
146     }
147
148  leave:
149   if (!err && opt.verbose)
150     log_info ("created   '%s/'\n", fname);
151   xfree (fname);
152   return err;
153 }
154
155
156 static gpg_error_t
157 extract (estream_t stream, const char *dirname, tar_header_t hdr)
158 {
159   gpg_error_t err;
160   size_t n;
161
162   n = strlen (hdr->name);
163 #ifdef HAVE_DOSISH_SYSTEM
164   if (strchr (hdr->name, '\\'))
165     {
166       log_error ("filename '%s' contains a backslash - "
167                  "can't extract on this system\n", hdr->name);
168       return gpg_error (GPG_ERR_INV_NAME);
169     }
170 #endif /*HAVE_DOSISH_SYSTEM*/
171
172   if (!n
173       || strstr (hdr->name, "//")
174       || strstr (hdr->name, "/../")
175       || !strncmp (hdr->name, "../", 3)
176       || (n >= 3 && !strcmp (hdr->name+n-3, "/.." )))
177     {
178       log_error ("filename '%s' as suspicious parts - not extracting\n",
179                  hdr->name);
180       return gpg_error (GPG_ERR_INV_NAME);
181     }
182
183   if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
184     err = extract_regular (stream, dirname, hdr);
185   else if (hdr->typeflag == TF_DIRECTORY)
186     err = extract_directory (dirname, hdr);
187   else
188     {
189       char record[RECORDSIZE];
190
191       log_info ("unsupported file type %d for '%s' - skipped\n",
192                 (int)hdr->typeflag, hdr->name);
193       for (err = 0, n=0; !err && n < hdr->nrecords; n++)
194         err = read_record (stream, record);
195     }
196   return err;
197 }
198
199
200 /* Create a new directory to be used for extracting the tarball.
201    Returns the name of the directory which must be freed by the
202    caller.  In case of an error a diagnostic is printed and NULL
203    returned.  */
204 static char *
205 create_directory (const char *dirprefix)
206 {
207   gpg_error_t err = 0;
208   char *prefix_buffer = NULL;
209   char *dirname = NULL;
210   size_t n;
211   int idx;
212
213   /* Remove common suffixes.  */
214   n = strlen (dirprefix);
215   if (n > 4 && (!compare_filenames    (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG)
216                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp")
217                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc")
218                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem")
219                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m")
220                 || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e")))
221     {
222       prefix_buffer = xtrystrdup (dirprefix);
223       if (!prefix_buffer)
224         {
225           err = gpg_error_from_syserror ();
226           goto leave;
227         }
228       prefix_buffer[n-4] = 0;
229       dirprefix = prefix_buffer;
230     }
231
232
233
234   for (idx=1; idx < 5000; idx++)
235     {
236       xfree (dirname);
237       dirname = xtryasprintf ("%s_%d_", dirprefix, idx);
238       if (!dirname)
239         {
240           err = gpg_error_from_syserror ();
241           goto leave;
242         }
243       if (!gnupg_mkdir (dirname, "-rwx------"))
244         goto leave; /* Ready.  */
245       if (errno != EEXIST && errno != ENOTDIR)
246         {
247           err = gpg_error_from_syserror ();
248           goto leave;
249         }
250     }
251   err = gpg_error (GPG_ERR_LIMIT_REACHED);
252
253  leave:
254   if (err)
255     {
256       log_error ("error creating an extract directory: %s\n",
257                  gpg_strerror (err));
258       xfree (dirname);
259       dirname = NULL;
260     }
261   xfree (prefix_buffer);
262   return dirname;
263 }
264
265
266 \f
267 void
268 gpgtar_extract (const char *filename)
269 {
270   gpg_error_t err;
271   estream_t stream;
272   tar_header_t header = NULL;
273   const char *dirprefix = NULL;
274   char *dirname = NULL;
275
276   if (filename)
277     {
278       if (!strcmp (filename, "-"))
279         stream = es_stdout;
280       else
281         stream = es_fopen (filename, "rb");
282       if (!stream)
283         {
284           err = gpg_error_from_syserror ();
285           log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
286           return;
287         }
288     }
289   else
290     stream = es_stdin;
291
292   if (stream == es_stdin)
293     es_set_binary (es_stdin);
294
295   if (filename)
296     {
297       dirprefix = strrchr (filename, '/');
298       if (dirprefix)
299         dirprefix++;
300       else
301         dirprefix = filename;
302     }
303   else if (opt.filename)
304     {
305       dirprefix = strrchr (opt.filename, '/');
306       if (dirprefix)
307         dirprefix++;
308       else
309         dirprefix = opt.filename;
310     }
311
312   if (!dirprefix || !*dirprefix)
313     dirprefix = "GPGARCH";
314
315   dirname = create_directory (dirprefix);
316   if (!dirname)
317     {
318       err = gpg_error (GPG_ERR_GENERAL);
319       goto leave;
320     }
321
322   if (opt.verbose)
323     log_info ("extracting to '%s/'\n", dirname);
324
325   for (;;)
326     {
327       header = gpgtar_read_header (stream);
328       if (!header)
329         goto leave;
330
331       if (extract (stream, dirname, header))
332         goto leave;
333       xfree (header);
334       header = NULL;
335     }
336
337
338  leave:
339   xfree (header);
340   xfree (dirname);
341   if (stream != es_stdin)
342     es_fclose (stream);
343   return;
344 }