Changeset 1435
- Timestamp:
- 05/18/08 10:41:13 (5 months ago)
- Files:
-
- cherokee/trunk/ChangeLog (modified) (1 diff)
- cherokee/trunk/admin/Form.py (modified) (3 diffs)
- cherokee/trunk/admin/ModuleHtdigest.py (modified) (1 diff)
- cherokee/trunk/admin/ModuleHtpasswd.py (modified) (1 diff)
- cherokee/trunk/admin/ModulePlain.py (modified) (1 diff)
- cherokee/trunk/admin/PageAdvanced.py (modified) (1 diff)
- cherokee/trunk/admin/PageEntry.py (modified) (1 diff)
- cherokee/trunk/admin/PageGeneral.py (modified) (1 diff)
- cherokee/trunk/admin/PageVServer.py (modified) (1 diff)
- cherokee/trunk/admin/PageVServers.py (modified) (1 diff)
- cherokee/trunk/admin/server.py (modified) (2 diffs)
- cherokee/trunk/admin/validations.py (modified) (1 diff)
- cherokee/trunk/cherokee/Makefile.am (modified) (1 diff)
- cherokee/trunk/cherokee/admin_client.c (modified) (10 diffs)
- cherokee/trunk/cherokee/admin_client.h (modified) (1 diff)
- cherokee/trunk/cherokee/downloader.c (modified) (6 diffs)
- cherokee/trunk/cherokee/downloader_async.c (modified) (3 diffs)
- cherokee/trunk/cherokee/macros.h (modified) (1 diff)
- cherokee/trunk/cherokee/main_tweak.c (modified) (5 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
cherokee/trunk/ChangeLog
r1434 r1435 1 1 2008-05-17 Alvaro Lopez Ortega <alvaro@alobbs.com> 2 3 * admin/validations.py (is_local_file_exists) 4 (is_local_dir_exists), PageEntry.py, ModuleHtdigest.py, 5 PageAdvanced.py, ModulePlain.py, PageVServers.py, PageGeneral.py, 6 PageVServer.py, Form.py, ModuleHtpasswd.py: Added chroot support 7 to the local file/directory testing. This fix is kind of hacky: I 8 needed to import the "cfg" global in order to check whether the 9 server is jailed within a chroot. Anyway, even if it isn't clean, 10 it's a fairly acceptable solution. 11 12 * cherokee/main_tweak.c: Updated to use getopt_long(). The help 13 text has been rewritten as well. 14 15 * cherokee/Makefile.am (bin_PROGRAMS): "Cherokee tweak" binary 16 executable has changed from cherokee_tweak to cherokee-tweak. It 17 looked kind of weird with the underscore. 18 19 * cherokee/admin_client.c: Cleaned out. The internal phase 20 property was no needed. ->dowloader->status is enough to know in 21 this case. 22 23 * cherokee/downloader.c (cherokee_downloader_step): ->status set 24 and unset code rewritten to use BIT_SET and BIT_UNSET macros. It 25 is more readable now. 2 26 3 27 * cherokee/handler_proxy.c (cherokee_handler_proxy_add_headers): cherokee/trunk/admin/Form.py
r1415 r1435 1 1 import re 2 import types 2 3 3 4 from Entry import * … … 313 314 314 315 def ValidateChange_SingleKey (self, key, post, validation): 315 for regex, validation_func in validation: 316 for regex, tmp in validation: 317 pass_cfg = False 318 319 if type(tmp) == types.FunctionType: 320 validation_func = tmp 321 322 elif type(tmp) == types.TupleType: 323 validation_func = tmp[0] 324 for k in tmp[1:]: 325 if k == 'cfg': 326 pass_cfg = True 327 else: 328 print "UNKNOWN validation option:", k 329 316 330 p = re.compile (regex) 317 331 if p.match (key): … … 320 334 continue 321 335 try: 322 tmp = validation_func (value) 336 if pass_cfg: 337 tmp = validation_func (value, self._cfg) 338 else: 339 tmp = validation_func (value) 323 340 post[key] = [tmp] 324 341 except ValueError, error: cherokee/trunk/admin/ModuleHtdigest.py
r1419 r1435 5 5 6 6 DATA_VALIDATION = [ 7 ('vserver!.*?!(directory|extensions|request)!.*?!passwdfile', validations.is_local_file_exists) 7 ('vserver!.*?!(directory|extensions|request)!.*?!passwdfile', 8 (validations.is_local_file_exists, 'cfg')) 8 9 ] 9 10 cherokee/trunk/admin/ModuleHtpasswd.py
r1419 r1435 5 5 6 6 DATA_VALIDATION = [ 7 ('vserver!.*?!(directory|extensions|request)!.*?!passwdfile', validations.is_local_file_exists) 7 ('vserver!.*?!(directory|extensions|request)!.*?!passwdfile', 8 (validations.is_local_file_exists, 'cfg')) 8 9 ] 9 10 cherokee/trunk/admin/ModulePlain.py
r1419 r1435 5 5 6 6 DATA_VALIDATION = [ 7 ('vserver!.*?!(directory|extensions|request)!.*?!passwdfile', validations.is_local_file_exists) 7 ('vserver!.*?!(directory|extensions|request)!.*?!passwdfile', 8 (validations.is_local_file_exists, 'cfg')) 8 9 ] 9 10 cherokee/trunk/admin/PageAdvanced.py
r1394 r1435 11 11 ("server!sendfile_min", validations.is_positive_int), 12 12 ("server!sendfile_max", validations.is_positive_int), 13 ('server!panic_action', validations.is_local_file_exists),13 ('server!panic_action', (validations.is_local_file_exists, 'cfg')), 14 14 ('server!listen_queue', validations.is_positive_int), 15 15 ('server!max_connection_reuse', validations.is_positive_int), cherokee/trunk/admin/PageEntry.py
r1419 r1435 16 16 17 17 DATA_VALIDATION = [ 18 ("vserver!.*?!(directory|extensions|request)!.*?!document_root", validations.is_local_dir_exists), 19 ("vserver!.*?!(directory|extensions|request)!.*?!allow_from", validations.is_ip_or_netmask_list) 18 ("vserver!.*?!(directory|extensions|request)!.*?!document_root", 19 (validations.is_local_dir_exists, 'cfg')), 20 ("vserver!.*?!(directory|extensions|request)!.*?!allow_from", 21 validations.is_ip_or_netmask_list) 20 22 ] 21 23 cherokee/trunk/admin/PageGeneral.py
r1420 r1435 20 20 ("server!port.*", validations.is_tcp_port), 21 21 ("server!listen", validations.is_ip), 22 ("server!chroot", validations.is_local_dir_exists),22 ("server!chroot", (validations.is_local_dir_exists, 'cfg')), 23 23 ] 24 24 cherokee/trunk/admin/PageVServer.py
r1432 r1435 9 9 10 10 DATA_VALIDATION = [ 11 ("vserver!.*?!document_root", validations.is_local_dir_exists),12 ("vserver!.*?!ssl_certificate_file", validations.is_local_file_exists),13 ("vserver!.*?!ssl_certificate_key_file", validations.is_local_file_exists),14 ("vserver!.*?!ssl_ca_list_file", validations.is_local_file_exists),15 ("vserver!.*?!logger!access!filename", validations.parent_is_dir),16 ("vserver!.*?!logger!error!filename", validations.parent_is_dir),17 ("vserver!.*?!logger!access!command", validations.is_local_file_exists),18 ("vserver!.*?!logger!error!command", validations.is_local_file_exists),11 ("vserver!.*?!document_root", (validations.is_local_dir_exists, 'cfg')), 12 ("vserver!.*?!ssl_certificate_file", (validations.is_local_file_exists, 'cfg')), 13 ("vserver!.*?!ssl_certificate_key_file", (validations.is_local_file_exists, 'cfg')), 14 ("vserver!.*?!ssl_ca_list_file", (validations.is_local_file_exists, 'cfg')), 15 ("vserver!.*?!logger!access!filename", (validations.parent_is_dir, 'cfg')), 16 ("vserver!.*?!logger!error!filename", (validations.parent_is_dir, 'cfg')), 17 ("vserver!.*?!logger!access!command", (validations.is_local_file_exists, 'cfg')), 18 ("vserver!.*?!logger!error!command", (validations.is_local_file_exists, 'cfg')), 19 19 ] 20 20 cherokee/trunk/admin/PageVServers.py
r1415 r1435 7 7 8 8 DATA_VALIDATION = [ 9 ("new_vserver_name", validations.is_safe_id),10 ("new_vserver_droot", validations.is_local_dir_exists),9 ("new_vserver_name", validations.is_safe_id), 10 ("new_vserver_droot", (validations.is_local_dir_exists, 'cfg')), 11 11 ] 12 12 cherokee/trunk/admin/server.py
r1326 r1435 46 46 def handle_request (self): 47 47 global cfg 48 48 49 49 page = None 50 50 headers = "" … … 165 165 srv.server_close() 166 166 167 168 167 if __name__ == '__main__': 169 168 main() cherokee/trunk/admin/validations.py
r1400 r1435 87 87 return value 88 88 89 def is_local_dir_exists (value ):89 def is_local_dir_exists (value, cfg): 90 90 value = is_path (value) 91 91 92 if not os.path.exists(value): 92 chroot = cfg.get_val('server!chroot') 93 if chroot: 94 path = os.path.normpath (chroot + os.path.sep + value) 95 else: 96 path = value 97 98 if not os.path.exists(path): 93 99 raise ValueError, 'Path does not exits' 94 100 95 if not os.path.isdir( value):101 if not os.path.isdir(path): 96 102 raise ValueError, 'Path is not a directory' 97 103 98 104 return value 99 105 100 def is_local_file_exists (value ):106 def is_local_file_exists (value, cfg): 101 107 value = is_path (value) 102 108 103 if not os.path.exists(value): 109 chroot = cfg.get_val('server!chroot') 110 if chroot: 111 path = os.path.normpath (chroot + os.path.sep + value) 112 else: 113 path = value 114 115 if not os.path.exists(path): 104 116 raise ValueError, 'Path does not exits' 105 117 106 if not os.path.isfile( value):118 if not os.path.isfile(path): 107 119 raise ValueError, 'Path is not a regular file' 108 120 109 121 return value 110 122 111 def parent_is_dir (value ):123 def parent_is_dir (value, cfg): 112 124 value = is_path (value) 113 125 114 126 dirname, filename = os.path.split(value) 115 is_local_dir_exists (dirname )127 is_local_dir_exists (dirname, cfg) 116 128 117 129 return value cherokee/trunk/cherokee/Makefile.am
r1430 r1435 1147 1147 # Log rotate utility 1148 1148 # 1149 bin_PROGRAMS = cherokee _tweak1149 bin_PROGRAMS = cherokee-tweak 1150 1150 1151 1151 cherokee_tweak_SOURCES = main_tweak.c cherokee/trunk/cherokee/admin_client.c
r1131 r1435 36 36 37 37 38 typedef enum {39 admin_phase_init,40 admin_phase_stepping,41 admin_phase_finished42 } admin_phase_t;43 44 45 38 struct cherokee_admin_client { 46 39 cherokee_downloader_async_t *downloader; … … 51 44 52 45 cherokee_post_t post; 53 admin_phase_t phase;54 46 cherokee_fdpoll_t *poll_ref; 55 47 }; … … 66 58 /* Init 67 59 */ 68 n->phase = admin_phase_init; 69 70 n->poll_ref = NULL; 71 n->url_ref = NULL; 60 n->poll_ref = NULL; 61 n->url_ref = NULL; 72 62 73 63 cherokee_post_init (&n->post); … … 92 82 93 83 free (admin); 94 return ret_ok;95 }96 97 98 static ret_t99 on_downloader_finish (void *_downloader, void *_param)100 {101 cherokee_admin_client_t *admin = _param;102 103 admin->phase = admin_phase_finished;104 84 return ret_ok; 105 85 } … … 115 95 ret_t ret; 116 96 cherokee_downloader_t *downloader = DOWNLOADER(admin->downloader); 117 118 admin->phase = admin_phase_init;119 97 120 98 admin->poll_ref = poll; … … 134 112 /* Set up the downloader object properties 135 113 */ 136 ret = cherokee_downloader_async_set_fdpoll ( downloader, admin->poll_ref);114 ret = cherokee_downloader_async_set_fdpoll (DOWNLOADER_ASYNC(downloader), admin->poll_ref); 137 115 if (unlikely (ret != ret_ok)) return ret; 138 116 … … 148 126 if (unlikely (ret != ret_ok)) return ret; 149 127 150 #warning "Fix this!"151 /* ret = cherokee_downloader_connect_event (downloader, downloader_event_finish, on_downloader_finish, admin); */152 /* if (unlikely (ret != ret_ok)) return ret; */153 154 128 TRACE(ENTRIES, "Exists obj=%p\n", admin); 155 129 return ret_ok; … … 177 151 cherokee_buffer_clean (&admin->reply); 178 152 179 admin->phase = admin_phase_init; 180 return ret_ok; 181 } 182 183 184 ret_t 185 cherokee_admin_client_internal_step (cherokee_admin_client_t *admin) 186 { 187 ret_t ret; 188 189 TRACE(ENTRIES, "Enters phase=%d\n", admin->phase); 190 191 /* Has it finished? 192 */ 193 if (admin->phase == admin_phase_finished) 194 return ret_ok; 195 196 /* Sanity check 197 */ 198 if (admin->phase != admin_phase_stepping) 199 return ret_error; 153 return ret_ok; 154 } 155 156 157 static ret_t 158 internal_step (cherokee_admin_client_t *admin) 159 { 160 ret_t ret; 161 cherokee_downloader_t *downloader = DOWNLOADER(admin->downloader); 162 163 TRACE(ENTRIES, "Downloader phase=%d\n", downloader->phase); 200 164 201 165 /* It's stepping … … 233 197 cherokee_post_append (&admin->post, str, str_len); 234 198 cherokee_downloader_post_set (downloader, &admin->post); 235 236 admin->phase = admin_phase_stepping;237 199 } 238 200 … … 279 241 void *argument) 280 242 { 281 ret_t ret; 282 283 TRACE(ENTRIES, "phase %d\n", admin->phase); 284 285 switch (admin->phase) { 286 case admin_phase_init: 243 ret_t ret; 244 cherokee_downloader_t *downloader = DOWNLOADER(admin->downloader); 245 246 TRACE(ENTRIES, "Downloader phase: %d\n", downloader->phase); 247 248 /* Initial state: needs to get the Post info 249 */ 250 if ((downloader->phase == downloader_phase_init) && 251 (downloader->post == NULL)) 252 { 287 253 conf_request_func (admin, argument); 288 254 return ret_eagain; 289 case admin_phase_stepping: 290 ret = cherokee_admin_client_internal_step (admin); 291 return ret; 292 case admin_phase_finished: 255 } 256 257 /* Finished 258 */ 259 if (downloader->phase == downloader_phase_finished) 293 260 return ret_ok; 294 default: 295 SHOULDNT_HAPPEN;296 }297 298 return ret _error;261 262 /* It's iterating 263 */ 264 ret = internal_step (admin); 265 return ret; 299 266 } 300 267 cherokee/trunk/cherokee/admin_client.h
r1131 r1435 80 80 81 81 82 ret_t cherokee_admin_client_new (cherokee_admin_client_t **admin);83 ret_t cherokee_admin_client_free (cherokee_admin_client_t *admin);82 ret_t cherokee_admin_client_new (cherokee_admin_client_t **admin); 83 ret_t cherokee_admin_client_free (cherokee_admin_client_t *admin); 84 84 85 ret_t cherokee_admin_client_prepare (cherokee_admin_client_t *admin, cherokee_fdpoll_t *poll, cherokee_buffer_t *url, cherokee_buffer_t *user, cherokee_buffer_t *pass); 86 ret_t cherokee_admin_client_connect (cherokee_admin_client_t *admin); 87 ret_t cherokee_admin_client_reuse (cherokee_admin_client_t *admin); 88 ret_t cherokee_admin_client_internal_step (cherokee_admin_client_t *admin); 89 ret_t cherokee_admin_client_get_reply_code (cherokee_admin_client_t *admin, cherokee_http_t *code); 85 ret_t cherokee_admin_client_prepare (cherokee_admin_client_t *admin, cherokee_fdpoll_t *poll, cherokee_buffer_t *url, cherokee_buffer_t *user, cherokee_buffer_t *pass); 86 ret_t cherokee_admin_client_connect (cherokee_admin_client_t *admin); 87 ret_t cherokee_admin_client_reuse (cherokee_admin_client_t *admin); 88 ret_t cherokee_admin_client_get_reply_code (cherokee_admin_client_t *admin, cherokee_http_t *code); 90 89 91 90 /* Retrieve information methods cherokee/trunk/cherokee/downloader.c
r1433 r1435 469 469 if (unlikely(ret != ret_ok)) return ret; 470 470 471 downloader->status = downloader_status_headers_sent;471 BIT_SET (downloader->status, downloader_status_headers_sent); 472 472 downloader->phase = downloader_phase_send_post; 473 473 … … 482 482 } 483 483 484 downloader->status = downloader->status | downloader_status_post_sent;484 BIT_SET (downloader->status, downloader_status_post_sent); 485 485 downloader->phase = downloader_phase_read_headers; 486 486 break; … … 494 494 /* We have the header parsed, continue.. 495 495 */ 496 downloader->status = downloader->status | downloader_status_headers_received;496 BIT_SET (downloader->status, downloader_status_headers_received); 497 497 downloader->phase = downloader_phase_step; 498 498 … … 500 500 */ 501 501 if (downloader->info.body_recv >= downloader->content_length) { 502 downloader->status = downloader->status | downloader_status_data_available | downloader_status_finished; 502 BIT_SET (downloader->status, downloader_status_data_available); 503 BIT_SET (downloader->status, downloader_status_finished); 503 504 return ret_eof_have_data; 504 505 } … … 512 513 break; 513 514 case ret_ok: 514 downloader->status = downloader->status | downloader_status_data_available;515 BIT_SET (downloader->status, downloader_status_data_available); 515 516 break; 516 517 case ret_eof_have_data: 517 downloader->status = downloader->status | downloader_status_data_available | downloader_status_finished; 518 BIT_SET (downloader->status, downloader_status_data_available); 519 BIT_SET (downloader->status, downloader_status_finished); 518 520 break; 519 521 case ret_eof: 520 downloader->status = downloader->status & (~downloader_status_data_available | downloader_status_finished); 522 BIT_UNSET (downloader->status, downloader_status_data_available); 523 BIT_SET (downloader->status, downloader_status_finished); 521 524 break; 522 525 case ret_eagain: 523 downloader->status = downloader->status & ~downloader_status_data_available;526 BIT_UNSET (downloader->status, downloader_status_data_available); 524 527 break; 525 528 default: … … 531 534 TRACE(ENTRIES, "Phase %s\n", "finished"); 532 535 533 downloader->status = downloader->status & ~downloader_status_data_available & downloader_status_finished; 536 BIT_SET (downloader->status, downloader_status_finished); 537 BIT_UNSET (downloader->status, downloader_status_data_available); 534 538 return ret_ok; 535 539 cherokee/trunk/cherokee/downloader_async.c
r1131 r1435 107 107 int re; 108 108 int fd; 109 int rw = FDPOLL_MODE_READ;109 int rw; 110 110 cherokee_downloader_t *down = DOWNLOADER(adownloader); 111 111 cherokee_fdpoll_t *fdpoll = adownloader->fdpoll_ref; 112 112 113 113 TRACE(ENTRIES, "Enters obj=%p fdpoll=%p\n", adownloader, fdpoll); 114 115 if (fdpoll == NULL) 116 return ret_error; 114 return_if_fail ((fdpoll != NULL), ret_error); 117 115 118 116 /* Watch the fd poll … … 127 125 if (down->phase <= downloader_phase_send_post) 128 126 rw = FDPOLL_MODE_WRITE; 127 else 128 rw = FDPOLL_MODE_READ; 129 129 130 130 TRACE(ENTRIES, "rw = %d\n", rw); … … 133 133 */ 134 134 fd = down->socket.socket; 135 136 135 re = cherokee_fdpoll_check (fdpoll, fd, rw); 137 136 switch (re) { cherokee/trunk/cherokee/macros.h
r1359 r1435 333 333 /* Bit masks 334 334 */ 335 #define BIT_SET(var,bit) var |= bit335 #define BIT_SET(var,bit) var |= (bit) 336 336 #define BIT_UNSET(var,bit) var &= ~(bit) 337 337 cherokee/trunk/cherokee/main_tweak.c
r1355 r1435 33 33 #include <cherokee/cherokee.h> 34 34 35 /* Notices 36 */ 37 #define APP_NAME \ 38 "Cherokee Web Server: Tweaker" 39 40 #define APP_COPY_NOTICE \ 41 "Written by Alvaro Lopez Ortega <alvaro@gnu.org>\n\n" \ 42 "Copyright (C) 2001-2008 Alvaro Lopez Ortega.\n" \ 43 "This is free software; see the source for copying conditions. There is NO\n" \ 44 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" 45 46 35 47 #define EXIT_OK 0 36 48 #define EXIT_ERROR 1 … … 58 70 print_help (void) 59 71 { 60 printf ("Cherokee Tweak\n" 61 "Usage: cherokee_tweak -c command -u url [options]\n\n" 62 " -h Print this help\n" 63 " -V Print version and exit\n\n" 72 printf (APP_NAME "\n" 73 "Usage: cherokee-tweak -c command -u url [options]\n\n" 74 " -h, --help Print this help\n" 75 " -V, --version Print version and exit\n\n" 76 " Required:\n" 77 " -c, --command=STRING Command: logrotate, trace, info\n" 78 " -a, --url=URL URL to the admin interface\n\n" 64 79 " Secutiry:\n" 65 " -u STRING User name\n" 66 " -p STRING Password\n\n" 67 " Required:\n" 68 " -c STRING Command: logrotate, trace, info\n" 69 " -a URL URL to the admin interface\n\n" 80 " -u, --user=STRING User name\n" 81 " -p, --password=STRING Password\n\n" 70 82 " Logrotate:\n" 71 " -l PATHLog file to be rotated\n\n"83 " -l, --log=PATH Log file to be rotated\n\n" 72 84 " Trace:\n" 73 " -t STRING Modules to be traced\n" 74 "\n" 75 "Report bugs to cherokee@cherokee-project.org\n"); 85 " -t, --trace=STRING Modules to be traced\n\n" 86 "Report bugs to " PACKAGE_BUGREPORT "\n"); 76 87 } 77 88 … … 79 90 print_usage (void) 80 91 { 81 printf ( "Cherokee Tweak\n"82 "Usage: cherokee _tweak -c command -u url [options]\n\n"83 "Try `cherokee _tweak --help' for more options.\n");92 printf (APP_NAME "\n" 93 "Usage: cherokee-tweak -c command -u url [options]\n\n" 94 "Try `cherokee-tweak --help' for more options.\n"); 84 95 } 85 96 … … 357 368 cherokee_buffer_t password = CHEROKEE_BUF_INIT; 358 369 370 struct option long_options[] = { 371 {"help", no_argument, NULL, 'h'}, 372 {"version", no_argument, NULL, 'V'}, 373 {"command", required_argument, NULL, 'c'}, 374 {"url", required_argument, NULL, 'a'}, 375 {"user", required_argument, NULL, 'u'}, 376 {"password", required_argument, NULL, 'p'}, 377 {"log", required_argument, NULL, 'l'}, 378 {"trace", required_argument, NULL, 't'}, 379 {NULL, 0, NULL, 0} 380 }; 381 382 /* Initialize the library 383 */ 359 384 cherokee_init(); 360 385 TRACE(ENTRIES, "Starts %d args\n", argc-1); 361 386 362 387 /* Parse the parameters 363 388 */ 364 while ((c = getopt(argc, argv, GETOPT_OPT)) != -1) {389 while ((c = getopt_long(argc, argv, "hVc:a:u:p:l:t:", long_options, NULL)) != -1) { 365 390 switch(c) { 366 case 'V':367 printf ("Cherokee Web Server %s\n"368 "Copyright (C) 2001-2008 Alvaro Lopez Ortega <alvaro@gnu.org>.\n\n"369 "This is free software; see the source for copying conditions. There is NO\n"370 "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",371 PACKAGE_VERSION);372 exit(0);373 391 case 'u': 374 392 cherokee_buffer_add (&user, optarg, strlen(optarg)); … … 389 407 cherokee_buffer_add (&log, optarg, strlen(optarg)); 390 408 break; 409 case 'V': 410 printf (APP_NAME " " PACKAGE_VERSION "\n" APP_COPY_NOTICE); 411 exit (EXIT_OK); 391 412 case 'h': 392 413 case '?':