You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

671 lines
16 KiB

28 years ago
25 years ago
28 years ago
25 years ago
28 years ago
27 years ago
27 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
25 years ago
28 years ago
28 years ago
28 years ago
25 years ago
28 years ago
28 years ago
28 years ago
25 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
25 years ago
25 years ago
25 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
25 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
28 years ago
  1. /*
  2. +----------------------------------------------------------------------+
  3. | PHP Version 4 |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 1997-2002 The PHP Group |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 2.02 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available at through the world-wide-web at |
  10. | http://www.php.net/license/2_02.txt. |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. | Authors: Rasmus Lerdorf <rasmus@lerdorf.on.ca> |
  16. | Jim Winstead <jimw@php.net> |
  17. +----------------------------------------------------------------------+
  18. */
  19. /* $Id$ */
  20. /* {{{ includes
  21. */
  22. #include "php.h"
  23. #include "php_globals.h"
  24. #include "SAPI.h"
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <errno.h>
  28. #include <sys/types.h>
  29. #include <sys/stat.h>
  30. #include <fcntl.h>
  31. #ifdef PHP_WIN32
  32. #include <windows.h>
  33. #include <winsock.h>
  34. #define O_RDONLY _O_RDONLY
  35. #include "win32/param.h"
  36. #else
  37. #include <sys/param.h>
  38. #endif
  39. #include "safe_mode.h"
  40. #include "ext/standard/head.h"
  41. #include "ext/standard/php_standard.h"
  42. #include "zend_compile.h"
  43. #include "php_network.h"
  44. #if HAVE_PWD_H
  45. #ifdef PHP_WIN32
  46. #include "win32/pwd.h"
  47. #else
  48. #include <pwd.h>
  49. #endif
  50. #endif
  51. #include <sys/types.h>
  52. #if HAVE_SYS_SOCKET_H
  53. #include <sys/socket.h>
  54. #endif
  55. #ifndef S_ISREG
  56. #define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG)
  57. #endif
  58. #ifdef PHP_WIN32
  59. #include <winsock.h>
  60. #else
  61. #include <netinet/in.h>
  62. #include <netdb.h>
  63. #if HAVE_ARPA_INET_H
  64. #include <arpa/inet.h>
  65. #endif
  66. #endif
  67. #if defined(PHP_WIN32) || defined(__riscos__)
  68. #undef AF_UNIX
  69. #endif
  70. #if defined(AF_UNIX)
  71. #include <sys/un.h>
  72. #endif
  73. /* }}} */
  74. static FILE *php_fopen_url_wrapper(const char *, char *, int, int *, int *, char ** TSRMLS_DC);
  75. static HashTable fopen_url_wrappers_hash;
  76. /* {{{ php_register_url_wrapper
  77. */
  78. PHPAPI int php_register_url_wrapper(const char *protocol, php_fopen_url_wrapper_t wrapper TSRMLS_DC)
  79. {
  80. if(PG(allow_url_fopen)) {
  81. return zend_hash_add(&fopen_url_wrappers_hash, (char *) protocol, strlen(protocol), &wrapper, sizeof(wrapper), NULL);
  82. } else {
  83. return FAILURE;
  84. }
  85. }
  86. /* }}} */
  87. /* {{{ php_unregister_url_wrapper
  88. */
  89. PHPAPI int php_unregister_url_wrapper(char *protocol TSRMLS_DC)
  90. {
  91. if(PG(allow_url_fopen)) {
  92. return zend_hash_del(&fopen_url_wrappers_hash, protocol, strlen(protocol));
  93. } else {
  94. return SUCCESS;
  95. }
  96. }
  97. /* }}} */
  98. /* {{{ php_init_fopen_wrappers
  99. */
  100. int php_init_fopen_wrappers(TSRMLS_D)
  101. {
  102. if(PG(allow_url_fopen)) {
  103. return zend_hash_init(&fopen_url_wrappers_hash, 0, NULL, NULL, 1);
  104. }
  105. return SUCCESS;
  106. }
  107. /* }}} */
  108. /* {{{ php_shutdown_fopen_wrappers
  109. */
  110. int php_shutdown_fopen_wrappers(TSRMLS_D)
  111. {
  112. if(PG(allow_url_fopen)) {
  113. zend_hash_destroy(&fopen_url_wrappers_hash);
  114. }
  115. return SUCCESS;
  116. }
  117. /* }}} */
  118. /* {{{ php_check_specific_open_basedir
  119. When open_basedir is not NULL, check if the given filename is located in
  120. open_basedir. Returns -1 if error or not in the open_basedir, else 0
  121. When open_basedir is NULL, always return 0
  122. */
  123. PHPAPI int php_check_specific_open_basedir(char *basedir, char *path TSRMLS_DC)
  124. {
  125. char resolved_name[MAXPATHLEN];
  126. char resolved_basedir[MAXPATHLEN];
  127. char local_open_basedir[MAXPATHLEN];
  128. int local_open_basedir_pos;
  129. /* Special case basedir==".": Use script-directory */
  130. if ((strcmp(basedir, ".") == 0) &&
  131. SG(request_info).path_translated &&
  132. *SG(request_info).path_translated
  133. ) {
  134. strlcpy(local_open_basedir, SG(request_info).path_translated, sizeof(local_open_basedir));
  135. local_open_basedir_pos = strlen(local_open_basedir) - 1;
  136. /* Strip filename */
  137. while (!IS_SLASH(local_open_basedir[local_open_basedir_pos])
  138. && (local_open_basedir_pos >= 0)) {
  139. local_open_basedir[local_open_basedir_pos--] = 0;
  140. }
  141. } else {
  142. /* Else use the unmodified path */
  143. strlcpy(local_open_basedir, basedir, sizeof(local_open_basedir));
  144. }
  145. /* Resolve the real path into resolved_name */
  146. if ((expand_filepath(path, resolved_name TSRMLS_CC) != NULL) && (expand_filepath(local_open_basedir, resolved_basedir TSRMLS_CC) != NULL)) {
  147. /* Check the path */
  148. #ifdef PHP_WIN32
  149. if (strncasecmp(resolved_basedir, resolved_name, strlen(resolved_basedir)) == 0) {
  150. #else
  151. if (strncmp(resolved_basedir, resolved_name, strlen(resolved_basedir)) == 0) {
  152. #endif
  153. /* File is in the right directory */
  154. return 0;
  155. } else {
  156. return -1;
  157. }
  158. } else {
  159. /* Unable to resolve the real path, return -1 */
  160. return -1;
  161. }
  162. }
  163. /* }}} */
  164. /* {{{ php_check_open_basedir
  165. */
  166. PHPAPI int php_check_open_basedir(char *path TSRMLS_DC)
  167. {
  168. /* Only check when open_basedir is available */
  169. if (PG(open_basedir) && *PG(open_basedir)) {
  170. char *pathbuf;
  171. char *ptr;
  172. char *end;
  173. pathbuf = estrdup(PG(open_basedir));
  174. ptr = pathbuf;
  175. while (ptr && *ptr) {
  176. end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
  177. if (end != NULL) {
  178. *end = '\0';
  179. end++;
  180. }
  181. if (php_check_specific_open_basedir(ptr, path TSRMLS_CC) == 0) {
  182. efree(pathbuf);
  183. return 0;
  184. }
  185. ptr = end;
  186. }
  187. php_error(E_WARNING, "open_basedir restriction in effect. File is in wrong directory");
  188. efree(pathbuf);
  189. errno = EPERM; /* we deny permission to open it */
  190. return -1;
  191. }
  192. /* Nothing to check... */
  193. return 0;
  194. }
  195. /* }}} */
  196. /* {{{ php_check_safe_mode_include_dir
  197. */
  198. PHPAPI int php_check_safe_mode_include_dir(char *path TSRMLS_DC)
  199. {
  200. /* Only check when safe_mode on and safe_mode_include_dir is available */
  201. if (PG(safe_mode) && PG(safe_mode_include_dir) &&
  202. *PG(safe_mode_include_dir))
  203. {
  204. char *pathbuf;
  205. char *ptr;
  206. char *end;
  207. char resolved_name[MAXPATHLEN];
  208. /* Resolve the real path into resolved_name */
  209. if (expand_filepath(path, resolved_name TSRMLS_CC) == NULL)
  210. return -1;
  211. pathbuf = estrdup(PG(safe_mode_include_dir));
  212. ptr = pathbuf;
  213. while (ptr && *ptr) {
  214. end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
  215. if (end != NULL) {
  216. *end = '\0';
  217. end++;
  218. }
  219. /* Check the path */
  220. #ifdef PHP_WIN32
  221. if (strncasecmp(ptr, resolved_name, strlen(ptr)) == 0)
  222. #else
  223. if (strncmp(ptr, resolved_name, strlen(ptr)) == 0)
  224. #endif
  225. {
  226. /* File is in the right directory */
  227. efree(pathbuf);
  228. return 0;
  229. }
  230. ptr = end;
  231. }
  232. efree(pathbuf);
  233. return -1;
  234. }
  235. /* Nothing to check... */
  236. return 0;
  237. }
  238. /* }}} */
  239. /* {{{ php_fopen_and_set_opened_path
  240. */
  241. static FILE *php_fopen_and_set_opened_path(const char *path, char *mode, char **opened_path TSRMLS_DC)
  242. {
  243. FILE *fp;
  244. if (php_check_open_basedir((char *)path TSRMLS_CC)) {
  245. return NULL;
  246. }
  247. fp = VCWD_FOPEN(path, mode);
  248. if (fp && opened_path) {
  249. *opened_path = expand_filepath(path, NULL TSRMLS_CC);
  250. }
  251. return fp;
  252. }
  253. /* }}} */
  254. /* {{{ php_fopen_wrapper
  255. */
  256. PHPAPI FILE *php_fopen_wrapper(char *path, char *mode, int options, int *issock, int *socketd, char **opened_path TSRMLS_DC)
  257. {
  258. if (opened_path) {
  259. *opened_path = NULL;
  260. }
  261. if(!path || !*path) {
  262. return NULL;
  263. }
  264. if(PG(allow_url_fopen)) {
  265. if (!(options & IGNORE_URL)) {
  266. return php_fopen_url_wrapper(path, mode, options, issock, socketd, opened_path TSRMLS_CC);
  267. }
  268. }
  269. if (options & USE_PATH && PG(include_path) != NULL) {
  270. return php_fopen_with_path(path, mode, PG(include_path), opened_path TSRMLS_CC);
  271. } else {
  272. if (options & ENFORCE_SAFE_MODE && PG(safe_mode) && (!php_checkuid(path, mode, CHECKUID_CHECK_MODE_PARAM))) {
  273. return NULL;
  274. }
  275. return php_fopen_and_set_opened_path(path, mode, opened_path TSRMLS_CC);
  276. }
  277. }
  278. /* }}} */
  279. /* {{{ php_fopen_primary_script
  280. */
  281. PHPAPI int php_fopen_primary_script(zend_file_handle *file_handle TSRMLS_DC)
  282. {
  283. FILE *fp;
  284. struct stat st;
  285. char *path_info, *filename;
  286. int length;
  287. filename = SG(request_info).path_translated;
  288. path_info = SG(request_info).request_uri;
  289. #if HAVE_PWD_H
  290. if (PG(user_dir) && *PG(user_dir)
  291. && path_info && '/' == path_info[0] && '~' == path_info[1]) {
  292. char user[32];
  293. struct passwd *pw;
  294. char *s = strchr(path_info + 2, '/');
  295. filename = NULL; /* discard the original filename, it must not be used */
  296. if (s) { /* if there is no path name after the file, do not bother */
  297. /* to try open the directory */
  298. length = s - (path_info + 2);
  299. if (length > sizeof(user) - 1)
  300. length = sizeof(user) - 1;
  301. memcpy(user, path_info + 2, length);
  302. user[length] = '\0';
  303. pw = getpwnam(user);
  304. if (pw && pw->pw_dir) {
  305. filename = emalloc(strlen(PG(user_dir)) + strlen(path_info) + strlen(pw->pw_dir) + 4);
  306. if (filename) {
  307. sprintf(filename, "%s%c%s%c%s", pw->pw_dir, PHP_DIR_SEPARATOR,
  308. PG(user_dir), PHP_DIR_SEPARATOR, s+1); /* Safe */
  309. STR_FREE(SG(request_info).path_translated);
  310. SG(request_info).path_translated = filename;
  311. }
  312. }
  313. }
  314. } else
  315. #endif
  316. if (PG(doc_root) && path_info) {
  317. length = strlen(PG(doc_root));
  318. if (IS_ABSOLUTE_PATH(PG(doc_root), length)) {
  319. filename = emalloc(length + strlen(path_info) + 2);
  320. if (filename) {
  321. memcpy(filename, PG(doc_root), length);
  322. if (!IS_SLASH(filename[length - 1])) { /* length is never 0 */
  323. filename[length++] = PHP_DIR_SEPARATOR;
  324. }
  325. if (IS_SLASH(path_info[0])) {
  326. length--;
  327. }
  328. strcpy(filename + length, path_info);
  329. STR_FREE(SG(request_info).path_translated);
  330. SG(request_info).path_translated = filename;
  331. }
  332. }
  333. } /* if doc_root && path_info */
  334. if (!filename) {
  335. /* we have to free SG(request_info).path_translated here because
  336. php_destroy_request_info assumes that it will get
  337. freed when the include_names hash is emptied, but
  338. we're not adding it in this case */
  339. STR_FREE(SG(request_info).path_translated);
  340. SG(request_info).path_translated = NULL;
  341. return FAILURE;
  342. }
  343. fp = VCWD_FOPEN(filename, "rb");
  344. /* refuse to open anything that is not a regular file */
  345. if (fp && (0 > fstat(fileno(fp), &st) || !S_ISREG(st.st_mode))) {
  346. fclose(fp);
  347. fp = NULL;
  348. }
  349. if (!fp) {
  350. php_error(E_ERROR, "Unable to open %s", filename);
  351. STR_FREE(SG(request_info).path_translated); /* for same reason as above */
  352. return FAILURE;
  353. }
  354. file_handle->opened_path = expand_filepath(filename, NULL TSRMLS_CC);
  355. if (!(SG(options) & SAPI_OPTION_NO_CHDIR)) {
  356. VCWD_CHDIR_FILE(filename);
  357. }
  358. SG(request_info).path_translated = filename;
  359. file_handle->filename = SG(request_info).path_translated;
  360. file_handle->free_filename = 0;
  361. file_handle->handle.fp = fp;
  362. file_handle->type = ZEND_HANDLE_FP;
  363. return SUCCESS;
  364. }
  365. /* }}} */
  366. /* {{{ php_fopen_with_path
  367. * Tries to open a file with a PATH-style list of directories.
  368. * If the filename starts with "." or "/", the path is ignored.
  369. */
  370. PHPAPI FILE *php_fopen_with_path(char *filename, char *mode, char *path, char **opened_path TSRMLS_DC)
  371. {
  372. char *pathbuf, *ptr, *end;
  373. char *exec_fname;
  374. char trypath[MAXPATHLEN];
  375. struct stat sb;
  376. FILE *fp;
  377. int path_length;
  378. int filename_length;
  379. int exec_fname_length;
  380. if (opened_path) {
  381. *opened_path = NULL;
  382. }
  383. if(!filename) {
  384. return NULL;
  385. }
  386. filename_length = strlen(filename);
  387. /* Relative path open */
  388. if (*filename == '.') {
  389. if (PG(safe_mode) && (!php_checkuid(filename, mode, CHECKUID_CHECK_MODE_PARAM))) {
  390. return NULL;
  391. }
  392. return php_fopen_and_set_opened_path(filename, mode, opened_path TSRMLS_CC);
  393. }
  394. /*
  395. * files in safe_mode_include_dir (or subdir) are excluded from
  396. * safe mode GID/UID checks
  397. */
  398. /* Absolute path open */
  399. if (IS_ABSOLUTE_PATH(filename, filename_length)) {
  400. if ((php_check_safe_mode_include_dir(filename TSRMLS_CC)) == 0)
  401. /* filename is in safe_mode_include_dir (or subdir) */
  402. return php_fopen_and_set_opened_path(filename, mode, opened_path TSRMLS_CC);
  403. if (PG(safe_mode) && (!php_checkuid(filename, mode, CHECKUID_CHECK_MODE_PARAM)))
  404. return NULL;
  405. return php_fopen_and_set_opened_path(filename, mode, opened_path TSRMLS_CC);
  406. }
  407. if (!path || (path && !*path)) {
  408. if (PG(safe_mode) && (!php_checkuid(filename, mode, CHECKUID_CHECK_MODE_PARAM))) {
  409. return NULL;
  410. }
  411. return php_fopen_and_set_opened_path(filename, mode, opened_path TSRMLS_CC);
  412. }
  413. /* check in provided path */
  414. /* append the calling scripts' current working directory
  415. * as a fall back case
  416. */
  417. if (zend_is_executing(TSRMLS_C)) {
  418. exec_fname = zend_get_executed_filename(TSRMLS_C);
  419. exec_fname_length = strlen(exec_fname);
  420. path_length = strlen(path);
  421. while ((--exec_fname_length >= 0) && !IS_SLASH(exec_fname[exec_fname_length]));
  422. if ((exec_fname && exec_fname[0] == '[')
  423. || exec_fname_length<=0) {
  424. /* [no active file] or no path */
  425. pathbuf = estrdup(path);
  426. } else {
  427. pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1);
  428. memcpy(pathbuf, path, path_length);
  429. pathbuf[path_length] = DEFAULT_DIR_SEPARATOR;
  430. memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length);
  431. pathbuf[path_length + exec_fname_length +1] = '\0';
  432. }
  433. } else {
  434. pathbuf = estrdup(path);
  435. }
  436. ptr = pathbuf;
  437. while (ptr && *ptr) {
  438. end = strchr(ptr, DEFAULT_DIR_SEPARATOR);
  439. if (end != NULL) {
  440. *end = '\0';
  441. end++;
  442. }
  443. snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename);
  444. if (PG(safe_mode)) {
  445. if (VCWD_STAT(trypath, &sb) == 0) {
  446. /* file exists ... check permission */
  447. if ((php_check_safe_mode_include_dir(trypath TSRMLS_CC) == 0) ||
  448. php_checkuid(trypath, mode, CHECKUID_CHECK_MODE_PARAM))
  449. /* UID ok, or trypath is in safe_mode_include_dir */
  450. fp = php_fopen_and_set_opened_path(trypath, mode, opened_path TSRMLS_CC);
  451. else
  452. fp = NULL;
  453. efree(pathbuf);
  454. return fp;
  455. }
  456. }
  457. fp = php_fopen_and_set_opened_path(trypath, mode, opened_path TSRMLS_CC);
  458. if (fp) {
  459. efree(pathbuf);
  460. return fp;
  461. }
  462. ptr = end;
  463. } /* end provided path */
  464. efree(pathbuf);
  465. return NULL;
  466. }
  467. /* }}} */
  468. /* {{{ php_fopen_url_wrapper
  469. */
  470. static FILE *php_fopen_url_wrapper(const char *path, char *mode, int options, int *issock, int *socketd, char **opened_path TSRMLS_DC)
  471. {
  472. FILE *fp = NULL;
  473. const char *p;
  474. const char *protocol=NULL;
  475. int n=0;
  476. for (p=path; isalnum((int)*p); p++) {
  477. n++;
  478. }
  479. if ((*p==':')&&(n>1)) {
  480. protocol=path;
  481. }
  482. if (protocol) {
  483. php_fopen_url_wrapper_t *wrapper=NULL;
  484. if (FAILURE==zend_hash_find(&fopen_url_wrappers_hash, (char *) protocol, n, (void **)&wrapper)) {
  485. wrapper=NULL;
  486. protocol=NULL;
  487. }
  488. if (wrapper) {
  489. return (*wrapper)(path, mode, options, issock, socketd, opened_path TSRMLS_CC);
  490. }
  491. }
  492. if (!protocol || !strncasecmp(protocol, "file", n)){
  493. *issock = 0;
  494. if(protocol) {
  495. if(path[n+1]=='/') {
  496. if(path[n+2]=='/') {
  497. php_error(E_WARNING, "remote host file access not supported, %s", path);
  498. return NULL;
  499. }
  500. }
  501. path+= n+1;
  502. }
  503. if (options & USE_PATH) {
  504. fp = php_fopen_with_path((char *) path, mode, PG(include_path), opened_path TSRMLS_CC);
  505. } else {
  506. if (options & ENFORCE_SAFE_MODE && PG(safe_mode) && (!php_checkuid(path, mode, CHECKUID_CHECK_MODE_PARAM))) {
  507. fp = NULL;
  508. } else {
  509. fp = php_fopen_and_set_opened_path(path, mode, opened_path TSRMLS_CC);
  510. }
  511. }
  512. return (fp);
  513. }
  514. php_error(E_WARNING, "Invalid URL specified, %s", path);
  515. return NULL;
  516. }
  517. /* }}} */
  518. /* {{{ php_strip_url_passwd
  519. */
  520. PHPAPI char *php_strip_url_passwd(char *url)
  521. {
  522. register char *p = url, *url_start;
  523. while (*p) {
  524. if (*p==':' && *(p+1)=='/' && *(p+2)=='/') {
  525. /* found protocol */
  526. url_start = p = p+3;
  527. while (*p) {
  528. if (*p=='@') {
  529. int i;
  530. for (i=0; i<3 && url_start<p; i++, url_start++) {
  531. *url_start = '.';
  532. }
  533. for (; *p; p++) {
  534. *url_start++ = *p;
  535. }
  536. *url_start=0;
  537. break;
  538. }
  539. p++;
  540. }
  541. return url;
  542. }
  543. p++;
  544. }
  545. return url;
  546. }
  547. /* }}} */
  548. /* {{{ expand_filepath
  549. */
  550. PHPAPI char *expand_filepath(const char *filepath, char *real_path TSRMLS_DC)
  551. {
  552. cwd_state new_state;
  553. char cwd[MAXPATHLEN];
  554. char *result;
  555. result = VCWD_GETCWD(cwd, MAXPATHLEN);
  556. if (!result) {
  557. cwd[0] = '\0';
  558. }
  559. new_state.cwd = strdup(cwd);
  560. new_state.cwd_length = strlen(cwd);
  561. if(virtual_file_ex(&new_state, filepath, NULL)) {
  562. free(new_state.cwd);
  563. return NULL;
  564. }
  565. if(real_path) {
  566. int copy_len = new_state.cwd_length>MAXPATHLEN-1?MAXPATHLEN-1:new_state.cwd_length;
  567. memcpy(real_path, new_state.cwd, copy_len);
  568. real_path[copy_len]='\0';
  569. } else {
  570. real_path = estrndup(new_state.cwd, new_state.cwd_length);
  571. }
  572. free(new_state.cwd);
  573. return real_path;
  574. }
  575. /* }}} */
  576. /*
  577. * Local variables:
  578. * tab-width: 4
  579. * c-basic-offset: 4
  580. * End:
  581. * vim600: sw=4 ts=4 fdm=marker
  582. * vim<600: sw=4 ts=4
  583. */