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