xref: /freebsd/contrib/libarchive/libarchive/test/test_write_disk_perms.c (revision eb5165bb491138f60d9004bc4c781490016d9288)
1 /*-
2  * Copyright (c) 2003-2007 Tim Kientzle
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #include "test.h"
26 
27 #if !defined(_WIN32) || defined(__CYGWIN__)
28 
29 #ifdef HAVE_GETEUID
30 #define getuid() geteuid()
31 #endif
32 #ifdef HAVE_GETEGID
33 #define getgid() getegid()
34 #endif
35 
36 #define UMASK 022
37 
38 static long _default_gid = -1;
39 static long _invalid_gid = -1;
40 static long _alt_gid = -1;
41 
42 /*
43  * To fully test SGID restores, we need three distinct GIDs to work
44  * with:
45  *    * the GID that files are created with by default (for the
46  *      current user in the current directory)
47  *    * An "alt gid" that this user can create files with
48  *    * An "invalid gid" that this user is not permitted to create
49  *      files with.
50  * The second fails if this user doesn't belong to at least two groups;
51  * the third fails if the current user is root.
52  */
53 static void
searchgid(void)54 searchgid(void)
55 {
56 	static int   _searched = 0;
57 	uid_t uid = getuid();
58 	gid_t gid = 0;
59 	unsigned int n;
60 	struct stat st;
61 	int fd;
62 
63 	/* If we've already looked this up, we're done. */
64 	if (_searched)
65 		return;
66 	_searched = 1;
67 
68 	/* Create a file on disk in the current default dir. */
69 	fd = open("test_gid", O_CREAT | O_BINARY, 0664);
70 	failure("Couldn't create a file for gid testing.");
71 	assert(fd > 0);
72 
73 	/* See what GID it ended up with.  This is our "valid" GID. */
74 	assert(fstat(fd, &st) == 0);
75 	_default_gid = st.st_gid;
76 
77 	/* Find a GID for which fchown() fails.  This is our "invalid" GID. */
78 	_invalid_gid = -1;
79 	/* This loop stops when we wrap the gid or examine 10,000 gids. */
80 	for (gid = 1, n = 1; gid == n && n < 10000 ; n++, gid++) {
81 		if (fchown(fd, uid, gid) != 0) {
82 			_invalid_gid = gid;
83 			break;
84 		}
85 	}
86 
87 	/*
88 	 * Find a GID for which fchown() succeeds, but which isn't the
89 	 * default.  This is the "alternate" gid.
90 	 */
91 	_alt_gid = -1;
92 	for (gid = 0, n = 0; gid == n && n < 10000 ; n++, gid++) {
93 		/* _alt_gid must be different than _default_gid */
94 		if (gid == (gid_t)_default_gid)
95 			continue;
96 		if (fchown(fd, uid, gid) == 0) {
97 			_alt_gid = gid;
98 			break;
99 		}
100 	}
101 	close(fd);
102 }
103 
104 static long
altgid(void)105 altgid(void)
106 {
107 	searchgid();
108 	return (_alt_gid);
109 }
110 
111 static long
invalidgid(void)112 invalidgid(void)
113 {
114 	searchgid();
115 	return (_invalid_gid);
116 }
117 
118 static long
defaultgid(void)119 defaultgid(void)
120 {
121 	searchgid();
122 	return (_default_gid);
123 }
124 #endif
125 
126 /*
127  * Exercise permission and ownership restores.
128  * In particular, try to exercise a bunch of border cases related
129  * to files/dirs that already exist, SUID/SGID bits, etc.
130  */
131 
DEFINE_TEST(test_write_disk_perms)132 DEFINE_TEST(test_write_disk_perms)
133 {
134 #if defined(_WIN32) && !defined(__CYGWIN__)
135 	skipping("archive_write_disk interface");
136 #else
137 	struct archive *a;
138 	struct archive_entry *ae;
139 	struct stat st;
140 	uid_t original_uid;
141 	uid_t try_to_change_uid;
142 
143 	assertUmask(UMASK);
144 
145 	/*
146 	 * Set ownership of the current directory to the group of this
147 	 * process.  Otherwise, the SGID tests below fail if the
148 	 * /tmp directory is owned by a group to which we don't belong
149 	 * and we're on a system where group ownership is inherited.
150 	 * (Because we're not allowed to SGID files with defaultgid().)
151 	 */
152 	assertChown(".", getuid(), getgid());
153 
154 	/* Create an archive_write_disk object. */
155 	assert((a = archive_write_disk_new()) != NULL);
156 
157 	/* Write a regular file to it. */
158 	assert((ae = archive_entry_new()) != NULL);
159 	archive_entry_copy_pathname(ae, "file_0755");
160 	archive_entry_set_mode(ae, S_IFREG | 0777);
161 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
162 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
163 	archive_entry_free(ae);
164 
165 	/* Write a regular file, then write over it. */
166 	/* For files, the perms should get updated. */
167 	assert((ae = archive_entry_new()) != NULL);
168 	archive_entry_copy_pathname(ae, "file_overwrite_0144");
169 	archive_entry_set_mode(ae, S_IFREG | 0777);
170 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
171 	archive_entry_free(ae);
172 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
173 	/* Check that file was created with different perms. */
174 	assertEqualInt(0, stat("file_overwrite_0144", &st));
175 	failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
176 	assert((st.st_mode & 07777) != 0144);
177 	/* Overwrite, this should change the perms. */
178 	assert((ae = archive_entry_new()) != NULL);
179 	archive_entry_copy_pathname(ae, "file_overwrite_0144");
180 	archive_entry_set_mode(ae, S_IFREG | 0144);
181 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
182 	archive_entry_free(ae);
183 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
184 
185 	/* Write a regular dir. */
186 	assert((ae = archive_entry_new()) != NULL);
187 	archive_entry_copy_pathname(ae, "dir_0514");
188 	archive_entry_set_mode(ae, S_IFDIR | 0514);
189 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
190 	archive_entry_free(ae);
191 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
192 
193 	/* Overwrite an existing dir. */
194 	/* For dir, the first perms should get left. */
195 	assertMakeDir("dir_overwrite_0744", 0744);
196 	/* Check original perms. */
197 	assertEqualInt(0, stat("dir_overwrite_0744", &st));
198 	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
199 	assertEqualInt(st.st_mode & 0777, 0744);
200 	/* Overwrite shouldn't edit perms. */
201 	assert((ae = archive_entry_new()) != NULL);
202 	archive_entry_copy_pathname(ae, "dir_overwrite_0744");
203 	archive_entry_set_mode(ae, S_IFDIR | 0777);
204 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
205 	archive_entry_free(ae);
206 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
207 	/* Make sure they're unchanged. */
208 	assertEqualInt(0, stat("dir_overwrite_0744", &st));
209 	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
210 	assertEqualInt(st.st_mode & 0777, 0744);
211 
212 	/* For dir, the owner should get left when not overwriting. */
213 	assertMakeDir("dir_owner", 0744);
214 
215 	if (getuid() == 0) {
216 		original_uid = getuid() + 1;
217 		try_to_change_uid = getuid();
218 		assertChown("dir_owner", original_uid, getgid());
219 	} else {
220 		original_uid = getuid();
221 		try_to_change_uid = getuid() + 1;
222 	}
223 
224 	/* Check original owner. */
225 	assertEqualInt(0, stat("dir_owner", &st));
226 	failure("dir_owner: st.st_uid=%jd", (intmax_t)st.st_uid);
227 	assertEqualInt(st.st_uid, original_uid);
228 	/* Shouldn't try to edit the owner when no overwrite option is set. */
229 	assert((ae = archive_entry_new()) != NULL);
230 	archive_entry_copy_pathname(ae, "dir_owner");
231 	archive_entry_set_mode(ae, S_IFDIR | 0744);
232 	archive_entry_set_uid(ae, try_to_change_uid);
233 	archive_write_disk_set_options(a,
234 	    ARCHIVE_EXTRACT_OWNER | ARCHIVE_EXTRACT_NO_OVERWRITE);
235 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
236 	archive_entry_free(ae);
237 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
238 	/* Make sure they're unchanged. */
239 	assertEqualInt(0, stat("dir_owner", &st));
240 	failure("dir_owner: st.st_uid=%jd", (intmax_t)st.st_uid);
241 	assertEqualInt(st.st_uid, original_uid);
242 
243 	/* Write a regular file with SUID bit, but don't use _EXTRACT_PERM. */
244 	assert((ae = archive_entry_new()) != NULL);
245 	archive_entry_copy_pathname(ae, "file_no_suid");
246 	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0777);
247 	archive_write_disk_set_options(a, 0);
248 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
249 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
250 
251 	/* Write a regular file with ARCHIVE_EXTRACT_PERM. */
252 	assert(archive_entry_clear(ae) != NULL);
253 	archive_entry_copy_pathname(ae, "file_0777");
254 	archive_entry_set_mode(ae, S_IFREG | 0777);
255 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
256 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
257 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
258 
259 	/* Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit */
260 	assert(archive_entry_clear(ae) != NULL);
261 	archive_entry_copy_pathname(ae, "file_4742");
262 	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
263 	archive_entry_set_uid(ae, getuid());
264 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
265 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
266 	assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
267 
268 	/*
269 	 * Write a regular file with ARCHIVE_EXTRACT_PERM & SUID bit,
270 	 * but wrong uid.  POSIX says you shouldn't restore SUID bit
271 	 * unless the UID could be restored.
272 	 */
273 	assert(archive_entry_clear(ae) != NULL);
274 	archive_entry_copy_pathname(ae, "file_bad_suid");
275 	archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
276 	archive_entry_set_uid(ae, getuid() + 1);
277 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
278 	assertA(0 == archive_write_header(a, ae));
279 	/*
280 	 * Because we didn't ask for owner, the failure to
281 	 * restore SUID shouldn't return a failure.
282 	 * We check below to make sure SUID really wasn't set.
283 	 * See more detailed comments below.
284 	 */
285 	failure("Opportunistic SUID failure shouldn't return error.");
286 	assertEqualInt(0, archive_write_finish_entry(a));
287 
288         if (getuid() != 0) {
289 		assert(archive_entry_clear(ae) != NULL);
290 		archive_entry_copy_pathname(ae, "file_bad_suid2");
291 		archive_entry_set_mode(ae, S_IFREG | S_ISUID | 0742);
292 		archive_entry_set_uid(ae, getuid() + 1);
293 		archive_write_disk_set_options(a,
294 		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
295 		assertA(0 == archive_write_header(a, ae));
296 		/* Owner change should fail here. */
297 		failure("Non-opportunistic SUID failure should return error.");
298 		assertEqualInt(ARCHIVE_WARN, archive_write_finish_entry(a));
299 	}
300 
301 	/* Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit */
302 	assert(archive_entry_clear(ae) != NULL);
303 	archive_entry_copy_pathname(ae, "file_perm_sgid");
304 	archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
305 	archive_entry_set_gid(ae, defaultgid());
306 	archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
307 	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
308 	failure("Setting SGID bit should succeed here.");
309 	assertEqualIntA(a, 0, archive_write_finish_entry(a));
310 
311 	if (altgid() == -1) {
312 		/*
313 		 * Current user must belong to at least two groups or
314 		 * else we can't test setting the GID to another group.
315 		 */
316 		skipping("Current user can't test gid restore: must belong to more than one group.");
317 	} else {
318 		/*
319 		 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit
320 		 * but without ARCHIVE_EXTRACT_OWNER.
321 		 */
322 		/*
323 		 * This is a weird case: The user has asked for permissions to
324 		 * be restored but not asked for ownership to be restored.  As
325 		 * a result, the default file creation will create a file with
326 		 * the wrong group.  There are several possible behaviors for
327 		 * libarchive in this scenario:
328 		 *  = Set the SGID bit.  It is wrong and a security hole to
329 		 *    set SGID with the wrong group.  Even POSIX thinks so.
330 		 *  = Implicitly set the group.  I don't like this.
331 		 *  = drop the SGID bit and warn (the old libarchive behavior)
332 		 *  = drop the SGID bit and don't warn (the current libarchive
333 		 *    behavior).
334 		 * The current behavior sees SGID/SUID restore when you
335 		 * don't ask for owner restore as an "opportunistic"
336 		 * action.  That is, libarchive should do it if it can,
337 		 * but if it can't, it's not an error.
338 		 */
339 		assert(archive_entry_clear(ae) != NULL);
340 		archive_entry_copy_pathname(ae, "file_alt_sgid");
341 		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
342 		archive_entry_set_uid(ae, getuid());
343 		archive_entry_set_gid(ae, altgid());
344 		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
345 		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
346 		failure("Setting SGID bit should fail because of group mismatch but the failure should be silent because we didn't ask for the group to be set.");
347 		assertEqualIntA(a, 0, archive_write_finish_entry(a));
348 
349 		/*
350 		 * As above, but add _EXTRACT_OWNER to verify that it
351 		 * does succeed.
352 		 */
353 		assert(archive_entry_clear(ae) != NULL);
354 		archive_entry_copy_pathname(ae, "file_alt_sgid_owner");
355 		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
356 		archive_entry_set_uid(ae, getuid());
357 		archive_entry_set_gid(ae, altgid());
358 		archive_write_disk_set_options(a,
359 		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
360 		assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
361 		failure("Setting SGID bit should succeed here.");
362 		assertEqualIntA(a, ARCHIVE_OK, archive_write_finish_entry(a));
363 	}
364 
365 	/*
366 	 * Write a regular file with ARCHIVE_EXTRACT_PERM & SGID bit,
367 	 * but wrong GID.  POSIX says you shouldn't restore SGID bit
368 	 * unless the GID could be restored.
369 	 */
370 	if (invalidgid() == -1) {
371 		/* This test always fails for root. */
372 		printf("Running as root: Can't test SGID failures.\n");
373 	} else {
374 		assert(archive_entry_clear(ae) != NULL);
375 		archive_entry_copy_pathname(ae, "file_bad_sgid");
376 		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
377 		archive_entry_set_gid(ae, invalidgid());
378 		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_PERM);
379 		assertA(0 == archive_write_header(a, ae));
380 		failure("This SGID restore should fail without an error.");
381 		assertEqualIntA(a, 0, archive_write_finish_entry(a));
382 
383 		assert(archive_entry_clear(ae) != NULL);
384 		archive_entry_copy_pathname(ae, "file_bad_sgid2");
385 		archive_entry_set_mode(ae, S_IFREG | S_ISGID | 0742);
386 		archive_entry_set_gid(ae, invalidgid());
387 		archive_write_disk_set_options(a,
388 		    ARCHIVE_EXTRACT_PERM | ARCHIVE_EXTRACT_OWNER);
389 		assertA(0 == archive_write_header(a, ae));
390 		failure("This SGID restore should fail with an error.");
391 		assertEqualIntA(a, ARCHIVE_WARN, archive_write_finish_entry(a));
392 	}
393 
394 	/* Set ownership should fail if we're not root. */
395 	if (getuid() == 0) {
396 		printf("Running as root: Can't test setuid failures.\n");
397 	} else {
398 		assert(archive_entry_clear(ae) != NULL);
399 		archive_entry_copy_pathname(ae, "file_bad_owner");
400 		archive_entry_set_mode(ae, S_IFREG | 0744);
401 		archive_entry_set_uid(ae, getuid() + 1);
402 		archive_write_disk_set_options(a, ARCHIVE_EXTRACT_OWNER);
403 		assertA(0 == archive_write_header(a, ae));
404 		assertEqualIntA(a,ARCHIVE_WARN,archive_write_finish_entry(a));
405 	}
406 
407 	assertEqualInt(ARCHIVE_OK, archive_write_free(a));
408 	archive_entry_free(ae);
409 
410 	/* Test the entries on disk. */
411 	assertEqualInt(0, stat("file_0755", &st));
412 	failure("file_0755: st.st_mode=%o", st.st_mode);
413 	assertEqualInt(st.st_mode & 07777, 0755);
414 
415 	assertEqualInt(0, stat("file_overwrite_0144", &st));
416 	failure("file_overwrite_0144: st.st_mode=%o", st.st_mode);
417 	assertEqualInt(st.st_mode & 07777, 0144);
418 
419 	assertEqualInt(0, stat("dir_0514", &st));
420 	failure("dir_0514: st.st_mode=%o", st.st_mode);
421 	assertEqualInt(st.st_mode & 07777, 0514);
422 
423 	assertEqualInt(0, stat("dir_overwrite_0744", &st));
424 	failure("dir_overwrite_0744: st.st_mode=%o", st.st_mode);
425 	assertEqualInt(st.st_mode & 0777, 0744);
426 
427 	assertEqualInt(0, stat("file_no_suid", &st));
428 	failure("file_0755: st.st_mode=%o", st.st_mode);
429 	assertEqualInt(st.st_mode & 07777, 0755);
430 
431 	assertEqualInt(0, stat("file_0777", &st));
432 	failure("file_0777: st.st_mode=%o", st.st_mode);
433 	assertEqualInt(st.st_mode & 07777, 0777);
434 
435 	/* SUID bit should get set here. */
436 	assertEqualInt(0, stat("file_4742", &st));
437 	failure("file_4742: st.st_mode=%o", st.st_mode);
438 	assertEqualInt(st.st_mode & 07777, S_ISUID | 0742);
439 
440 	/* SUID bit should NOT have been set here. */
441 	assertEqualInt(0, stat("file_bad_suid", &st));
442 	failure("file_bad_suid: st.st_mode=%o", st.st_mode);
443 	assertEqualInt(st.st_mode & 07777, 0742);
444 
445 	/* Some things don't fail if you're root, so suppress this. */
446 	if (getuid() != 0) {
447 		/* SUID bit should NOT have been set here. */
448 		assertEqualInt(0, stat("file_bad_suid2", &st));
449 		failure("file_bad_suid2: st.st_mode=%o", st.st_mode);
450 		assertEqualInt(st.st_mode & 07777, 0742);
451 	}
452 
453 	/* SGID should be set here. */
454 	assertEqualInt(0, stat("file_perm_sgid", &st));
455 	failure("file_perm_sgid: st.st_mode=%o", st.st_mode);
456 	assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
457 
458 	if (altgid() != -1) {
459 		/* SGID should not be set here. */
460 		assertEqualInt(0, stat("file_alt_sgid", &st));
461 		failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
462 		assertEqualInt(st.st_mode & 07777, 0742);
463 
464 		/* SGID should be set here. */
465 		assertEqualInt(0, stat("file_alt_sgid_owner", &st));
466 		failure("file_alt_sgid: st.st_mode=%o", st.st_mode);
467 		assertEqualInt(st.st_mode & 07777, S_ISGID | 0742);
468 	}
469 
470 	if (invalidgid() != -1) {
471 		/* SGID should NOT be set here. */
472 		assertEqualInt(0, stat("file_bad_sgid", &st));
473 		failure("file_bad_sgid: st.st_mode=%o", st.st_mode);
474 		assertEqualInt(st.st_mode & 07777, 0742);
475 		/* SGID should NOT be set here. */
476 		assertEqualInt(0, stat("file_bad_sgid2", &st));
477 		failure("file_bad_sgid2: st.st_mode=%o", st.st_mode);
478 		assertEqualInt(st.st_mode & 07777, 0742);
479 	}
480 
481 	if (getuid() != 0) {
482 		assertEqualInt(0, stat("file_bad_owner", &st));
483 		failure("file_bad_owner: st.st_mode=%o", st.st_mode);
484 		assertEqualInt(st.st_mode & 07777, 0744);
485 		failure("file_bad_owner: st.st_uid=%jd getuid()=%jd",
486 		    (intmax_t)st.st_uid, (intmax_t)getuid());
487 		/* The entry had getuid()+1, but because we're
488 		 * not root, we should not have been able to set that. */
489 		assertEqualInt(st.st_uid, getuid());
490 	}
491 #endif
492 }
493