diff options
| author | Stefan Esser | 2010-02-21 11:44:54 +0100 |
|---|---|---|
| committer | Stefan Esser | 2010-02-21 11:44:54 +0100 |
| commit | 36dbfacbe64697d959f524e537b15b73c090d898 (patch) | |
| tree | f1c7ce1409b0e7765fc72d550546967fcf0f9717 /ex_imp.c | |
Inital commit
Diffstat (limited to 'ex_imp.c')
| -rw-r--r-- | ex_imp.c | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/ex_imp.c b/ex_imp.c new file mode 100644 index 0000000..f602860 --- /dev/null +++ b/ex_imp.c | |||
| @@ -0,0 +1,450 @@ | |||
| 1 | /* | ||
| 2 | +----------------------------------------------------------------------+ | ||
| 3 | | Suhosin Version 1 | | ||
| 4 | +----------------------------------------------------------------------+ | ||
| 5 | | Copyright (c) 2006-2007 The Hardened-PHP Project | | ||
| 6 | | Copyright (c) 2007 SektionEins GmbH | | ||
| 7 | +----------------------------------------------------------------------+ | ||
| 8 | | This source file is subject to version 3.01 of the PHP license, | | ||
| 9 | | that is bundled with this package in the file LICENSE, and is | | ||
| 10 | | available through the world-wide-web at the following url: | | ||
| 11 | | http://www.php.net/license/3_01.txt | | ||
| 12 | | If you did not receive a copy of the PHP license and are unable to | | ||
| 13 | | obtain it through the world-wide-web, please send a note to | | ||
| 14 | | license@php.net so we can mail you a copy immediately. | | ||
| 15 | +----------------------------------------------------------------------+ | ||
| 16 | | Author: Stefan Esser <sesser@sektioneins.de> | | ||
| 17 | +----------------------------------------------------------------------+ | ||
| 18 | */ | ||
| 19 | /* | ||
| 20 | $Id: ex_imp.c,v 1.2 2008-01-04 11:23:47 sesser Exp $ | ||
| 21 | */ | ||
| 22 | |||
| 23 | #ifdef HAVE_CONFIG_H | ||
| 24 | #include "config.h" | ||
| 25 | #endif | ||
| 26 | |||
| 27 | #include "php.h" | ||
| 28 | #include "php_ini.h" | ||
| 29 | #include "php_suhosin.h" | ||
| 30 | #include "ext/standard/php_smart_str.h" | ||
| 31 | |||
| 32 | |||
| 33 | #define EXTR_OVERWRITE 0 | ||
| 34 | #define EXTR_SKIP 1 | ||
| 35 | #define EXTR_PREFIX_SAME 2 | ||
| 36 | #define EXTR_PREFIX_ALL 3 | ||
| 37 | #define EXTR_PREFIX_INVALID 4 | ||
| 38 | #define EXTR_PREFIX_IF_EXISTS 5 | ||
| 39 | #define EXTR_IF_EXISTS 6 | ||
| 40 | |||
| 41 | #define EXTR_REFS 0x100 | ||
| 42 | |||
| 43 | |||
| 44 | static int php_valid_var_name(char *var_name) | ||
| 45 | { | ||
| 46 | int len, i; | ||
| 47 | |||
| 48 | if (!var_name) | ||
| 49 | return 0; | ||
| 50 | |||
| 51 | len = strlen(var_name); | ||
| 52 | |||
| 53 | if (!isalpha((int)((unsigned char *)var_name)[0]) && var_name[0] != '_') | ||
| 54 | return 0; | ||
| 55 | |||
| 56 | if (len > 1) { | ||
| 57 | for (i=1; i<len; i++) { | ||
| 58 | if (!isalnum((int)((unsigned char *)var_name)[i]) && var_name[i] != '_') { | ||
| 59 | return 0; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | if (var_name[0] == 'H') { | ||
| 65 | if ((strcmp(var_name, "HTTP_GET_VARS")==0)|| | ||
| 66 | (strcmp(var_name, "HTTP_POST_VARS")==0)|| | ||
| 67 | (strcmp(var_name, "HTTP_POST_FILES")==0)|| | ||
| 68 | (strcmp(var_name, "HTTP_ENV_VARS")==0)|| | ||
| 69 | (strcmp(var_name, "HTTP_SERVER_VARS")==0)|| | ||
| 70 | (strcmp(var_name, "HTTP_SESSION_VARS")==0)|| | ||
| 71 | (strcmp(var_name, "HTTP_COOKIE_VARS")==0)|| | ||
| 72 | (strcmp(var_name, "HTTP_RAW_POST_DATA")==0)) { | ||
| 73 | return 0; | ||
| 74 | } | ||
| 75 | } else if (var_name[0] == '_') { | ||
| 76 | if ((strcmp(var_name, "_COOKIE")==0)|| | ||
| 77 | (strcmp(var_name, "_ENV")==0)|| | ||
| 78 | (strcmp(var_name, "_FILES")==0)|| | ||
| 79 | (strcmp(var_name, "_GET")==0)|| | ||
| 80 | (strcmp(var_name, "_POST")==0)|| | ||
| 81 | (strcmp(var_name, "_REQUEST")==0)|| | ||
| 82 | (strcmp(var_name, "_SESSION")==0)|| | ||
| 83 | (strcmp(var_name, "_SERVER")==0)) { | ||
| 84 | return 0; | ||
| 85 | } | ||
| 86 | } else if (strcmp(var_name, "GLOBALS")==0) { | ||
| 87 | return 0; | ||
| 88 | } | ||
| 89 | |||
| 90 | return 1; | ||
| 91 | } | ||
| 92 | |||
| 93 | |||
| 94 | /* {{{ proto int extract(array var_array [, int extract_type [, string prefix]]) | ||
| 95 | Imports variables into symbol table from an array */ | ||
| 96 | PHP_FUNCTION(suhosin_extract) | ||
| 97 | { | ||
| 98 | zval **var_array, **z_extract_type, **prefix; | ||
| 99 | zval **entry, *data; | ||
| 100 | char *var_name; | ||
| 101 | smart_str final_name = {0}; | ||
| 102 | ulong num_key; | ||
| 103 | uint var_name_len; | ||
| 104 | int var_exists, extract_type, key_type, count = 0; | ||
| 105 | int extract_refs = 0; | ||
| 106 | HashPosition pos; | ||
| 107 | |||
| 108 | switch (ZEND_NUM_ARGS()) { | ||
| 109 | case 1: | ||
| 110 | if (zend_get_parameters_ex(1, &var_array) == FAILURE) { | ||
| 111 | WRONG_PARAM_COUNT; | ||
| 112 | } | ||
| 113 | extract_type = EXTR_OVERWRITE; | ||
| 114 | break; | ||
| 115 | |||
| 116 | case 2: | ||
| 117 | if (zend_get_parameters_ex(2, &var_array, &z_extract_type) == FAILURE) { | ||
| 118 | WRONG_PARAM_COUNT; | ||
| 119 | } | ||
| 120 | convert_to_long_ex(z_extract_type); | ||
| 121 | extract_type = Z_LVAL_PP(z_extract_type); | ||
| 122 | extract_refs = (extract_type & EXTR_REFS); | ||
| 123 | extract_type &= 0xff; | ||
| 124 | if (extract_type > EXTR_SKIP && extract_type <= EXTR_PREFIX_IF_EXISTS) { | ||
| 125 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Prefix expected to be specified"); | ||
| 126 | return; | ||
| 127 | } | ||
| 128 | break; | ||
| 129 | |||
| 130 | case 3: | ||
| 131 | if (zend_get_parameters_ex(3, &var_array, &z_extract_type, &prefix) == FAILURE) { | ||
| 132 | WRONG_PARAM_COUNT; | ||
| 133 | } | ||
| 134 | convert_to_long_ex(z_extract_type); | ||
| 135 | extract_type = Z_LVAL_PP(z_extract_type); | ||
| 136 | extract_refs = (extract_type & EXTR_REFS); | ||
| 137 | extract_type &= 0xff; | ||
| 138 | convert_to_string_ex(prefix); | ||
| 139 | break; | ||
| 140 | |||
| 141 | default: | ||
| 142 | WRONG_PARAM_COUNT; | ||
| 143 | break; | ||
| 144 | } | ||
| 145 | |||
| 146 | #if PHP_VERSION_ID >= 50300 | ||
| 147 | if (!EG(active_symbol_table)) { | ||
| 148 | zend_rebuild_symbol_table(TSRMLS_C); | ||
| 149 | } | ||
| 150 | #endif | ||
| 151 | |||
| 152 | if (extract_type < EXTR_OVERWRITE || extract_type > EXTR_IF_EXISTS) { | ||
| 153 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown extract type"); | ||
| 154 | return; | ||
| 155 | } | ||
| 156 | |||
| 157 | if (Z_TYPE_PP(var_array) != IS_ARRAY) { | ||
| 158 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "First argument should be an array"); | ||
| 159 | return; | ||
| 160 | } | ||
| 161 | |||
| 162 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(var_array), &pos); | ||
| 163 | while (zend_hash_get_current_data_ex(Z_ARRVAL_PP(var_array), (void **)&entry, &pos) == SUCCESS) { | ||
| 164 | key_type = zend_hash_get_current_key_ex(Z_ARRVAL_PP(var_array), &var_name, &var_name_len, &num_key, 0, &pos); | ||
| 165 | var_exists = 0; | ||
| 166 | |||
| 167 | if (key_type == HASH_KEY_IS_STRING) { | ||
| 168 | var_name_len--; | ||
| 169 | var_exists = zend_hash_exists(EG(active_symbol_table), var_name, var_name_len + 1); | ||
| 170 | } else if (extract_type == EXTR_PREFIX_ALL || extract_type == EXTR_PREFIX_INVALID) { | ||
| 171 | smart_str_appendl(&final_name, Z_STRVAL_PP(prefix), Z_STRLEN_PP(prefix)); | ||
| 172 | smart_str_appendc(&final_name, '_'); | ||
| 173 | smart_str_append_long(&final_name, num_key); | ||
| 174 | } else { | ||
| 175 | zend_hash_move_forward_ex(Z_ARRVAL_PP(var_array), &pos); | ||
| 176 | continue; | ||
| 177 | } | ||
| 178 | |||
| 179 | switch (extract_type) { | ||
| 180 | case EXTR_IF_EXISTS: | ||
| 181 | if (!var_exists) break; | ||
| 182 | /* break omitted intentionally */ | ||
| 183 | |||
| 184 | case EXTR_OVERWRITE: | ||
| 185 | /* GLOBALS protection */ | ||
| 186 | if (var_exists && !strcmp(var_name, "GLOBALS")) { | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 190 | break; | ||
| 191 | |||
| 192 | case EXTR_PREFIX_IF_EXISTS: | ||
| 193 | if (var_exists) { | ||
| 194 | smart_str_appendl(&final_name, Z_STRVAL_PP(prefix), Z_STRLEN_PP(prefix)); | ||
| 195 | smart_str_appendc(&final_name, '_'); | ||
| 196 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 197 | } | ||
| 198 | break; | ||
| 199 | |||
| 200 | case EXTR_PREFIX_SAME: | ||
| 201 | if (!var_exists) | ||
| 202 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 203 | /* break omitted intentionally */ | ||
| 204 | |||
| 205 | case EXTR_PREFIX_ALL: | ||
| 206 | if (final_name.len == 0 && var_name_len != 0) { | ||
| 207 | smart_str_appendl(&final_name, Z_STRVAL_PP(prefix), Z_STRLEN_PP(prefix)); | ||
| 208 | smart_str_appendc(&final_name, '_'); | ||
| 209 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 210 | } | ||
| 211 | break; | ||
| 212 | |||
| 213 | case EXTR_PREFIX_INVALID: | ||
| 214 | if (final_name.len == 0) { | ||
| 215 | if (!php_valid_var_name(var_name)) { | ||
| 216 | smart_str_appendl(&final_name, Z_STRVAL_PP(prefix), Z_STRLEN_PP(prefix)); | ||
| 217 | smart_str_appendc(&final_name, '_'); | ||
| 218 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 219 | } else | ||
| 220 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 221 | } | ||
| 222 | break; | ||
| 223 | |||
| 224 | default: | ||
| 225 | if (!var_exists) | ||
| 226 | smart_str_appendl(&final_name, var_name, var_name_len); | ||
| 227 | break; | ||
| 228 | } | ||
| 229 | |||
| 230 | if (final_name.len) { | ||
| 231 | smart_str_0(&final_name); | ||
| 232 | if (php_valid_var_name(final_name.c)) { | ||
| 233 | if (extract_refs) { | ||
| 234 | zval **orig_var; | ||
| 235 | |||
| 236 | if (zend_hash_find(EG(active_symbol_table), final_name.c, final_name.len+1, (void **) &orig_var) == SUCCESS) { | ||
| 237 | SEPARATE_ZVAL_TO_MAKE_IS_REF(entry); | ||
| 238 | zval_add_ref(entry); | ||
| 239 | |||
| 240 | zval_ptr_dtor(orig_var); | ||
| 241 | |||
| 242 | *orig_var = *entry; | ||
| 243 | } else { | ||
| 244 | if (Z_REFCOUNT_PP(var_array) > 1) { | ||
| 245 | SEPARATE_ZVAL_TO_MAKE_IS_REF(entry); | ||
| 246 | } else { | ||
| 247 | Z_SET_ISREF_PP(entry); | ||
| 248 | } | ||
| 249 | zval_add_ref(entry); | ||
| 250 | zend_hash_update(EG(active_symbol_table), final_name.c, final_name.len+1, (void **) entry, sizeof(zval *), NULL); | ||
| 251 | } | ||
| 252 | } else { | ||
| 253 | MAKE_STD_ZVAL(data); | ||
| 254 | *data = **entry; | ||
| 255 | zval_copy_ctor(data); | ||
| 256 | |||
| 257 | ZEND_SET_SYMBOL_WITH_LENGTH(EG(active_symbol_table), final_name.c, final_name.len+1, data, 1, 0); | ||
| 258 | } | ||
| 259 | |||
| 260 | count++; | ||
| 261 | } | ||
| 262 | final_name.len = 0; | ||
| 263 | } | ||
| 264 | |||
| 265 | zend_hash_move_forward_ex(Z_ARRVAL_PP(var_array), &pos); | ||
| 266 | } | ||
| 267 | |||
| 268 | smart_str_free(&final_name); | ||
| 269 | |||
| 270 | RETURN_LONG(count); | ||
| 271 | } | ||
| 272 | /* }}} */ | ||
| 273 | |||
| 274 | |||
| 275 | static int copy_request_variable(void *pDest, int num_args, va_list args, zend_hash_key *hash_key) | ||
| 276 | { | ||
| 277 | char *prefix, *new_key; | ||
| 278 | uint prefix_len, new_key_len; | ||
| 279 | zval **var = (zval **) pDest; | ||
| 280 | TSRMLS_FETCH(); | ||
| 281 | |||
| 282 | if (num_args != 2) { | ||
| 283 | return 0; | ||
| 284 | } | ||
| 285 | |||
| 286 | prefix = va_arg(args, char *); | ||
| 287 | prefix_len = va_arg(args, uint); | ||
| 288 | |||
| 289 | if (!prefix_len) { | ||
| 290 | if (!hash_key->nKeyLength) { | ||
| 291 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Numeric key detected - possible security hazard."); | ||
| 292 | return 0; | ||
| 293 | } else if (!strcmp(hash_key->arKey, "GLOBALS")) { | ||
| 294 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Attempted GLOBALS variable overwrite."); | ||
| 295 | return 0; | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | if (hash_key->nKeyLength) { | ||
| 300 | new_key_len = prefix_len + hash_key->nKeyLength; | ||
| 301 | new_key = (char *) emalloc(new_key_len); | ||
| 302 | |||
| 303 | memcpy(new_key, prefix, prefix_len); | ||
| 304 | memcpy(new_key+prefix_len, hash_key->arKey, hash_key->nKeyLength); | ||
| 305 | } else { | ||
| 306 | new_key_len = spprintf(&new_key, 0, "%s%ld", prefix, hash_key->h); | ||
| 307 | } | ||
| 308 | |||
| 309 | if (new_key[0] == 'H') { | ||
| 310 | if ((strcmp(new_key, "HTTP_GET_VARS")==0)|| | ||
| 311 | (strcmp(new_key, "HTTP_POST_VARS")==0)|| | ||
| 312 | (strcmp(new_key, "HTTP_POST_FILES")==0)|| | ||
| 313 | (strcmp(new_key, "HTTP_ENV_VARS")==0)|| | ||
| 314 | (strcmp(new_key, "HTTP_SERVER_VARS")==0)|| | ||
| 315 | (strcmp(new_key, "HTTP_SESSION_VARS")==0)|| | ||
| 316 | (strcmp(new_key, "HTTP_COOKIE_VARS")==0)|| | ||
| 317 | (strcmp(new_key, "HTTP_RAW_POST_DATA")==0)) { | ||
| 318 | efree(new_key); | ||
| 319 | return 0; | ||
| 320 | } | ||
| 321 | } else if (new_key[0] == '_') { | ||
| 322 | if ((strcmp(new_key, "_COOKIE")==0)|| | ||
| 323 | (strcmp(new_key, "_ENV")==0)|| | ||
| 324 | (strcmp(new_key, "_FILES")==0)|| | ||
| 325 | (strcmp(new_key, "_GET")==0)|| | ||
| 326 | (strcmp(new_key, "_POST")==0)|| | ||
| 327 | (strcmp(new_key, "_REQUEST")==0)|| | ||
| 328 | (strcmp(new_key, "_SESSION")==0)|| | ||
| 329 | (strcmp(new_key, "_SERVER")==0)) { | ||
| 330 | efree(new_key); | ||
| 331 | return 0; | ||
| 332 | } | ||
| 333 | } else if (strcmp(new_key, "GLOBALS")==0) { | ||
| 334 | efree(new_key); | ||
| 335 | return 0; | ||
| 336 | } | ||
| 337 | |||
| 338 | #if PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 0) | ||
| 339 | zend_delete_global_variable(new_key, new_key_len-1 TSRMLS_CC); | ||
| 340 | #else | ||
| 341 | zend_hash_del(&EG(symbol_table), new_key, new_key_len-1); | ||
| 342 | #endif | ||
| 343 | ZEND_SET_SYMBOL_WITH_LENGTH(&EG(symbol_table), new_key, new_key_len, *var, Z_REFCOUNT_PP(var)+1, 0); | ||
| 344 | |||
| 345 | efree(new_key); | ||
| 346 | return 0; | ||
| 347 | } | ||
| 348 | |||
| 349 | /* {{{ proto bool import_request_variables(string types [, string prefix]) | ||
| 350 | Import GET/POST/Cookie variables into the global scope */ | ||
| 351 | PHP_FUNCTION(suhosin_import_request_variables) | ||
| 352 | { | ||
| 353 | zval **z_types, **z_prefix; | ||
| 354 | char *types, *prefix; | ||
| 355 | uint prefix_len; | ||
| 356 | char *p; | ||
| 357 | |||
| 358 | switch (ZEND_NUM_ARGS()) { | ||
| 359 | |||
| 360 | case 1: | ||
| 361 | if (zend_get_parameters_ex(1, &z_types) == FAILURE) { | ||
| 362 | RETURN_FALSE; | ||
| 363 | } | ||
| 364 | prefix = ""; | ||
| 365 | prefix_len = 0; | ||
| 366 | break; | ||
| 367 | |||
| 368 | case 2: | ||
| 369 | if (zend_get_parameters_ex(2, &z_types, &z_prefix) == FAILURE) { | ||
| 370 | RETURN_FALSE; | ||
| 371 | } | ||
| 372 | convert_to_string_ex(z_prefix); | ||
| 373 | prefix = Z_STRVAL_PP(z_prefix); | ||
| 374 | prefix_len = Z_STRLEN_PP(z_prefix); | ||
| 375 | break; | ||
| 376 | |||
| 377 | default: | ||
| 378 | ZEND_WRONG_PARAM_COUNT(); | ||
| 379 | } | ||
| 380 | |||
| 381 | if (prefix_len == 0) { | ||
| 382 | php_error_docref(NULL TSRMLS_CC, E_NOTICE, "No prefix specified - possible security hazard"); | ||
| 383 | } | ||
| 384 | |||
| 385 | convert_to_string_ex(z_types); | ||
| 386 | types = Z_STRVAL_PP(z_types); | ||
| 387 | |||
| 388 | for (p = types; p && *p; p++) { | ||
| 389 | switch (*p) { | ||
| 390 | |||
| 391 | case 'g': | ||
| 392 | case 'G': | ||
| 393 | zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_GET]), (apply_func_args_t) copy_request_variable, 2, prefix, prefix_len); | ||
| 394 | break; | ||
| 395 | |||
| 396 | case 'p': | ||
| 397 | case 'P': | ||
| 398 | zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_POST]), (apply_func_args_t) copy_request_variable, 2, prefix, prefix_len); | ||
| 399 | zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_FILES]), (apply_func_args_t) copy_request_variable, 2, prefix, prefix_len); | ||
| 400 | break; | ||
| 401 | |||
| 402 | case 'c': | ||
| 403 | case 'C': | ||
| 404 | zend_hash_apply_with_arguments(Z_ARRVAL_P(PG(http_globals)[TRACK_VARS_COOKIE]), (apply_func_args_t) copy_request_variable, 2, prefix, prefix_len); | ||
| 405 | break; | ||
| 406 | } | ||
| 407 | } | ||
| 408 | } | ||
| 409 | /* }}} */ | ||
| 410 | |||
| 411 | |||
| 412 | /* {{{ suhosin_ex_imp_functions[] | ||
| 413 | */ | ||
| 414 | function_entry suhosin_ex_imp_functions[] = { | ||
| 415 | PHP_NAMED_FE(extract, PHP_FN(suhosin_extract), NULL) | ||
| 416 | PHP_NAMED_FE(import_request_variables, PHP_FN(suhosin_import_request_variables), NULL) | ||
| 417 | {NULL, NULL, NULL} | ||
| 418 | }; | ||
| 419 | /* }}} */ | ||
| 420 | |||
| 421 | |||
| 422 | void suhosin_hook_ex_imp() | ||
| 423 | { | ||
| 424 | TSRMLS_FETCH(); | ||
| 425 | |||
| 426 | /* replace the extract and import_request_variables functions */ | ||
| 427 | zend_hash_del(CG(function_table), "extract", sizeof("extract")); | ||
| 428 | zend_hash_del(CG(function_table), "import_request_variables", sizeof("import_request_variables")); | ||
| 429 | #ifndef ZEND_ENGINE_2 | ||
| 430 | zend_register_functions(suhosin_ex_imp_functions, NULL, MODULE_PERSISTENT TSRMLS_CC); | ||
| 431 | #else | ||
| 432 | zend_register_functions(NULL, suhosin_ex_imp_functions, NULL, MODULE_PERSISTENT TSRMLS_CC); | ||
| 433 | #endif | ||
| 434 | |||
| 435 | |||
| 436 | |||
| 437 | |||
| 438 | } | ||
| 439 | |||
| 440 | |||
| 441 | /* | ||
| 442 | * Local variables: | ||
| 443 | * tab-width: 4 | ||
| 444 | * c-basic-offset: 4 | ||
| 445 | * End: | ||
| 446 | * vim600: noet sw=4 ts=4 fdm=marker | ||
| 447 | * vim<600: noet sw=4 ts=4 | ||
| 448 | */ | ||
| 449 | |||
| 450 | |||
