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