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