Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34381783
gpgtar-extract.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
14 KB
Subscribers
None
gpgtar-extract.c
View Options
/* gpgtar-extract.c - Extract from a TAR archive
* Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH
* Copyright (C) 2010, 2012, 2013 Werner Koch
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include
<config.h>
#include
<errno.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<unistd.h>
#include
"../common/i18n.h"
#include
<gpg-error.h>
#include
"../common/exechelp.h"
#include
"../common/sysutils.h"
#include
"../common/ccparray.h"
#include
"gpgtar.h"
static
gpg_error_t
check_suspicious_name
(
const
char
*
name
,
tarinfo_t
info
)
{
size_t
n
;
n
=
strlen
(
name
);
#ifdef HAVE_DOSISH_SYSTEM
if
(
strchr
(
name
,
'\\'
))
{
log_error
(
"filename '%s' contains a backslash - "
"can't extract on this system
\n
"
,
name
);
info
->
skipped_badname
++
;
return
gpg_error
(
GPG_ERR_INV_NAME
);
}
#endif
/*HAVE_DOSISH_SYSTEM*/
if
(
!
n
||
strstr
(
name
,
"//"
)
||
strstr
(
name
,
"/../"
)
||
!
strncmp
(
name
,
"../"
,
3
)
||
(
n
>=
3
&&
!
strcmp
(
name
+
n
-3
,
"/.."
)))
{
log_error
(
"filename '%s' has suspicious parts - not extracting
\n
"
,
name
);
info
->
skipped_suspicious
++
;
return
gpg_error
(
GPG_ERR_INV_NAME
);
}
return
0
;
}
static
gpg_error_t
extract_regular
(
estream_t
stream
,
const
char
*
dirname
,
tarinfo_t
info
,
tar_header_t
hdr
,
strlist_t
exthdr
)
{
gpg_error_t
err
;
char
record
[
RECORDSIZE
];
size_t
n
,
nbytes
,
nwritten
;
char
*
fname_buffer
=
NULL
;
const
char
*
fname
;
estream_t
outfp
=
NULL
;
strlist_t
sl
;
fname
=
hdr
->
name
;
for
(
sl
=
exthdr
;
sl
;
sl
=
sl
->
next
)
if
(
sl
->
flags
==
1
)
fname
=
sl
->
d
;
err
=
check_suspicious_name
(
fname
,
info
);
if
(
err
)
goto
leave
;
fname_buffer
=
strconcat
(
dirname
,
"/"
,
fname
,
NULL
);
if
(
!
fname_buffer
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error creating filename: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
fname
=
fname_buffer
;
if
(
opt
.
dry_run
)
outfp
=
es_fopen
(
"/dev/null"
,
"wb"
);
else
outfp
=
es_fopen
(
fname
,
"wb,sysopen"
);
if
(
!
outfp
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error creating '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
for
(
n
=
0
;
n
<
hdr
->
nrecords
;)
{
err
=
read_record
(
stream
,
record
);
if
(
err
)
goto
leave
;
info
->
nblocks
++
;
n
++
;
if
(
n
<
hdr
->
nrecords
||
(
hdr
->
size
&&
!
(
hdr
->
size
%
RECORDSIZE
)))
nbytes
=
RECORDSIZE
;
else
nbytes
=
(
hdr
->
size
%
RECORDSIZE
);
nwritten
=
es_fwrite
(
record
,
1
,
nbytes
,
outfp
);
if
(
nwritten
!=
nbytes
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error writing '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
goto
leave
;
}
}
/* Fixme: Set permissions etc. */
leave
:
if
(
!
err
)
{
if
(
opt
.
verbose
)
log_info
(
"extracted '%s'
\n
"
,
fname
);
info
->
nextracted
++
;
}
es_fclose
(
outfp
);
if
(
err
&&
fname
&&
outfp
)
{
if
(
gnupg_remove
(
fname
))
log_error
(
"error removing incomplete file '%s': %s
\n
"
,
fname
,
gpg_strerror
(
gpg_error_from_syserror
()));
}
xfree
(
fname_buffer
);
return
err
;
}
static
gpg_error_t
extract_directory
(
const
char
*
dirname
,
tarinfo_t
info
,
tar_header_t
hdr
,
strlist_t
exthdr
)
{
gpg_error_t
err
;
const
char
*
name
;
char
*
fname
=
NULL
;
strlist_t
sl
;
name
=
hdr
->
name
;
for
(
sl
=
exthdr
;
sl
;
sl
=
sl
->
next
)
if
(
sl
->
flags
==
1
)
name
=
sl
->
d
;
err
=
check_suspicious_name
(
name
,
info
);
if
(
err
)
goto
leave
;
fname
=
strconcat
(
dirname
,
"/"
,
name
,
NULL
);
if
(
!
fname
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error creating filename: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
}
/* Remove a possible trailing slash. */
if
(
fname
[
strlen
(
fname
)
-1
]
==
'/'
)
fname
[
strlen
(
fname
)
-1
]
=
0
;
if
(
!
opt
.
dry_run
&&
gnupg_mkdir
(
fname
,
"-rwx------"
))
{
err
=
gpg_error_from_syserror
();
if
(
gpg_err_code
(
err
)
==
GPG_ERR_EEXIST
)
{
/* Ignore existing directories while extracting. */
err
=
0
;
}
if
(
gpg_err_code
(
err
)
==
GPG_ERR_ENOENT
)
{
/* Try to create the directory with parents but keep the
original error code in case of a failure. */
int
rc
=
0
;
char
*
p
;
size_t
prefixlen
;
/* (PREFIXLEN is the length of the new directory we use to
* extract the tarball.) */
prefixlen
=
strlen
(
dirname
)
+
1
;
for
(
p
=
fname
+
prefixlen
;
(
p
=
strchr
(
p
,
'/'
));
p
++
)
{
*
p
=
0
;
rc
=
gnupg_mkdir
(
fname
,
"-rwx------"
);
if
(
gpg_err_code
(
rc
)
==
GPG_ERR_EEXIST
)
rc
=
0
;
*
p
=
'/'
;
if
(
rc
)
break
;
}
if
(
!
rc
&&
!
gnupg_mkdir
(
fname
,
"-rwx------"
))
err
=
0
;
}
if
(
err
)
log_error
(
"error creating directory '%s': %s
\n
"
,
fname
,
gpg_strerror
(
err
));
}
leave
:
if
(
!
err
&&
opt
.
verbose
)
log_info
(
"created '%s/'
\n
"
,
fname
);
xfree
(
fname
);
return
err
;
}
static
gpg_error_t
extract
(
estream_t
stream
,
const
char
*
dirname
,
tarinfo_t
info
,
tar_header_t
hdr
,
strlist_t
exthdr
)
{
gpg_error_t
err
;
size_t
n
;
if
(
hdr
->
typeflag
==
TF_REGULAR
||
hdr
->
typeflag
==
TF_UNKNOWN
)
err
=
extract_regular
(
stream
,
dirname
,
info
,
hdr
,
exthdr
);
else
if
(
hdr
->
typeflag
==
TF_DIRECTORY
)
err
=
extract_directory
(
dirname
,
info
,
hdr
,
exthdr
);
else
{
char
record
[
RECORDSIZE
];
log_info
(
"unsupported file type %d for '%s' - skipped
\n
"
,
(
int
)
hdr
->
typeflag
,
hdr
->
name
);
if
(
hdr
->
typeflag
==
TF_SYMLINK
)
info
->
skipped_symlinks
++
;
else
if
(
hdr
->
typeflag
==
TF_HARDLINK
)
info
->
skipped_hardlinks
++
;
else
info
->
skipped_other
++
;
for
(
err
=
0
,
n
=
0
;
!
err
&&
n
<
hdr
->
nrecords
;
n
++
)
{
err
=
read_record
(
stream
,
record
);
if
(
!
err
)
info
->
nblocks
++
;
}
}
return
err
;
}
/* Create a new directory to be used for extracting the tarball.
Returns the name of the directory which must be freed by the
caller. In case of an error a diagnostic is printed and NULL
returned. */
static
char
*
create_directory
(
const
char
*
dirprefix
)
{
gpg_error_t
err
=
0
;
char
*
prefix_buffer
=
NULL
;
char
*
dirname
=
NULL
;
size_t
n
;
int
idx
;
/* Remove common suffixes. */
n
=
strlen
(
dirprefix
);
if
(
n
>
4
&&
(
!
compare_filenames
(
dirprefix
+
n
-
4
,
EXTSEP_S
GPGEXT_GPG
)
||
!
compare_filenames
(
dirprefix
+
n
-
4
,
EXTSEP_S
"pgp"
)
||
!
compare_filenames
(
dirprefix
+
n
-
4
,
EXTSEP_S
"asc"
)
||
!
compare_filenames
(
dirprefix
+
n
-
4
,
EXTSEP_S
"pem"
)
||
!
compare_filenames
(
dirprefix
+
n
-
4
,
EXTSEP_S
"p7m"
)
||
!
compare_filenames
(
dirprefix
+
n
-
4
,
EXTSEP_S
"p7e"
)))
{
prefix_buffer
=
xtrystrdup
(
dirprefix
);
if
(
!
prefix_buffer
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
prefix_buffer
[
n
-4
]
=
0
;
dirprefix
=
prefix_buffer
;
}
for
(
idx
=
1
;
idx
<
5000
;
idx
++
)
{
xfree
(
dirname
);
dirname
=
xtryasprintf
(
"%s_%d_"
,
dirprefix
,
idx
);
if
(
!
dirname
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
if
(
!
gnupg_mkdir
(
dirname
,
"-rwx------"
))
goto
leave
;
/* Ready. */
if
(
errno
!=
EEXIST
&&
errno
!=
ENOTDIR
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
}
err
=
gpg_error
(
GPG_ERR_LIMIT_REACHED
);
leave
:
if
(
err
)
{
log_error
(
"error creating an extract directory: %s
\n
"
,
gpg_strerror
(
err
));
xfree
(
dirname
);
dirname
=
NULL
;
}
xfree
(
prefix_buffer
);
return
dirname
;
}
gpg_error_t
gpgtar_extract
(
const
char
*
filename
,
int
decrypt
)
{
gpg_error_t
err
;
estream_t
stream
=
NULL
;
tar_header_t
header
=
NULL
;
strlist_t
extheader
=
NULL
;
const
char
*
dirprefix
=
NULL
;
char
*
dirname
=
NULL
;
struct
tarinfo_s
tarinfo_buffer
;
tarinfo_t
tarinfo
=
&
tarinfo_buffer
;
pid_t
pid
=
(
pid_t
)(
-1
);
char
*
logfilename
=
NULL
;
unsigned
long
long
notextracted
;
memset
(
&
tarinfo_buffer
,
0
,
sizeof
tarinfo_buffer
);
if
(
opt
.
directory
)
dirname
=
xtrystrdup
(
opt
.
directory
);
else
{
if
(
opt
.
filename
)
{
dirprefix
=
strrchr
(
opt
.
filename
,
'/'
);
if
(
dirprefix
)
dirprefix
++
;
else
dirprefix
=
opt
.
filename
;
}
else
if
(
filename
)
{
dirprefix
=
strrchr
(
filename
,
'/'
);
if
(
dirprefix
)
dirprefix
++
;
else
dirprefix
=
filename
;
}
if
(
!
dirprefix
||
!*
dirprefix
)
dirprefix
=
"GPGARCH"
;
dirname
=
create_directory
(
dirprefix
);
if
(
!
dirname
)
{
err
=
gpg_error
(
GPG_ERR_GENERAL
);
goto
leave
;
}
}
if
(
opt
.
verbose
)
log_info
(
"extracting to '%s/'
\n
"
,
dirname
);
if
(
decrypt
)
{
strlist_t
arg
;
ccparray_t
ccp
;
int
except
[
2
]
=
{
-1
,
-1
};
const
char
**
argv
;
ccparray_init
(
&
ccp
,
0
);
if
(
opt
.
batch
)
ccparray_put
(
&
ccp
,
"--batch"
);
if
(
opt
.
require_compliance
)
ccparray_put
(
&
ccp
,
"--require-compliance"
);
if
(
opt
.
status_fd
!=
-1
)
{
static
char
tmpbuf
[
40
];
snprintf
(
tmpbuf
,
sizeof
tmpbuf
,
"--status-fd=%d"
,
opt
.
status_fd
);
ccparray_put
(
&
ccp
,
tmpbuf
);
except
[
0
]
=
opt
.
status_fd
;
}
if
(
opt
.
with_log
)
{
ccparray_put
(
&
ccp
,
"--log-file"
);
logfilename
=
xstrconcat
(
dirname
,
".log"
,
NULL
);
ccparray_put
(
&
ccp
,
logfilename
);
}
ccparray_put
(
&
ccp
,
"--output"
);
ccparray_put
(
&
ccp
,
"-"
);
ccparray_put
(
&
ccp
,
"--decrypt"
);
for
(
arg
=
opt
.
gpg_arguments
;
arg
;
arg
=
arg
->
next
)
ccparray_put
(
&
ccp
,
arg
->
d
);
if
(
filename
)
{
ccparray_put
(
&
ccp
,
"--"
);
ccparray_put
(
&
ccp
,
filename
);
}
ccparray_put
(
&
ccp
,
NULL
);
argv
=
ccparray_get
(
&
ccp
,
NULL
);
if
(
!
argv
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
err
=
gnupg_spawn_process
(
opt
.
gpg_program
,
argv
,
except
[
0
]
==
-1
?
NULL
:
except
,
((
filename
?
0
:
GNUPG_SPAWN_KEEP_STDIN
)
|
GNUPG_SPAWN_KEEP_STDERR
),
NULL
,
&
stream
,
NULL
,
&
pid
);
xfree
(
argv
);
if
(
err
)
goto
leave
;
es_set_binary
(
stream
);
}
else
if
(
filename
)
{
if
(
!
strcmp
(
filename
,
"-"
))
stream
=
es_stdin
;
else
stream
=
es_fopen
(
filename
,
"rb,sysopen"
);
if
(
!
stream
)
{
err
=
gpg_error_from_syserror
();
log_error
(
"error opening '%s': %s
\n
"
,
filename
,
gpg_strerror
(
err
));
return
err
;
}
if
(
stream
==
es_stdin
)
es_set_binary
(
es_stdin
);
}
else
{
stream
=
es_stdin
;
es_set_binary
(
es_stdin
);
}
for
(;;)
{
err
=
gpgtar_read_header
(
stream
,
tarinfo
,
&
header
,
&
extheader
);
if
(
err
||
header
==
NULL
)
goto
leave
;
err
=
extract
(
stream
,
dirname
,
tarinfo
,
header
,
extheader
);
if
(
err
)
goto
leave
;
free_strlist
(
extheader
);
extheader
=
NULL
;
xfree
(
header
);
header
=
NULL
;
}
if
(
pid
!=
(
pid_t
)(
-1
))
{
int
exitcode
;
err
=
es_fclose
(
stream
);
stream
=
NULL
;
if
(
err
)
log_error
(
"error closing pipe: %s
\n
"
,
gpg_strerror
(
err
));
else
{
err
=
gnupg_wait_process
(
opt
.
gpg_program
,
pid
,
1
,
&
exitcode
);
if
(
err
)
log_error
(
"running %s failed (exitcode=%d): %s"
,
opt
.
gpg_program
,
exitcode
,
gpg_strerror
(
err
));
gnupg_release_process
(
pid
);
pid
=
(
pid_t
)(
-1
);
}
}
leave
:
notextracted
=
tarinfo
->
skipped_badname
;
notextracted
+=
tarinfo
->
skipped_suspicious
;
notextracted
+=
tarinfo
->
skipped_symlinks
;
notextracted
+=
tarinfo
->
skipped_hardlinks
;
notextracted
+=
tarinfo
->
skipped_other
;
if
(
opt
.
status_stream
)
es_fprintf
(
opt
.
status_stream
,
"[GNUPG:] GPGTAR_EXTRACT"
" %llu %llu %lu %lu %lu %lu %lu
\n
"
,
tarinfo
->
nextracted
,
notextracted
,
tarinfo
->
skipped_badname
,
tarinfo
->
skipped_suspicious
,
tarinfo
->
skipped_symlinks
,
tarinfo
->
skipped_hardlinks
,
tarinfo
->
skipped_other
);
if
(
notextracted
&&
!
opt
.
quiet
)
{
log_info
(
"Number of files not extracted: %llu
\n
"
,
notextracted
);
if
(
tarinfo
->
skipped_badname
)
log_info
(
" invalid name: %lu
\n
"
,
tarinfo
->
skipped_badname
);
if
(
tarinfo
->
skipped_suspicious
)
log_info
(
" suspicious name: %lu
\n
"
,
tarinfo
->
skipped_suspicious
);
if
(
tarinfo
->
skipped_symlinks
)
log_info
(
" symlink: %lu
\n
"
,
tarinfo
->
skipped_symlinks
);
if
(
tarinfo
->
skipped_hardlinks
)
log_info
(
" hardlink: %lu
\n
"
,
tarinfo
->
skipped_hardlinks
);
if
(
tarinfo
->
skipped_other
)
log_info
(
" other reason: %lu
\n
"
,
tarinfo
->
skipped_other
);
}
free_strlist
(
extheader
);
xfree
(
header
);
xfree
(
dirname
);
xfree
(
logfilename
);
if
(
stream
!=
es_stdin
)
es_fclose
(
stream
);
return
err
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Jan 3, 11:46 PM (9 h, 38 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
55/ac/ac9880fc887a308d09936fc42791
Attached To
rG GnuPG
Event Timeline
Log In to Comment