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