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