Add unfinished gpgtar.
[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
36 static gpg_error_t
37 extract_regular (estream_t stream, const char *dirname,
38                  tar_header_t hdr)
39 {
40   gpg_error_t err;
41   char record[RECORDSIZE];
42   size_t n, nbytes, nwritten;
43   char *fname;
44   estream_t outfp = NULL;
45
46   fname = strconcat (dirname, "/", hdr->name, NULL);
47   if (!fname)
48     {
49       err = gpg_error_from_syserror ();
50       log_error ("error creating filename: %s\n", gpg_strerror (err));
51       goto leave;
52     }
53   else
54     err = 0;
55   
56   outfp = es_fopen (fname, "wb");
57   if (!outfp)
58     {
59       err = gpg_error_from_syserror ();
60       log_error ("error creating `%s': %s\n", fname, gpg_strerror (err));
61       goto leave;
62     }
63
64   for (n=0; n < hdr->nrecords;)
65     {
66       err = read_record (stream, record);
67       if (err)
68         goto leave;
69       n++;
70       nbytes = (n < hdr->nrecords)? RECORDSIZE : (hdr->size % RECORDSIZE);
71       nwritten = es_fwrite (record, 1, nbytes, outfp);
72       if (nwritten != nbytes)
73         {
74           err = gpg_error_from_syserror ();
75           log_error ("error writing `%s': %s\n", fname, gpg_strerror (err));
76           goto leave;
77         }
78     }
79   /* Fixme: Set permissions etc.  */
80
81  leave:
82   es_fclose (outfp);
83   if (err && fname && outfp)
84     {
85       if (gnupg_remove (fname))
86         log_error ("error removing incomplete file `%s': %s\n",
87                    fname, gpg_strerror (gpg_error_from_syserror ()));
88     }
89   xfree (fname);
90   return err;
91 }
92
93
94 static gpg_error_t
95 extract_directory (const char *dirname, tar_header_t hdr)
96 {
97   gpg_error_t err;
98   char *fname;
99
100   fname = strconcat (dirname, "/", hdr->name, NULL);
101   if (!fname)
102     {
103       err = gpg_error_from_syserror ();
104       log_error ("error creating filename: %s\n", gpg_strerror (err));
105       goto leave;
106     }
107   else
108     err = 0;
109
110   if (gnupg_mkdir (fname, "-rwx------"))
111     {
112       err = gpg_error_from_syserror ();
113       log_error ("error creating directory `%s': %s\n",
114                  fname, gpg_strerror (err));
115     }
116
117  leave:
118   xfree (fname);
119   return err;
120 }
121
122
123 static gpg_error_t
124 extract (estream_t stream, const char *dirname, tar_header_t hdr)
125 {
126   gpg_error_t err;
127   size_t n;
128
129   n = strlen (hdr->name);
130 #ifdef HAVE_DOSISH_SYSTEM
131   if (strchr (hdr->name, '\\'))
132     {
133       log_error ("filename `%s' contains a backslash - "
134                  "can't extract on this system\n", hdr->name);
135       return gpg_error (GPG_ERR_INV_NAME);
136     }
137 #endif /*HAVE_DOSISH_SYSTEM*/
138
139   if (!n
140       || strstr (hdr->name, "//") 
141       || strstr (hdr->name, "/../") 
142       || !strncmp (hdr->name, "../", 3)
143       || (n >= 3 && !strcmp (hdr->name+n-3, "/.." )))
144     {
145       log_error ("filename `%s' as suspicious parts - not extracting\n",
146                  hdr->name);
147       return gpg_error (GPG_ERR_INV_NAME);
148     }
149
150   if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN)
151     err = extract_regular (stream, dirname, hdr);
152   else if (hdr->typeflag == TF_DIRECTORY)
153     err = extract_directory (dirname, hdr);
154   else
155     {
156       char record[RECORDSIZE];
157
158       log_info ("unsupported file type for `%s' - skipped\n", hdr->name);
159       for (err = 0, n=0; !err && n < hdr->nrecords; n++)
160         err = read_record (stream, record);
161     }
162   return err;
163 }
164
165
166 /* Create a new directory to be used for extracting the tarball.
167    Returns the name of the directory which must be freed by the
168    caller.  In case of an error a diagnostic is printed and NULL
169    returned.  */
170 static char *
171 create_directory (const char *dirprefix)
172 {
173   gpg_error_t err = 0;
174   char *dirname = NULL;
175   int idx;
176
177   for (idx=1; idx < 5000; idx++)
178     {
179       xfree (dirname);
180       dirname = xtryasprintf ("%s_%d_", dirprefix, idx);
181       if (!dirname)
182         {
183           err = gpg_error_from_syserror ();
184           goto leave;
185         }
186       if (!gnupg_mkdir (dirname, "-rwx------"))
187         goto leave;
188       if (errno != EEXIST && errno != ENOTDIR)
189         {
190           err = gpg_error_from_syserror ();
191           goto leave;
192         }
193     }
194   err = gpg_error_from_syserror ();
195
196  leave:
197   if (err)
198     {
199       log_error ("error creating an extract directory: %s\n",
200                  gpg_strerror (err));
201       xfree (dirname);
202       dirname = NULL;
203     }
204   return dirname;
205 }
206
207
208 \f
209 void
210 gpgtar_extract (const char *filename)
211 {
212   gpg_error_t err;
213   estream_t stream;
214   tar_header_t header = NULL;
215   const char *dirprefix = NULL;
216   char *dirname = NULL;
217
218   if (filename)
219     {
220       dirprefix = strrchr (filename, '/');
221       if (dirprefix)
222         dirprefix++;
223       stream = es_fopen (filename, "rb");
224       if (!stream)
225         {
226           err = gpg_error_from_syserror ();
227           log_error ("error opening `%s': %s\n", filename, gpg_strerror (err));
228           return;
229         }
230     }
231   else
232     stream = es_stdin;  /* FIXME:  How can we enforce binary mode?  */
233
234   if (!dirprefix || !*dirprefix)
235     dirprefix = "GPGARCH";
236
237   dirname = create_directory (dirprefix);
238   if (!dirname)
239     {
240       err = gpg_error (GPG_ERR_GENERAL);
241       goto leave;
242     }
243
244   if (opt.verbose)
245     log_info ("extracting to `%s/'\n", dirname);
246
247   for (;;)
248     {
249       header = gpgtar_read_header (stream);
250       if (!header)
251         goto leave;
252      
253       if (extract (stream, dirname, header))
254         goto leave;
255       xfree (header);
256       header = NULL;
257     }
258
259
260  leave:
261   xfree (header);
262   xfree (dirname);
263   if (stream != es_stdin)
264     es_fclose (stream);
265   return;
266 }