1 /* vi: set sw=4 ts=4: */
2 /*
3  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
4  */
5 #include "libbb.h"
6 #include "bb_archive.h"
7 
data_extract_all(archive_handle_t * archive_handle)8 void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
9 {
10 	file_header_t *file_header = archive_handle->file_header;
11 	int dst_fd;
12 	int res;
13 	char *hard_link;
14 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
15 	char *dst_name;
16 #else
17 # define dst_name (file_header->name)
18 #endif
19 
20 #if ENABLE_FEATURE_TAR_SELINUX
21 	char *sctx = archive_handle->tar__sctx[PAX_NEXT_FILE];
22 	if (!sctx)
23 		sctx = archive_handle->tar__sctx[PAX_GLOBAL];
24 	if (sctx) { /* setfscreatecon is 4 syscalls, avoid if possible */
25 		setfscreatecon(sctx);
26 		free(archive_handle->tar__sctx[PAX_NEXT_FILE]);
27 		archive_handle->tar__sctx[PAX_NEXT_FILE] = NULL;
28 	}
29 #endif
30 
31 	/* Hard links are encoded as regular files of size 0
32 	 * with a nonempty link field */
33 	hard_link = NULL;
34 	if (S_ISREG(file_header->mode) && file_header->size == 0)
35 		hard_link = file_header->link_target;
36 
37 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
38 	dst_name = file_header->name;
39 	if (archive_handle->tar__strip_components) {
40 		unsigned n = archive_handle->tar__strip_components;
41 		do {
42 			dst_name = strchr(dst_name, '/');
43 			if (!dst_name || dst_name[1] == '\0') {
44 				data_skip(archive_handle);
45 				goto ret;
46 			}
47 			dst_name++;
48 			/*
49 			 * Link target is shortened only for hardlinks:
50 			 * softlinks restored unchanged.
51 			 */
52 			if (hard_link) {
53 // GNU tar 1.26 does not check that we reached end of link name:
54 // if "dir/hardlink" is hardlinked to "file",
55 // tar xvf a.tar --strip-components=1 says:
56 //  tar: hardlink: Cannot hard link to '': No such file or directory
57 // and continues processing. We silently skip such entries.
58 				hard_link = strchr(hard_link, '/');
59 				if (!hard_link || hard_link[1] == '\0') {
60 					data_skip(archive_handle);
61 					goto ret;
62 				}
63 				hard_link++;
64 			}
65 		} while (--n != 0);
66 	}
67 #endif
68 
69 	if (archive_handle->ah_flags & ARCHIVE_CREATE_LEADING_DIRS) {
70 		char *slash = strrchr(dst_name, '/');
71 		if (slash) {
72 			*slash = '\0';
73 			bb_make_directory(dst_name, -1, FILEUTILS_RECUR);
74 			*slash = '/';
75 		}
76 	}
77 
78 	if (archive_handle->ah_flags & ARCHIVE_UNLINK_OLD) {
79 		/* Remove the entry if it exists */
80 		if (!S_ISDIR(file_header->mode)) {
81 			if (hard_link) {
82 				/* Ugly special case:
83 				 * tar cf t.tar hardlink1 hardlink2 hardlink1
84 				 * results in this tarball structure:
85 				 * hardlink1
86 				 * hardlink2 -> hardlink1
87 				 * hardlink1 -> hardlink1 <== !!!
88 				 */
89 				if (strcmp(hard_link, dst_name) == 0)
90 					goto ret;
91 			}
92 			/* Proceed with deleting */
93 			if (unlink(dst_name) == -1
94 			 && errno != ENOENT
95 			) {
96 				bb_perror_msg_and_die("can't remove old file %s",
97 						dst_name);
98 			}
99 		}
100 	}
101 	else if (archive_handle->ah_flags & ARCHIVE_EXTRACT_NEWER) {
102 		/* Remove the existing entry if its older than the extracted entry */
103 		struct stat existing_sb;
104 		if (lstat(dst_name, &existing_sb) == -1) {
105 			if (errno != ENOENT) {
106 				bb_simple_perror_msg_and_die("can't stat old file");
107 			}
108 		}
109 		else if (existing_sb.st_mtime >= file_header->mtime) {
110 			if (!S_ISDIR(file_header->mode)) {
111 				bb_error_msg("%s not created: newer or "
112 					"same age file exists", dst_name);
113 			}
114 			data_skip(archive_handle);
115 			goto ret;
116 		}
117 		else if ((unlink(dst_name) == -1) && (errno != EISDIR)) {
118 			bb_perror_msg_and_die("can't remove old file %s",
119 					dst_name);
120 		}
121 	}
122 
123 	/* Handle hard links separately */
124 	if (hard_link) {
125 		create_or_remember_link(&archive_handle->link_placeholders,
126 				hard_link,
127 				dst_name,
128 				1);
129 		/* Hardlinks have no separate mode/ownership, skip chown/chmod */
130 		goto ret;
131 	}
132 
133 	/* Create the filesystem entry */
134 	switch (file_header->mode & S_IFMT) {
135 	case S_IFREG: {
136 		/* Regular file */
137 		char *dst_nameN;
138 		int flags = O_WRONLY | O_CREAT | O_EXCL;
139 		if (archive_handle->ah_flags & ARCHIVE_O_TRUNC)
140 			flags = O_WRONLY | O_CREAT | O_TRUNC;
141 		dst_nameN = dst_name;
142 #ifdef ARCHIVE_REPLACE_VIA_RENAME
143 		if (archive_handle->ah_flags & ARCHIVE_REPLACE_VIA_RENAME)
144 			/* rpm-style temp file name */
145 			dst_nameN = xasprintf("%s;%x", dst_name, (int)getpid());
146 #endif
147 		dst_fd = xopen3(dst_nameN,
148 			flags,
149 			file_header->mode
150 			);
151 		bb_copyfd_exact_size(archive_handle->src_fd, dst_fd, file_header->size);
152 		close(dst_fd);
153 #ifdef ARCHIVE_REPLACE_VIA_RENAME
154 		if (archive_handle->ah_flags & ARCHIVE_REPLACE_VIA_RENAME) {
155 			xrename(dst_nameN, dst_name);
156 			free(dst_nameN);
157 		}
158 #endif
159 		break;
160 	}
161 	case S_IFDIR:
162 //TODO: this causes problems if tarball contains a r-xr-xr-x directory:
163 // we create this directory, and then fail to create files inside it
164 // (if tar xf isn't run as root).
165 // GNU tar works around this by chmod-ing directories *after* all files are extracted.
166 		res = mkdir(dst_name, file_header->mode);
167 		if ((res != 0)
168 		 && (errno != EISDIR) /* btw, Linux doesn't return this */
169 		 && (errno != EEXIST)
170 		) {
171 			bb_perror_msg("can't make dir %s", dst_name);
172 		}
173 		break;
174 	case S_IFLNK:
175 		/* Symlink */
176 //TODO: what if file_header->link_target == NULL (say, corrupted tarball?)
177 
178 		/* To avoid a directory traversal attack via symlinks,
179 		 * do not restore symlinks with ".." components
180 		 * or symlinks starting with "/", unless a magic
181 		 * envvar is set.
182 		 *
183 		 * For example, consider a .tar created via:
184 		 *  $ tar cvf bug.tar anything.txt
185 		 *  $ ln -s /tmp symlink
186 		 *  $ tar --append -f bug.tar symlink
187 		 *  $ rm symlink
188 		 *  $ mkdir symlink
189 		 *  $ tar --append -f bug.tar symlink/evil.py
190 		 *
191 		 * This will result in an archive that contains:
192 		 *  $ tar --list -f bug.tar
193 		 *  anything.txt
194 		 *  symlink [-> /tmp]
195 		 *  symlink/evil.py
196 		 *
197 		 * Untarring bug.tar would otherwise place evil.py in '/tmp'.
198 		 */
199 		create_or_remember_link(&archive_handle->link_placeholders,
200 				file_header->link_target,
201 				dst_name,
202 				0);
203 		break;
204 	case S_IFSOCK:
205 	case S_IFBLK:
206 	case S_IFCHR:
207 	case S_IFIFO:
208 		res = mknod(dst_name, file_header->mode, file_header->device);
209 		if (res != 0) {
210 			bb_perror_msg("can't create node %s", dst_name);
211 		}
212 		break;
213 	default:
214 		bb_simple_error_msg_and_die("unrecognized file type");
215 	}
216 
217 	if (!S_ISLNK(file_header->mode)) {
218 		if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_OWNER)) {
219 			uid_t uid = file_header->uid;
220 			gid_t gid = file_header->gid;
221 #if ENABLE_FEATURE_TAR_UNAME_GNAME
222 			if (!(archive_handle->ah_flags & ARCHIVE_NUMERIC_OWNER)) {
223 				if (file_header->tar__uname) {
224 //TODO: cache last name/id pair?
225 					struct passwd *pwd = getpwnam(file_header->tar__uname);
226 					if (pwd) uid = pwd->pw_uid;
227 				}
228 				if (file_header->tar__gname) {
229 					struct group *grp = getgrnam(file_header->tar__gname);
230 					if (grp) gid = grp->gr_gid;
231 				}
232 			}
233 #endif
234 			/* GNU tar 1.15.1 uses chown, not lchown */
235 			chown(dst_name, uid, gid);
236 		}
237 		/* uclibc has no lchmod, glibc is even stranger -
238 		 * it has lchmod which seems to do nothing!
239 		 * so we use chmod... */
240 		if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_PERM)) {
241 			chmod(dst_name, file_header->mode);
242 		}
243 		if (archive_handle->ah_flags & ARCHIVE_RESTORE_DATE) {
244 			struct timeval t[2];
245 
246 			t[1].tv_sec = t[0].tv_sec = file_header->mtime;
247 			t[1].tv_usec = t[0].tv_usec = 0;
248 			utimes(dst_name, t);
249 		}
250 	}
251 
252  ret: ;
253 #if ENABLE_FEATURE_TAR_SELINUX
254 	if (sctx) {
255 		/* reset the context after creating an entry */
256 		setfscreatecon(NULL);
257 	}
258 #endif
259 }
260