1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * Copyright 2015 Nexenta Systems, Inc. All rights reserved. 27 */ 28 29 /* $Id: mod_ipp.c 149 2006-04-25 16:55:01Z njacobs $ */ 30 31 /* 32 * Internet Printing Protocol (IPP) module for Apache. 33 */ 34 35 #include "ap_config.h" 36 37 #include <stdio.h> 38 #include <unistd.h> 39 #include <time.h> 40 #include <sys/time.h> 41 #include <values.h> 42 #include <libintl.h> 43 #include <alloca.h> 44 45 #include "httpd.h" 46 #include "http_config.h" 47 #include "http_core.h" 48 #include "http_protocol.h" 49 #include "http_log.h" 50 #include "http_main.h" 51 #ifndef APACHE2 52 #include "apr_compat.h" 53 #define apr_table_get ap_table_get 54 #endif /* APACHE2 */ 55 56 #include "papi.h" 57 58 #include <papi.h> 59 #include <ipp-listener.h> 60 61 #ifndef APACHE2 62 module MODULE_VAR_EXPORT ipp_module; 63 #else 64 module AP_MODULE_DECLARE_DATA ipp_module; 65 #endif 66 67 #ifndef AP_INIT_TAKE1 /* Apache 2.X has this, but 1.3.X does not */ 68 #define AP_INIT_NO_ARGS(directive, action, arg, where, mesg) \ 69 { directive, action, arg, where, NO_ARGS, mesg } 70 #define AP_INIT_TAKE1(directive, action, arg, where, mesg) \ 71 { directive, action, arg, where, TAKE1, mesg } 72 #define AP_INIT_TAKE2(directive, action, arg, where, mesg) \ 73 { directive, action, arg, where, TAKE2, mesg } 74 #endif 75 76 typedef struct { 77 int conformance; 78 char *default_user; 79 char *default_svc; 80 papi_attribute_t **operations; 81 } IPPListenerConfig; 82 83 #ifdef DEBUG 84 void 85 dump_buffer(FILE *fp, char *tag, char *buffer, int bytes) 86 { 87 int i, j, ch; 88 89 fprintf(fp, "%s %d(0x%x) bytes\n", (tag ? tag : ""), bytes, bytes); 90 for (i = 0; i < bytes; i += 16) { 91 fprintf(fp, "%s ", (tag ? tag : "")); 92 93 for (j = 0; j < 16 && (i + j) < bytes; j ++) 94 fprintf(fp, " %02X", buffer[i + j] & 255); 95 96 while (j < 16) { 97 fprintf(fp, " "); 98 j++; 99 } 100 101 fprintf(fp, " "); 102 for (j = 0; j < 16 && (i + j) < bytes; j ++) { 103 ch = buffer[i + j] & 255; 104 if (ch < ' ' || ch == 127) 105 ch = '.'; 106 putc(ch, fp); 107 } 108 putc('\n', fp); 109 } 110 fflush(fp); 111 } 112 #endif 113 114 static ssize_t 115 read_data(void *fd, void *buf, size_t siz) 116 { 117 ssize_t len_read; 118 request_rec *ap_r = (request_rec *)fd; 119 120 len_read = ap_get_client_block(ap_r, buf, siz); 121 #ifndef APACHE2 122 ap_reset_timeout(ap_r); 123 #endif 124 125 #ifdef DEBUG 126 fprintf(stderr, "read_data(0x%8.8x, 0x%8.8x, %d): %d", 127 fd, buf, siz, len_read); 128 if (len_read < 0) 129 fprintf(stderr, ": %s", strerror(errno)); 130 putc('\n', stderr); 131 dump_buffer(stderr, "read_data:", buf, len_read); 132 #endif 133 134 return (len_read); 135 } 136 137 static ssize_t 138 write_data(void *fd, void *buf, size_t siz) 139 { 140 ssize_t len_written; 141 request_rec *ap_r = (request_rec *)fd; 142 143 #ifndef APACHE2 144 ap_reset_timeout(ap_r); 145 #endif 146 #ifdef DEBUG 147 dump_buffer(stderr, "write_data:", buf, siz); 148 #endif 149 len_written = ap_rwrite(buf, siz, ap_r); 150 151 return (len_written); 152 } 153 154 static void 155 discard_data(request_rec *r) 156 { 157 #ifdef APACHE2 158 (void) ap_discard_request_body(r); 159 #else 160 /* 161 * This is taken from ap_discard_request_body(). The reason we can't 162 * just use it in Apache 1.3 is that it does various timeout things we 163 * don't want it to do. Apache 2.0 doesn't do that, so we can safely 164 * use the normal function. 165 */ 166 if (r->read_chunked || r->remaining > 0) { 167 char dumpbuf[HUGE_STRING_LEN]; 168 int i; 169 170 do { 171 i = ap_get_client_block(r, dumpbuf, HUGE_STRING_LEN); 172 #ifdef DEBUG 173 dump_buffer(stderr, "discarded", dumpbuf, i); 174 #endif 175 } while (i > 0); 176 } 177 #endif 178 } 179 180 void _log_rerror(const char *file, int line, int level, request_rec *r, 181 const char *fmt, ...) 182 { 183 va_list args; 184 size_t size; 185 char *message = alloca(BUFSIZ); 186 187 va_start(args, fmt); 188 /* 189 * fill in the message. If the buffer is too small, allocate 190 * one that is large enough and fill it in. 191 */ 192 if ((size = vsnprintf(message, BUFSIZ, fmt, args)) >= BUFSIZ) 193 if ((message = alloca(size)) != NULL) 194 vsnprintf(message, size, fmt, args); 195 va_end(args); 196 197 #ifdef APACHE2 198 ap_log_rerror(file, line, level, APR_SUCCESS, r, message); 199 #else 200 ap_log_rerror(file, line, level, r, message); 201 #endif 202 } 203 204 static int 205 ipp_handler(request_rec *r) 206 { 207 papi_attribute_t **request = NULL, **response = NULL; 208 IPPListenerConfig *config; 209 papi_status_t status; 210 const char *s; 211 int sockfd = -1; 212 int ret; 213 214 /* Really, IPP is all POST requests */ 215 if (r->method_number != M_POST) 216 return (DECLINED); 217 218 /* 219 * An IPP request must have a MIME type of "application/ipp" 220 * (RFC-2910, Section 4, page 19). If it doesn't match this 221 * MIME type, we should decline the request and let someone else 222 * try and handle it. 223 */ 224 if (r->headers_in == NULL) 225 return (DECLINED); 226 s = apr_table_get(r->headers_in, "Content-Type"); 227 if ((s == NULL) || (strcasecmp(s, "application/ipp") != 0)) 228 return (DECLINED); 229 230 /* CHUNKED_DECHUNK might not work right for IPP? */ 231 if ((ret = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK)) != OK) 232 return (ret); 233 234 if (!ap_should_client_block(r)) 235 return (HTTP_INTERNAL_SERVER_ERROR); 236 237 #ifndef APACHE2 238 ap_soft_timeout("ipp_module: read/reply request ", r); 239 #endif 240 /* read the IPP request off the network */ 241 status = ipp_read_message(read_data, r, &request, IPP_TYPE_REQUEST); 242 243 if (status != PAPI_OK) 244 _log_rerror(APLOG_MARK, APLOG_ERR, r, 245 "read failed: %s\n", papiStatusString(status)); 246 #ifdef DEBUG 247 papiAttributeListPrint(stderr, request, "request (%d) ", getpid()); 248 #endif 249 250 #ifdef APACHE2 251 s = ap_get_remote_host(r->connection, r->per_dir_config, 252 REMOTE_NAME, NULL); 253 #else 254 s = ap_get_remote_host(r->connection, r->per_dir_config, 255 REMOTE_NAME); 256 #endif 257 (void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL, 258 "originating-host", (char *)s); 259 260 (void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL, 261 "uri-port", ap_get_server_port(r)); 262 263 if (r->headers_in != NULL) { 264 char *host = (char *)apr_table_get(r->headers_in, "Host"); 265 266 if ((host == NULL) || (host[0] == '\0')) 267 host = (char *)ap_get_server_name(r); 268 269 (void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL, 270 "uri-host", host); 271 } 272 (void) papiAttributeListAddString(&request, PAPI_ATTR_EXCL, 273 "uri-path", r->uri); 274 275 config = ap_get_module_config(r->per_dir_config, &ipp_module); 276 if (config != NULL) { 277 (void) papiAttributeListAddInteger(&request, PAPI_ATTR_EXCL, 278 "conformance", config->conformance); 279 (void) papiAttributeListAddCollection(&request, PAPI_ATTR_EXCL, 280 "operations", config->operations); 281 if (config->default_user != NULL) 282 (void) papiAttributeListAddString(&request, 283 PAPI_ATTR_EXCL, "default-user", 284 config->default_user); 285 if (config->default_svc != NULL) 286 (void) papiAttributeListAddString(&request, 287 PAPI_ATTR_EXCL, "default-service", 288 config->default_svc); 289 } 290 291 /* 292 * For Trusted Solaris, pass the fd number of the socket connection 293 * to the backend so the it can be forwarded to the backend print 294 * service to retrieve the sensativity label off of a multi-level 295 * port. 296 */ 297 #ifdef APACHE2 298 /* 299 * In Apache 2.4 and later, could use: ap_get_conn_socket() 300 * Apache 2.2 uses ap_get_module_config() but that needs 301 * &core_module, for .module_index (which is just zero). 302 * Could either inline that with index zero, or declare 303 * core_module here. Latter seems less evil. 304 */ 305 { 306 extern module core_module; 307 apr_socket_t *csd = ap_get_module_config( 308 r->connection->conn_config, &core_module); 309 if (csd != NULL) 310 (void) apr_os_sock_get(&sockfd, csd); 311 } 312 #else 313 sockfd = ap_bfileno(r->connection->client, B_RD); 314 #endif 315 if (sockfd != -1) { 316 (void) papiAttributeListAddInteger(&request, 317 PAPI_ATTR_EXCL, "peer-socket", sockfd); 318 } 319 320 /* process the request */ 321 status = ipp_process_request(request, &response, read_data, r); 322 if (status != PAPI_OK) { 323 errno = 0; 324 _log_rerror(APLOG_MARK, APLOG_ERR, r, 325 "request failed: %s\n", papiStatusString(status)); 326 discard_data(r); 327 } 328 #ifdef DEBUG 329 fprintf(stderr, "processing result: %s\n", papiStatusString(status)); 330 papiAttributeListPrint(stderr, response, "response (%d) ", getpid()); 331 #endif 332 333 /* 334 * If the client is using chunking and we have not yet received the 335 * final "0" sized chunk, we need to discard any data that may 336 * remain in the post request. 337 */ 338 if ((r->read_chunked != 0) && 339 (apr_table_get(r->headers_in, "Content-Length") == NULL)) 340 discard_data(r); 341 342 /* write an IPP response back to the network */ 343 r->content_type = "application/ipp"; 344 345 #ifndef APACHE2 346 ap_send_http_header(r); 347 #endif 348 349 status = ipp_write_message(write_data, r, response); 350 if (status != PAPI_OK) 351 _log_rerror(APLOG_MARK, APLOG_ERR, r, 352 "write failed: %s\n", papiStatusString(status)); 353 #ifdef DEBUG 354 fprintf(stderr, "write result: %s\n", papiStatusString(status)); 355 fflush(stderr); 356 #endif 357 358 papiAttributeListFree(request); 359 papiAttributeListFree(response); 360 361 #ifndef APACHE2 362 ap_kill_timeout(r); 363 if (ap_rflush(r) < 0) 364 _log_rerror(APLOG_MARK, APLOG_ERR, r, 365 "flush failed, response may not have been sent"); 366 #endif 367 368 return (OK); 369 } 370 371 372 /*ARGSUSED1*/ 373 static void * 374 create_ipp_dir_config( 375 #ifndef APACHE2 376 pool *p, 377 #else 378 apr_pool_t *p, 379 #endif 380 char *dirspec) 381 { 382 IPPListenerConfig *config; 383 #ifndef APACHE2 384 config = ap_pcalloc(p, sizeof (*config)); 385 #else 386 config = apr_pcalloc(p, sizeof (*config)); 387 #endif 388 389 if (config != NULL) { 390 (void) memset(config, 0, sizeof (*config)); 391 config->conformance = IPP_PARSE_CONFORMANCE_RASH; 392 config->default_user = NULL; 393 config->default_svc = NULL; 394 (void) ipp_configure_operation(&config->operations, 395 "required", "enable"); 396 } 397 398 return (config); 399 } 400 401 /*ARGSUSED0*/ 402 static const char * 403 ipp_conformance(cmd_parms *cmd, void *cfg, const char *arg) 404 { 405 IPPListenerConfig *config = (IPPListenerConfig *)cfg; 406 407 if (strncasecmp(arg, "automatic", 4) == 0) { 408 config->conformance = IPP_PARSE_CONFORMANCE_RASH; 409 } else if (strcasecmp(arg, "1.0") == 0) { 410 config->conformance = IPP_PARSE_CONFORMANCE_LOOSE; 411 } else if (strcasecmp(arg, "1.1") == 0) { 412 config->conformance = IPP_PARSE_CONFORMANCE_STRICT; 413 } else { 414 return ("unknown conformance, try (automatic/1.0/1.1)"); 415 } 416 417 return (NULL); 418 } 419 420 /*ARGSUSED0*/ 421 static const char * 422 ipp_operation(cmd_parms *cmd, void *cfg, const char *op, const char *toggle) 423 { 424 IPPListenerConfig *config = (IPPListenerConfig *)cfg; 425 papi_status_t status; 426 427 status = ipp_configure_operation(&config->operations, 428 (char *)op, (char *)toggle); 429 switch (status) { 430 case PAPI_OK: 431 return (NULL); 432 case PAPI_BAD_ARGUMENT: 433 return (gettext("internal error (invalid argument)")); 434 default: 435 return (papiStatusString(status)); 436 } 437 438 /* NOTREACHED */ 439 /* return (gettext("contact your software vendor")); */ 440 } 441 442 static const char * 443 ipp_default_user(cmd_parms *cmd, void *cfg, const char *arg) 444 { 445 IPPListenerConfig *config = (IPPListenerConfig *)cfg; 446 447 config->default_user = (char *)arg; 448 449 return (NULL); 450 } 451 452 static const char * 453 ipp_default_svc(cmd_parms *cmd, void *cfg, const char *arg) 454 { 455 IPPListenerConfig *config = (IPPListenerConfig *)cfg; 456 457 config->default_svc = (char *)arg; 458 459 return (NULL); 460 } 461 462 #ifdef DEBUG 463 /*ARGSUSED0*/ 464 volatile int ipp_module_hang_sleeping = 1; 465 static const char * 466 ipp_module_hang(cmd_parms *cmd, void *cfg) 467 { 468 469 /* 470 * Wait so we can attach with a debugger. Once attached, 471 * assign ipp_module_hang_sleeping = 0 and step through. 472 */ 473 while (ipp_module_hang_sleeping) 474 sleep(1); 475 476 return (NULL); 477 } 478 #endif /* DEBUG */ 479 480 static const command_rec ipp_cmds[] = 481 { 482 AP_INIT_TAKE1("ipp-conformance", ipp_conformance, NULL, ACCESS_CONF, 483 "IPP protocol conformance (loose/strict)"), 484 AP_INIT_TAKE2("ipp-operation", ipp_operation, NULL, ACCESS_CONF, 485 "IPP protocol operations to enable/disable)"), 486 AP_INIT_TAKE1("ipp-default-user", ipp_default_user, NULL, ACCESS_CONF, 487 "default user for various operations"), 488 AP_INIT_TAKE1("ipp-default-service", ipp_default_svc, NULL, ACCESS_CONF, 489 "default service for various operations"), 490 #ifdef DEBUG 491 AP_INIT_NO_ARGS("ipp-module-hang", ipp_module_hang, NULL, ACCESS_CONF, 492 "hang the module until we can attach a debugger (no args)"), 493 #endif 494 { NULL } 495 }; 496 497 #ifdef APACHE2 498 /*ARGSUSED0*/ 499 static const char * 500 ipp_scheme(const request_rec *r) 501 { 502 return ("ipp"); 503 } 504 505 /*ARGSUSED0*/ 506 static unsigned short 507 ipp_port(const request_rec *r) 508 { 509 return (631); 510 } 511 512 /* Dispatch list for API hooks */ 513 /*ARGSUSED0*/ 514 static void 515 ipp_register_hooks(apr_pool_t *p) 516 { 517 static const char * const modules[] = { "mod_dir.c", NULL }; 518 519 /* Need to make sure we don't get directory listings by accident */ 520 ap_hook_handler(ipp_handler, NULL, modules, APR_HOOK_MIDDLE); 521 ap_hook_default_port(ipp_port, NULL, NULL, APR_HOOK_MIDDLE); 522 ap_hook_http_scheme(ipp_scheme, NULL, NULL, APR_HOOK_MIDDLE); 523 } 524 525 module AP_MODULE_DECLARE_DATA ipp_module = { 526 STANDARD20_MODULE_STUFF, 527 create_ipp_dir_config, /* create per-dir config */ 528 NULL, /* merge per-dir config */ 529 NULL, /* create per-server config */ 530 NULL, /* merge per-server config */ 531 ipp_cmds, /* table of config commands */ 532 ipp_register_hooks /* register hooks */ 533 }; 534 535 #else /* Apache 1.X */ 536 537 /* Dispatch list of content handlers */ 538 static const handler_rec ipp_handlers[] = { 539 /* 540 * This handler association causes all IPP request with the 541 * correct MIME type to call the protocol handler. 542 */ 543 { "application/ipp", ipp_handler }, 544 /* 545 * This hander association is causes everything to go through the IPP 546 * protocol request handler. This is necessary because client POST 547 * request may be for something outside of the normal printer-uri 548 * space. 549 */ 550 { "*/*", ipp_handler }, 551 552 { NULL, NULL } 553 }; 554 555 556 module MODULE_VAR_EXPORT ipp_module = { 557 STANDARD_MODULE_STUFF, 558 NULL, /* module initializer */ 559 create_ipp_dir_config, /* create per-dir config structures */ 560 NULL, /* merge per-dir config structures */ 561 NULL, /* create per-server config structures */ 562 NULL, /* merge per-server config structures */ 563 ipp_cmds, /* table of config file commands */ 564 ipp_handlers, /* [#8] MIME-typed-dispatched handlers */ 565 NULL, /* [#1] URI to filename translation */ 566 NULL, /* [#4] validate user id from request */ 567 NULL, /* [#5] check if the user is ok _here_ */ 568 NULL, /* [#3] check access by host address */ 569 NULL, /* [#6] determine MIME type */ 570 NULL, /* [#7] pre-run fixups */ 571 NULL, /* [#9] log a transaction */ 572 NULL, /* [#2] header parser */ 573 NULL, /* child_init */ 574 NULL, /* child_exit */ 575 NULL /* [#0] post read-request */ 576 }; 577 #endif 578