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