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