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