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