X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fcups.git;a=blobdiff_plain;f=scheduler%2Fipp.c;h=637d760b4fdf7120c6b68a88a485cbad2cfe0d7c;hp=8dccbcfa1846423125dd8039a1afca18a7895e21;hb=f11a948a02771f78f50b530880a0269d4b4f58eb;hpb=a4d045870e17abe8840b77615c0d59f4b332ee31 diff --git a/scheduler/ipp.c b/scheduler/ipp.c index 8dccbcfa1..637d760b4 100644 --- a/scheduler/ipp.c +++ b/scheduler/ipp.c @@ -1,44 +1,49 @@ /* - * "$Id: ipp.c 5023 2006-01-29 14:39:44Z mike $" + * "$Id: ipp.c 7944 2008-09-16 22:32:42Z mike $" * * IPP routines for the Common UNIX Printing System (CUPS) scheduler. * - * Copyright 1997-2006 by Easy Software Products, all rights reserved. + * Copyright 2007-2009 by Apple Inc. + * Copyright 1997-2007 by Easy Software Products, all rights reserved. * - * These coded instructions, statements, and computer programs are the - * property of Easy Software Products and are protected by Federal - * copyright law. Distribution and use rights are outlined in the file - * "LICENSE.txt" which should have been included with this file. If this - * file is missing or damaged please contact Easy Software Products - * at: - * - * Attn: CUPS Licensing Information - * Easy Software Products - * 44141 Airport View Drive, Suite 204 - * Hollywood, Maryland 20636 USA + * This file contains Kerberos support code, copyright 2006 by + * Jelmer Vernooij. * - * Voice: (301) 373-9600 - * EMail: cups-info@cups.org - * WWW: http://www.cups.org + * These coded instructions, statements, and computer programs are the + * property of Apple Inc. and are protected by Federal copyright + * law. Distribution and use rights are outlined in the file "LICENSE.txt" + * which should have been included with this file. If this file is + * file is missing or damaged, see the license at "http://www.cups.org/". * * Contents: * - * cupsdProcessIPPRequest() - Process an incoming IPP request... + * cupsdProcessIPPRequest() - Process an incoming IPP request. + * cupsdTimeoutJob() - Timeout a job waiting on job files. * accept_jobs() - Accept print jobs to a printer. * add_class() - Add a class to the system. * add_file() - Add a file to a job. + * add_job() - Add a job to a print queue. * add_job_state_reasons() - Add the "job-state-reasons" attribute based * upon the job and printer state... - * add_job_subscriptions() - Add any subcriptions for a job. + * add_job_subscriptions() - Add any subscriptions for a job. + * add_job_uuid() - Add job-uuid attribute to a job. * add_printer() - Add a printer to the system. * add_printer_state_reasons() - Add the "printer-state-reasons" attribute * based upon the printer state... - * add_queued_job_count() - Add the "queued-job-count" attribute for + * add_queued_job_count() - Add the "queued-job-count" attribute for the + * specified printer or class. + * apple_init_profile() - Initialize a color profile. + * apple_register_profiles() - Register color profiles for a printer. + * apple_unregister_profiles() - Remove color profiles for the specified + * printer. + * apply_printer_defaults() - Apply printer default options to a job. * authenticate_job() - Set job authentication info. * cancel_all_jobs() - Cancel all print jobs. * cancel_job() - Cancel a print job. * cancel_subscription() - Cancel a subscription. * check_quotas() - Check quotas for a printer and user. + * check_rss_recipient() - Check that we do not have a duplicate RSS + * feed URI. * copy_attribute() - Copy a single attribute. * copy_attrs() - Copy attributes from one request to another. * copy_banner() - Copy a banner file to the requests directory @@ -56,33 +61,40 @@ * get_default() - Get the default destination. * get_devices() - Get the list of available devices on the * local system. + * get_document() - Get a copy of a job file. * get_job_attrs() - Get job attributes. * get_jobs() - Get a list of jobs for the specified printer. * get_notifications() - Get events for a subscription. + * get_ppd() - Get a named PPD from the local system. * get_ppds() - Get the list of PPD files on the local * system. * get_printer_attrs() - Get printer attributes. - * get_printers() - Get a list of printers. + * get_printers() - Get a list of printers or classes. * get_subscription_attrs() - Get subscription attributes. * get_subscriptions() - Get subscriptions. + * get_username() - Get the username associated with a request. * hold_job() - Hold a print job. * move_job() - Move a job to a new destination. - * ppd_add_default() - Add a PPD default choice. * ppd_parse_line() - Parse a PPD default line. * print_job() - Print a file to a printer or class. - * read_ps_line() - Read a line from a PS file... - * read_ps_job_ticket() - Reads a job ticket embedded in a PS file. + * read_job_ticket() - Read a job ticket embedded in a print file. * reject_jobs() - Reject print jobs to a printer. * release_job() - Release a held print job. + * renew_subscription() - Renew an existing subscription... * restart_job() - Restart an old print job. * save_auth_info() - Save authentication information for a job. + * save_krb5_creds() - Save Kerberos credentials for the job. * send_document() - Send a file to a printer or class. * send_http_error() - Send a HTTP error back to the IPP client. * send_ipp_status() - Send a status back to the IPP client. * set_default() - Set the default destination... * set_job_attrs() - Set job attributes. + * set_printer_attrs() - Set printer attributes. + * set_printer_defaults() - Set printer default options from a request. * start_printer() - Start a printer. * stop_printer() - Stop a printer. + * url_encode_attr() - URL-encode a string attribute. + * url_encode_string() - URL-encode a string. * user_allowed() - See if a user is allowed to print to a queue. * validate_job() - Validate printer options and destination. * validate_name() - Make sure the printer name only contains @@ -95,21 +107,26 @@ */ #include "cupsd.h" +#include #ifdef HAVE_LIBPAPER # include #endif /* HAVE_LIBPAPER */ - -/* - * PPD default choice structure... - */ - -typedef struct -{ - char option[PPD_MAX_NAME]; /* Main keyword (option name) */ - char choice[PPD_MAX_NAME]; /* Option keyword (choice name) */ -} ppd_default_t; +#ifdef __APPLE__ +# include +# include +# ifdef HAVE_MEMBERSHIP_H +# include +# endif /* HAVE_MEMBERSHIP_H */ +# ifdef HAVE_MEMBERSHIPPRIV_H +# include +# else +extern int mbr_user_name_to_uuid(const char* name, uuid_t uu); +extern int mbr_group_name_to_uuid(const char* name, uuid_t uu); +extern int mbr_check_membership_by_id(uuid_t user, gid_t group, int* ismember); +# endif /* HAVE_MEMBERSHIPPRIV_H */ +#endif /* __APPLE__ */ /* @@ -118,25 +135,42 @@ typedef struct static void accept_jobs(cupsd_client_t *con, ipp_attribute_t *uri); static void add_class(cupsd_client_t *con, ipp_attribute_t *uri); -static int add_file(cupsd_client_t *con, cupsd_job_t *job, mime_type_t *filetype, - int compression); +static int add_file(cupsd_client_t *con, cupsd_job_t *job, + mime_type_t *filetype, int compression); +static cupsd_job_t *add_job(cupsd_client_t *con, cupsd_printer_t *printer, + mime_type_t *filetype); static void add_job_state_reasons(cupsd_client_t *con, cupsd_job_t *job); static void add_job_subscriptions(cupsd_client_t *con, cupsd_job_t *job); +static void add_job_uuid(cupsd_client_t *con, cupsd_job_t *job); static void add_printer(cupsd_client_t *con, ipp_attribute_t *uri); -static void add_printer_state_reasons(cupsd_client_t *con, cupsd_printer_t *p); +static void add_printer_state_reasons(cupsd_client_t *con, + cupsd_printer_t *p); static void add_queued_job_count(cupsd_client_t *con, cupsd_printer_t *p); +#ifdef __APPLE__ +static void apple_init_profile(ppd_file_t *ppd, cups_array_t *languages, + CMDeviceProfileInfo *profile, unsigned id, + const char *name, const char *text, + const char *iccfile); +static void apple_register_profiles(cupsd_printer_t *p); +static void apple_unregister_profiles(cupsd_printer_t *p); +#endif /* __APPLE__ */ +static void apply_printer_defaults(cupsd_printer_t *printer, + cupsd_job_t *job); static void authenticate_job(cupsd_client_t *con, ipp_attribute_t *uri); static void cancel_all_jobs(cupsd_client_t *con, ipp_attribute_t *uri); static void cancel_job(cupsd_client_t *con, ipp_attribute_t *uri); static void cancel_subscription(cupsd_client_t *con, int id); +static int check_rss_recipient(const char *recipient); static int check_quotas(cupsd_client_t *con, cupsd_printer_t *p); static ipp_attribute_t *copy_attribute(ipp_t *to, ipp_attribute_t *attr, int quickcopy); static void copy_attrs(ipp_t *to, ipp_t *from, cups_array_t *ra, ipp_tag_t group, int quickcopy); -static int copy_banner(cupsd_client_t *con, cupsd_job_t *job, const char *name); +static int copy_banner(cupsd_client_t *con, cupsd_job_t *job, + const char *name); static int copy_file(const char *from, const char *to); -static int copy_model(cupsd_client_t *con, const char *from, const char *to); +static int copy_model(cupsd_client_t *con, const char *from, + const char *to); static void copy_job_attrs(cupsd_client_t *con, cupsd_job_t *job, cups_array_t *ra); @@ -152,29 +186,39 @@ static void create_subscription(cupsd_client_t *con, ipp_attribute_t *uri); static void delete_printer(cupsd_client_t *con, ipp_attribute_t *uri); static void get_default(cupsd_client_t *con); static void get_devices(cupsd_client_t *con); +static void get_document(cupsd_client_t *con, ipp_attribute_t *uri); static void get_jobs(cupsd_client_t *con, ipp_attribute_t *uri); static void get_job_attrs(cupsd_client_t *con, ipp_attribute_t *uri); -static void get_notifications(cupsd_client_t *con, int id); +static void get_notifications(cupsd_client_t *con); +static void get_ppd(cupsd_client_t *con, ipp_attribute_t *uri); static void get_ppds(cupsd_client_t *con); static void get_printers(cupsd_client_t *con, int type); static void get_printer_attrs(cupsd_client_t *con, ipp_attribute_t *uri); +static void get_printer_supported(cupsd_client_t *con, ipp_attribute_t *uri); static void get_subscription_attrs(cupsd_client_t *con, int sub_id); static void get_subscriptions(cupsd_client_t *con, ipp_attribute_t *uri); +static const char *get_username(cupsd_client_t *con); static void hold_job(cupsd_client_t *con, ipp_attribute_t *uri); +static void hold_new_jobs(cupsd_client_t *con, ipp_attribute_t *uri); static void move_job(cupsd_client_t *con, ipp_attribute_t *uri); -static int ppd_add_default(const char *option, const char *choice, - int num_defaults, ppd_default_t **defaults); static int ppd_parse_line(const char *line, char *option, int olen, char *choice, int clen); static void print_job(cupsd_client_t *con, ipp_attribute_t *uri); -static void read_ps_job_ticket(cupsd_client_t *con); +static void read_job_ticket(cupsd_client_t *con); static void reject_jobs(cupsd_client_t *con, ipp_attribute_t *uri); +static void release_held_new_jobs(cupsd_client_t *con, + ipp_attribute_t *uri); static void release_job(cupsd_client_t *con, ipp_attribute_t *uri); static void renew_subscription(cupsd_client_t *con, int sub_id); static void restart_job(cupsd_client_t *con, ipp_attribute_t *uri); -static void save_auth_info(cupsd_client_t *con, cupsd_job_t *job); +static void save_auth_info(cupsd_client_t *con, cupsd_job_t *job, + ipp_attribute_t *auth_info); +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5_H) +static void save_krb5_creds(cupsd_client_t *con, cupsd_job_t *job); +#endif /* HAVE_GSSAPI && HAVE_KRB5_H */ static void send_document(cupsd_client_t *con, ipp_attribute_t *uri); -static void send_http_error(cupsd_client_t *con, http_status_t status); +static void send_http_error(cupsd_client_t *con, http_status_t status, + cupsd_printer_t *printer); static void send_ipp_status(cupsd_client_t *con, ipp_status_t status, const char *message, ...) # ifdef __GNUC__ @@ -183,8 +227,14 @@ __attribute__ ((__format__ (__printf__, 3, 4))) ; static void set_default(cupsd_client_t *con, ipp_attribute_t *uri); static void set_job_attrs(cupsd_client_t *con, ipp_attribute_t *uri); +static void set_printer_attrs(cupsd_client_t *con, ipp_attribute_t *uri); +static void set_printer_defaults(cupsd_client_t *con, + cupsd_printer_t *printer); static void start_printer(cupsd_client_t *con, ipp_attribute_t *uri); static void stop_printer(cupsd_client_t *con, ipp_attribute_t *uri); +static void url_encode_attr(ipp_attribute_t *attr, char *buffer, + int bufsize); +static char *url_encode_string(const char *s, char *buffer, int bufsize); static int user_allowed(cupsd_printer_t *p, const char *username); static void validate_job(cupsd_client_t *con, ipp_attribute_t *uri); static int validate_name(const char *name); @@ -194,7 +244,7 @@ static int validate_user(cupsd_job_t *job, cupsd_client_t *con, /* - * 'cupsdProcessIPPRequest()' - Process an incoming IPP request... + * 'cupsdProcessIPPRequest()' - Process an incoming IPP request. */ int /* O - 1 on success, 0 on failure */ @@ -205,7 +255,7 @@ cupsdProcessIPPRequest( ipp_attribute_t *attr; /* Current attribute */ ipp_attribute_t *charset; /* Character set attribute */ ipp_attribute_t *language; /* Language attribute */ - ipp_attribute_t *uri; /* Printer URI attribute */ + ipp_attribute_t *uri = NULL; /* Printer or job URI attribute */ ipp_attribute_t *username; /* requesting-user-name attr */ int sub_id; /* Subscription ID */ @@ -220,18 +270,22 @@ cupsdProcessIPPRequest( con->response = ippNew(); - con->response->request.status.version[0] = con->request->request.op.version[0]; - con->response->request.status.version[1] = con->request->request.op.version[1]; - con->response->request.status.request_id = con->request->request.op.request_id; + con->response->request.status.version[0] = + con->request->request.op.version[0]; + con->response->request.status.version[1] = + con->request->request.op.version[1]; + con->response->request.status.request_id = + con->request->request.op.request_id; /* * Then validate the request header and required attributes... */ - - if (con->request->request.any.version[0] != 1) + + if (con->request->request.any.version[0] != 1 && + con->request->request.any.version[0] != 2) { /* - * Return an error, since we only support IPP 1.x. + * Return an error, since we only support IPP 1.x and 2.x. */ cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL, @@ -244,7 +298,21 @@ cupsdProcessIPPRequest( _("Bad request version number %d.%d!"), con->request->request.any.version[0], con->request->request.any.version[1]); - } + } + else if (con->request->request.any.request_id < 1) + { + /* + * Return an error, since request IDs must be between 1 and 2^31-1 + */ + + cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL, + "%04X %s Bad request ID %d", + IPP_BAD_REQUEST, con->http.hostname, + con->request->request.any.request_id); + + send_ipp_status(con, IPP_BAD_REQUEST, _("Bad request ID %d!"), + con->request->request.any.request_id); + } else if (!con->request->attrs) { cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL, @@ -292,7 +360,8 @@ cupsdProcessIPPRequest( */ attr = con->request->attrs; - if (attr && !strcmp(attr->name, "attributes-charset") && + if (attr && attr->name && + !strcmp(attr->name, "attributes-charset") && (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_CHARSET) charset = attr; else @@ -301,7 +370,8 @@ cupsdProcessIPPRequest( if (attr) attr = attr->next; - if (attr && !strcmp(attr->name, "attributes-natural-language") && + if (attr && attr->name && + !strcmp(attr->name, "attributes-natural-language") && (attr->value_tag & IPP_TAG_MASK) == IPP_TAG_LANGUAGE) language = attr; else @@ -313,12 +383,15 @@ cupsdProcessIPPRequest( else if ((attr = ippFindAttribute(con->request, "job-uri", IPP_TAG_URI)) != NULL) uri = attr; + else if (con->request->request.op.operation_id == CUPS_GET_PPD) + uri = ippFindAttribute(con->request, "ppd-name", IPP_TAG_NAME); else uri = NULL; if (charset) ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET, - "attributes-charset", NULL, charset->values[0].string.text); + "attributes-charset", NULL, + charset->values[0].string.text); else ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_CHARSET, "attributes-charset", NULL, DefaultCharset); @@ -331,13 +404,31 @@ cupsdProcessIPPRequest( ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, DefaultLanguage); - if (!charset || !language || - (!uri && - con->request->request.op.operation_id != CUPS_GET_DEFAULT && - con->request->request.op.operation_id != CUPS_GET_PRINTERS && - con->request->request.op.operation_id != CUPS_GET_CLASSES && - con->request->request.op.operation_id != CUPS_GET_DEVICES && - con->request->request.op.operation_id != CUPS_GET_PPDS)) + if (charset && + strcasecmp(charset->values[0].string.text, "us-ascii") && + strcasecmp(charset->values[0].string.text, "utf-8")) + { + /* + * Bad character set... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, "Unsupported character set \"%s\"!", + charset->values[0].string.text); + cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL, + "%04X %s Unsupported attributes-charset value \"%s\"", + IPP_CHARSET, con->http.hostname, + charset->values[0].string.text); + send_ipp_status(con, IPP_BAD_REQUEST, + _("Unsupported character set \"%s\"!"), + charset->values[0].string.text); + } + else if (!charset || !language || + (!uri && + con->request->request.op.operation_id != CUPS_GET_DEFAULT && + con->request->request.op.operation_id != CUPS_GET_PRINTERS && + con->request->request.op.operation_id != CUPS_GET_CLASSES && + con->request->request.op.operation_id != CUPS_GET_DEVICES && + con->request->request.op.operation_id != CUPS_GET_PPDS)) { /* * Return an error, since attributes-charset, @@ -368,17 +459,18 @@ cupsdProcessIPPRequest( if (!uri) { cupsdLogMessage(CUPSD_LOG_ERROR, - "Missing printer-uri or job-uri attribute!"); + "Missing printer-uri, job-uri, or ppd-name " + "attribute!"); cupsdAddEvent(CUPSD_EVENT_SERVER_AUDIT, NULL, NULL, - "%04X %s Missing printer-uri or job-uri attribute", - IPP_BAD_REQUEST, con->http.hostname); + "%04X %s Missing printer-uri, job-uri, or ppd-name " + "attribute", IPP_BAD_REQUEST, con->http.hostname); } cupsdLogMessage(CUPSD_LOG_DEBUG, "Request attributes follow..."); for (attr = con->request->attrs; attr; attr = attr->next) - cupsdLogMessage(CUPSD_LOG_DEBUG, + cupsdLogMessage(CUPSD_LOG_DEBUG, "attr \"%s\": group_tag = %x, value_tag = %x", attr->name ? attr->name : "(null)", attr->group_tag, attr->value_tag); @@ -410,7 +502,8 @@ cupsdProcessIPPRequest( * Remote unauthenticated user masquerading as local root... */ - cupsdSetString(&(username->values[0].string.text), RemoteRoot); + _cupsStrFree(username->values[0].string.text); + username->values[0].string.text = _cupsStrAlloc(RemoteRoot); } } @@ -425,9 +518,12 @@ cupsdProcessIPPRequest( */ if (uri) - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "cupsdProcessIPPRequest: URI=\"%s\"", - uri->values[0].string.text); + cupsdLogMessage(CUPSD_LOG_DEBUG, "%s %s", + ippOpString(con->request->request.op.operation_id), + uri->values[0].string.text); + else + cupsdLogMessage(CUPSD_LOG_DEBUG, "%s", + ippOpString(con->request->request.op.operation_id)); switch (con->request->request.op.operation_id) { @@ -463,6 +559,10 @@ cupsdProcessIPPRequest( get_printer_attrs(con, uri); break; + case IPP_GET_PRINTER_SUPPORTED_VALUES : + get_printer_supported(con, uri); + break; + case IPP_HOLD_JOB : hold_job(con, uri); break; @@ -491,6 +591,18 @@ cupsdProcessIPPRequest( set_job_attrs(con, uri); break; + case IPP_SET_PRINTER_ATTRIBUTES : + set_printer_attrs(con, uri); + break; + + case IPP_HOLD_NEW_JOBS : + hold_new_jobs(con, uri); + break; + + case IPP_RELEASE_HELD_NEW_JOBS : + release_held_new_jobs(con, uri); + break; + case CUPS_GET_DEFAULT : get_default(con); break; @@ -537,6 +649,14 @@ cupsdProcessIPPRequest( get_devices(con); break; + case CUPS_GET_DOCUMENT : + get_document(con, uri); + break; + + case CUPS_GET_PPD : + get_ppd(con, uri); + break; + case CUPS_GET_PPDS : get_ppds(con); break; @@ -571,7 +691,7 @@ cupsdProcessIPPRequest( break; case IPP_GET_NOTIFICATIONS : - get_notifications(con, sub_id); + get_notifications(con); break; default : @@ -583,7 +703,8 @@ cupsdProcessIPPRequest( send_ipp_status(con, IPP_OPERATION_NOT_SUPPORTED, _("%s not supported!"), - ippOpString(con->request->request.op.operation_id)); + ippOpString( + con->request->request.op.operation_id)); break; } } @@ -596,38 +717,77 @@ cupsdProcessIPPRequest( * Sending data from the scheduler... */ - cupsdLogMessage(CUPSD_LOG_DEBUG, - "cupsdProcessIPPRequest: %d status_code=%x (%s)", - con->http.fd, con->response->request.status.status_code, - ippErrorString(con->response->request.status.status_code)); + cupsdLogMessage(con->response->request.status.status_code + >= IPP_BAD_REQUEST && + con->response->request.status.status_code + != IPP_NOT_FOUND ? CUPSD_LOG_ERROR : CUPSD_LOG_DEBUG, + "Returning IPP %s for %s (%s) from %s", + ippErrorString(con->response->request.status.status_code), + ippOpString(con->request->request.op.operation_id), + uri ? uri->values[0].string.text : "no URI", + con->http.hostname); + + if (LogLevel == CUPSD_LOG_DEBUG2) + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cupsdProcessIPPRequest: ippLength(response)=%ld", + (long)ippLength(con->response)); - if (cupsdSendHeader(con, HTTP_OK, "application/ipp")) + if (cupsdSendHeader(con, HTTP_OK, "application/ipp", CUPSD_AUTH_NONE)) { +#ifdef CUPSD_USE_CHUNKING + /* + * Because older versions of CUPS (1.1.17 and older) and some IPP + * clients do not implement chunking properly, we cannot use + * chunking by default. This may become the default in future + * CUPS releases, or we might add a configuration directive for + * it. + */ + if (con->http.version == HTTP_1_1) { - con->http.data_encoding = HTTP_ENCODE_CHUNKED; + if (httpPrintf(HTTP(con), "Transfer-Encoding: chunked\r\n\r\n") < 0) + return (0); + + if (cupsdFlushHeader(con) < 0) + return (0); - httpPrintf(HTTP(con), "Transfer-Encoding: chunked\r\n\r\n"); + con->http.data_encoding = HTTP_ENCODE_CHUNKED; } else +#endif /* CUPSD_USE_CHUNKING */ { + size_t length; /* Length of response */ + + + length = ippLength(con->response); + + if (con->file >= 0 && !con->pipe_pid) + { + struct stat fileinfo; /* File information */ + + + if (!fstat(con->file, &fileinfo)) + length += fileinfo.st_size; + } + + if (httpPrintf(HTTP(con), "Content-Length: " CUPS_LLFMT "\r\n\r\n", + CUPS_LLCAST length) < 0) + return (0); + + if (cupsdFlushHeader(con) < 0) + return (0); + con->http.data_encoding = HTTP_ENCODE_LENGTH; - con->http.data_remaining = ippLength(con->response); + con->http.data_remaining = length; - if (con->http.data_remaining < INT_MAX) + if (con->http.data_remaining <= INT_MAX) con->http._data_remaining = con->http.data_remaining; else con->http._data_remaining = INT_MAX; - - httpPrintf(HTTP(con), "Content-Length: " CUPS_LLFMT "\r\n\r\n", - CUPS_LLCAST con->http.data_remaining); } - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "cupsdProcessIPPRequest: Adding fd %d to OutputSet...", - con->http.fd); - - FD_SET(con->http.fd, OutputSet); + cupsdAddSelect(con->http.fd, (cupsd_selfunc_t)cupsdReadClient, + (cupsd_selfunc_t)cupsdWriteClient, con); /* * Tell the caller the response header was sent successfully... @@ -656,6 +816,48 @@ cupsdProcessIPPRequest( } +/* + * 'cupsdTimeoutJob()' - Timeout a job waiting on job files. + */ + +int /* O - 0 on success, -1 on error */ +cupsdTimeoutJob(cupsd_job_t *job) /* I - Job to timeout */ +{ + cupsd_printer_t *printer; /* Destination printer or class */ + ipp_attribute_t *attr; /* job-sheets attribute */ + int kbytes; /* Kilobytes in banner */ + + + job->pending_timeout = 0; + + /* + * See if we need to add the ending sheet... + */ + + printer = cupsdFindDest(job->dest); + attr = ippFindAttribute(job->attrs, "job-sheets", IPP_TAG_NAME); + + if (printer && + !(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) && + attr && attr->num_values > 1) + { + /* + * Yes... + */ + + cupsdLogJob(job, CUPSD_LOG_INFO, "Adding end banner page \"%s\".", + attr->values[1].string.text); + + if ((kbytes = copy_banner(NULL, job, attr->values[1].string.text)) < 0) + return (-1); + + cupsdUpdateQuota(printer, job->username, 0, kbytes); + } + + return (0); +} + + /* * 'accept_jobs()' - Accept print jobs to a printer. */ @@ -665,13 +867,7 @@ accept_jobs(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer or class URI */ { http_status_t status; /* Policy status */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - const char *name; /* Printer name */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cupsd_printer_t *printer; /* Printer data */ @@ -682,11 +878,7 @@ accept_jobs(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((name = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -703,7 +895,7 @@ accept_jobs(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } @@ -717,12 +909,20 @@ accept_jobs(cupsd_client_t *con, /* I - Client connection */ cupsdAddPrinterHistory(printer); if (dtype & CUPS_PRINTER_CLASS) - cupsdSaveAllClasses(); + { + cupsdMarkDirty(CUPSD_DIRTY_CLASSES); + + cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" now accepting jobs (\"%s\").", + printer->name, get_username(con)); + } else - cupsdSaveAllPrinters(); + { + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); - cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" now accepting jobs (\"%s\").", name, - con->username); + cupsdLogMessage(CUPSD_LOG_INFO, + "Printer \"%s\" now accepting jobs (\"%s\").", + printer->name, get_username(con)); + } /* * Everything was ok, so return OK status... @@ -742,7 +942,7 @@ add_class(cupsd_client_t *con, /* I - Client connection */ { http_status_t status; /* Policy status */ int i; /* Looping var */ - char method[HTTP_MAX_URI], /* Method portion of URI */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ @@ -750,9 +950,10 @@ add_class(cupsd_client_t *con, /* I - Client connection */ cupsd_printer_t *pclass, /* Class */ *member; /* Member printer/class */ cups_ptype_t dtype; /* Destination type */ - const char *dest; /* Printer or class name */ ipp_attribute_t *attr; /* Printer attribute */ int modify; /* Non-zero if we just modified */ + char newname[IPP_MAX_NAME]; /* New class name */ + int need_restart_job; /* Need to restart job? */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_class(%p[%d], %s)", con, @@ -762,8 +963,8 @@ add_class(cupsd_client_t *con, /* I - Client connection */ * Do we have a valid URI? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); @@ -795,16 +996,6 @@ add_class(cupsd_client_t *con, /* I - Client connection */ return; } - /* - * Check policy... - */ - - if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) - { - send_http_error(con, status); - return; - } - /* * See if the class already exists; if not, create a new class... */ @@ -816,7 +1007,7 @@ add_class(cupsd_client_t *con, /* I - Client connection */ */ if ((pclass = cupsdFindPrinter(resource + 9)) != NULL && - !(pclass->type & CUPS_PRINTER_REMOTE)) + !(pclass->type & CUPS_PRINTER_DISCOVERED)) { /* * Yes, return an error... @@ -829,23 +1020,35 @@ add_class(cupsd_client_t *con, /* I - Client connection */ } /* - * No, add the pclass... + * No, check the default policy and then add the class... */ + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, NULL); + return; + } + pclass = cupsdAddClass(resource + 9); modify = 0; } else if (pclass->type & CUPS_PRINTER_IMPLICIT) { /* - * Rename the implicit class to "AnyClass" or remove it... + * Check the default policy, then rename the implicit class to "AnyClass" + * or remove it... */ + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, NULL); + return; + } + if (ImplicitAnyClasses) { - cupsArrayRemove(Printers, pclass); - cupsdSetStringf(&pclass->name, "Any%s", resource + 9); - cupsArrayAdd(Printers, pclass); + snprintf(newname, sizeof(newname), "Any%s", resource + 9); + cupsdRenamePrinter(pclass, newname); } else cupsdDeletePrinter(pclass, 1); @@ -857,17 +1060,20 @@ add_class(cupsd_client_t *con, /* I - Client connection */ pclass = cupsdAddClass(resource + 9); modify = 0; } - else if (pclass->type & CUPS_PRINTER_REMOTE) + else if (pclass->type & CUPS_PRINTER_DISCOVERED) { /* - * Rename the remote class to "Class"... + * Check the default policy, then rename the remote class to "Class"... */ - cupsdDeletePrinterFilters(pclass); - cupsArrayRemove(Printers, pclass); - cupsdSetStringf(&pclass->name, "%s@%s", resource + 9, pclass->hostname); - cupsdSetPrinterAttrs(pclass); - cupsArrayAdd(Printers, pclass); + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, NULL); + return; + } + + snprintf(newname, sizeof(newname), "%s@%s", resource + 9, pclass->hostname); + cupsdRenamePrinter(pclass, newname); /* * Add the class as a new local class... @@ -876,6 +1082,12 @@ add_class(cupsd_client_t *con, /* I - Client connection */ pclass = cupsdAddClass(resource + 9); modify = 0; } + else if ((status = cupsdCheckPolicy(pclass->op_policy_ptr, con, + NULL)) != HTTP_OK) + { + send_http_error(con, status, pclass); + return; + } else modify = 1; @@ -883,6 +1095,8 @@ add_class(cupsd_client_t *con, /* I - Client connection */ * Look for attributes and copy them over as needed... */ + need_restart_job = 0; + if ((attr = ippFindAttribute(con->request, "printer-location", IPP_TAG_TEXT)) != NULL) cupsdSetString(&pclass->location, attr->values[0].string.text); @@ -894,8 +1108,9 @@ add_class(cupsd_client_t *con, /* I - Client connection */ if ((attr = ippFindAttribute(con->request, "printer-is-accepting-jobs", IPP_TAG_BOOLEAN)) != NULL) { - cupsdLogMessage(CUPSD_LOG_INFO, "Setting %s printer-is-accepting-jobs to %d (was %d.)", - pclass->name, attr->values[0].boolean, pclass->accepting); + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s printer-is-accepting-jobs to %d (was %d.)", + pclass->name, attr->values[0].boolean, pclass->accepting); pclass->accepting = attr->values[0].boolean; cupsdAddPrinterHistory(pclass); @@ -905,7 +1120,7 @@ add_class(cupsd_client_t *con, /* I - Client connection */ IPP_TAG_BOOLEAN)) != NULL) { if (pclass->shared && !attr->values[0].boolean) - cupsdSendBrowseDelete(pclass); + cupsdDeregisterPrinter(pclass, 1); cupsdLogMessage(CUPSD_LOG_INFO, "Setting %s printer-is-shared to %d (was %d.)", @@ -926,13 +1141,16 @@ add_class(cupsd_client_t *con, /* I - Client connection */ return; } - cupsdLogMessage(CUPSD_LOG_INFO, "Setting %s printer-state to %d (was %d.)", pclass->name, - attr->values[0].integer, pclass->state); + cupsdLogMessage(CUPSD_LOG_INFO, "Setting %s printer-state to %d (was %d.)", + pclass->name, attr->values[0].integer, pclass->state); if (attr->values[0].integer == IPP_PRINTER_STOPPED) cupsdStopPrinter(pclass, 0); else + { cupsdSetPrinterState(pclass, (ipp_pstate_t)(attr->values[0].integer), 0); + need_restart_job = 1; + } } if ((attr = ippFindAttribute(con->request, "printer-state-message", IPP_TAG_TEXT)) != NULL) @@ -941,107 +1159,6 @@ add_class(cupsd_client_t *con, /* I - Client connection */ sizeof(pclass->state_message)); cupsdAddPrinterHistory(pclass); } - if ((attr = ippFindAttribute(con->request, "job-sheets-default", - IPP_TAG_ZERO)) != NULL && - !Classification) - { - cupsdSetString(&pclass->job_sheets[0], attr->values[0].string.text); - if (attr->num_values > 1) - cupsdSetString(&pclass->job_sheets[1], attr->values[1].string.text); - else - cupsdSetString(&pclass->job_sheets[1], "none"); - } - if ((attr = ippFindAttribute(con->request, "requesting-user-name-allowed", - IPP_TAG_ZERO)) != NULL) - { - cupsdFreePrinterUsers(pclass); - - pclass->deny_users = 0; - if (attr->value_tag == IPP_TAG_NAME && - (attr->num_values > 1 || - strcmp(attr->values[0].string.text, "all"))) - for (i = 0; i < attr->num_values; i ++) - cupsdAddPrinterUser(pclass, attr->values[i].string.text); - } - else if ((attr = ippFindAttribute(con->request, "requesting-user-name-denied", - IPP_TAG_ZERO)) != NULL) - { - cupsdFreePrinterUsers(pclass); - - pclass->deny_users = 1; - if (attr->value_tag == IPP_TAG_NAME && - (attr->num_values > 1 || - strcmp(attr->values[0].string.text, "none"))) - for (i = 0; i < attr->num_values; i ++) - cupsdAddPrinterUser(pclass, attr->values[i].string.text); - } - if ((attr = ippFindAttribute(con->request, "job-quota-period", - IPP_TAG_INTEGER)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "add_class: Setting job-quota-period to %d...", - attr->values[0].integer); - cupsdFreeQuotas(pclass); - pclass->quota_period = attr->values[0].integer; - } - if ((attr = ippFindAttribute(con->request, "job-k-limit", - IPP_TAG_INTEGER)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "add_class: Setting job-k-limit to %d...", - attr->values[0].integer); - cupsdFreeQuotas(pclass); - pclass->k_limit = attr->values[0].integer; - } - if ((attr = ippFindAttribute(con->request, "job-page-limit", - IPP_TAG_INTEGER)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "add_class: Setting job-page-limit to %d...", - attr->values[0].integer); - cupsdFreeQuotas(pclass); - pclass->page_limit = attr->values[0].integer; - } - if ((attr = ippFindAttribute(con->request, "printer-op-policy", - IPP_TAG_NAME)) != NULL) - { - cupsd_policy_t *p; /* Policy */ - - - if ((p = cupsdFindPolicy(attr->values[0].string.text)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "add_class: Setting printer-op-policy to \"%s\"...", - attr->values[0].string.text); - cupsdSetString(&pclass->op_policy, attr->values[0].string.text); - pclass->op_policy_ptr = p; - } - else - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("add_class: Unknown printer-op-policy \"%s\"."), - attr->values[0].string.text); - return; - } - } - if ((attr = ippFindAttribute(con->request, "printer-error-policy", - IPP_TAG_NAME)) != NULL) - { - if (strcmp(attr->values[0].string.text, "abort-job") && - strcmp(attr->values[0].string.text, "retry-job") && - strcmp(attr->values[0].string.text, "stop-printer")) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("add_class: Unknown printer-error-policy \"%s\"."), - attr->values[0].string.text); - return; - } - - cupsdLogMessage(CUPSD_LOG_DEBUG, - "add_class: Setting printer-error-policy to \"%s\"...", - attr->values[0].string.text); - cupsdSetString(&pclass->error_policy, attr->values[0].string.text); - } if ((attr = ippFindAttribute(con->request, "member-uris", IPP_TAG_URI)) != NULL) { @@ -1049,6 +1166,8 @@ add_class(cupsd_client_t *con, /* I - Client connection */ * Clear the printer array as needed... */ + need_restart_job = 1; + if (pclass->num_printers > 0) { free(pclass->printers); @@ -1065,11 +1184,7 @@ add_class(cupsd_client_t *con, /* I - Client connection */ * Search for the printer or class URI... */ - httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[i].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &member)) == NULL) + if (!cupsdValidateDest(attr->values[i].string.text, &dtype, &member)) { /* * Bad URI... @@ -1088,24 +1203,39 @@ add_class(cupsd_client_t *con, /* I - Client connection */ } } + set_printer_defaults(con, pclass); + + if ((attr = ippFindAttribute(con->request, "auth-info-required", + IPP_TAG_KEYWORD)) != NULL) + cupsdSetAuthInfoRequired(pclass, NULL, attr); + /* * Update the printer class attributes and return... */ cupsdSetPrinterAttrs(pclass); - cupsdSaveAllClasses(); - cupsdCheckJobs(); + cupsdMarkDirty(CUPSD_DIRTY_CLASSES); + + if (need_restart_job && pclass->job) + { + /* + * Reset the current job to a "pending" status... + */ + + cupsdSetJobState(pclass->job, IPP_JOB_PENDING, CUPSD_JOB_FORCE, + "Job restarted because the class was modified."); + } - cupsdWritePrintcap(); + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP); if (modify) { cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED, pclass, NULL, "Class \"%s\" modified by \"%s\".", pclass->name, - con->username); + get_username(con)); cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" modified by \"%s\".", - pclass->name, con->username); + pclass->name, get_username(con)); } else { @@ -1113,10 +1243,10 @@ add_class(cupsd_client_t *con, /* I - Client connection */ cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, pclass, NULL, "New class \"%s\" added by \"%s\".", pclass->name, - con->username); + get_username(con)); cupsdLogMessage(CUPSD_LOG_INFO, "New class \"%s\" added by \"%s\".", - pclass->name, con->username); + pclass->name, get_username(con)); } con->response->request.status.status_code = IPP_OK; @@ -1138,9 +1268,9 @@ add_file(cupsd_client_t *con, /* I - Connection to client */ cupsdLogMessage(CUPSD_LOG_DEBUG2, - "add_file(con=%p[%d], job=%d, filetype=%s/%s, compression=%d)", - con, con->http.fd, job->id, filetype->super, filetype->type, - compression); + "add_file(con=%p[%d], job=%d, filetype=%s/%s, " + "compression=%d)", con, con ? con->http.fd : -1, job->id, + filetype->super, filetype->type, compression); /* * Add the file to the job... @@ -1162,10 +1292,13 @@ add_file(cupsd_client_t *con, /* I - Connection to client */ if (!compressions || !filetypes) { - cupsdCancelJob(job, 1); + cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE, + "Job aborted because the scheduler ran out of memory."); + + if (con) + send_ipp_status(con, IPP_INTERNAL_ERROR, + _("Unable to allocate memory for file types!")); - send_ipp_status(con, IPP_INTERNAL_ERROR, - _("Unable to allocate memory for file types!")); return (-1); } @@ -1176,3196 +1309,4465 @@ add_file(cupsd_client_t *con, /* I - Connection to client */ job->num_files ++; + job->dirty = 1; + cupsdMarkDirty(CUPSD_DIRTY_JOBS); + return (0); } /* - * 'add_job_state_reasons()' - Add the "job-state-reasons" attribute based - * upon the job and printer state... + * 'add_job()' - Add a job to a print queue. */ -static void -add_job_state_reasons( - cupsd_client_t *con, /* I - Client connection */ - cupsd_job_t *job) /* I - Job info */ +static cupsd_job_t * /* O - Job object */ +add_job(cupsd_client_t *con, /* I - Client connection */ + cupsd_printer_t *printer, /* I - Destination printer */ + mime_type_t *filetype) /* I - First print file type, if any */ { - cupsd_printer_t *dest; /* Destination printer */ + http_status_t status; /* Policy status */ + ipp_attribute_t *attr, /* Current attribute */ + *auth_info; /* auth-info attribute */ + const char *val; /* Default option value */ + int priority; /* Job priority */ + char *title; /* Job name/title */ + cupsd_job_t *job; /* Current job */ + char job_uri[HTTP_MAX_URI]; /* Job URI */ + int kbytes; /* Size of print file */ + int i; /* Looping var */ + int lowerpagerange; /* Page range bound */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_job_state_reasons(%p[%d], %d)", - con, con->http.fd, job ? job->id : 0); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_job(%p[%d], %p(%s), %p(%s/%s))", + con, con->http.fd, printer, printer->name, + filetype, filetype ? filetype->super : "none", + filetype ? filetype->type : "none"); - switch (job ? job->state->values[0].integer : IPP_JOB_CANCELLED) - { - case IPP_JOB_PENDING : - if (job->dtype & CUPS_PRINTER_CLASS) - dest = cupsdFindClass(job->dest); - else - dest = cupsdFindPrinter(job->dest); + /* + * Check remote printing to non-shared printer... + */ - if (dest && dest->state == IPP_PRINTER_STOPPED) - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "printer-stopped"); - else - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "none"); - break; + if (!printer->shared && + strcasecmp(con->http.hostname, "localhost") && + strcasecmp(con->http.hostname, ServerName)) + { + send_ipp_status(con, IPP_NOT_AUTHORIZED, + _("The printer or class is not shared!")); + return (NULL); + } - case IPP_JOB_HELD : - if (ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD) != NULL || - ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_NAME) != NULL) - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "job-hold-until-specified"); - else - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "job-incoming"); - break; + /* + * Check policy... + */ - case IPP_JOB_PROCESSING : - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "job-printing"); - break; + auth_info = ippFindAttribute(con->request, "auth-info", IPP_TAG_TEXT); - case IPP_JOB_STOPPED : - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "job-stopped"); - break; - - case IPP_JOB_CANCELLED : - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "job-canceled-by-user"); - break; - - case IPP_JOB_ABORTED : - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "aborted-by-system"); - break; - - case IPP_JOB_COMPLETED : - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-state-reasons", NULL, "job-completed-successfully"); - break; + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return (NULL); } -} - - -/* - * 'add_job_subscriptions()' - Add any subcriptions for a job. - */ - -static void -add_job_subscriptions( - cupsd_client_t *con, /* I - Client connection */ - cupsd_job_t *job) /* I - Newly created job */ -{ - int i; /* Looping var */ - ipp_attribute_t *prev, /* Previous attribute */ - *next, /* Next attribute */ - *attr; /* Current attribute */ - cupsd_subscription_t *sub; /* Subscription object */ - const char *recipient, /* notify-recipient-uri */ - *pullmethod; /* notify-pull-method */ - ipp_attribute_t *user_data; /* notify-user-data */ - int interval; /* notify-time-interval */ - unsigned mask; /* notify-events */ + else if (printer->num_auth_info_required == 1 && + !strcmp(printer->auth_info_required[0], "negotiate") && + !con->username[0]) + { + send_http_error(con, HTTP_UNAUTHORIZED, printer); + return (NULL); + } +#ifdef HAVE_SSL + else if (auth_info && !con->http.tls && + !httpAddrLocalhost(con->http.hostaddr)) + { + /* + * Require encryption of auth-info over non-local connections... + */ + send_http_error(con, HTTP_UPGRADE_REQUIRED, printer); + return (NULL); + } +#endif /* HAVE_SSL */ /* - * Find the first subscription group attribute; return if we have - * none... + * See if the printer is accepting jobs... */ - for (attr = job->attrs->attrs, prev = NULL; - attr; - prev = attr, attr = attr->next) - if (attr->group_tag == IPP_TAG_SUBSCRIPTION) - break; - - if (!attr) - return; + if (!printer->accepting) + { + send_ipp_status(con, IPP_NOT_ACCEPTING, + _("Destination \"%s\" is not accepting jobs."), + printer->name); + return (NULL); + } /* - * Process the subscription attributes in the request... + * Validate job template attributes; for now just document-format, + * copies, number-up, and page-ranges... */ - while (attr) + if (filetype && printer->filetypes && + !cupsArrayFind(printer->filetypes, filetype)) { - recipient = NULL; - pullmethod = NULL; - user_data = NULL; - interval = 0; - mask = CUPSD_EVENT_NONE; - - while (attr && attr->group_tag != IPP_TAG_ZERO) - { - if (!strcmp(attr->name, "notify-recipient") && - attr->value_tag == IPP_TAG_URI) - recipient = attr->values[0].string.text; - else if (!strcmp(attr->name, "notify-pull-method") && - attr->value_tag == IPP_TAG_KEYWORD) - pullmethod = attr->values[0].string.text; - else if (!strcmp(attr->name, "notify-charset") && - attr->value_tag == IPP_TAG_CHARSET && - strcmp(attr->values[0].string.text, "us-ascii") && - strcmp(attr->values[0].string.text, "utf-8")) - { - send_ipp_status(con, IPP_CHARSET, - _("Character set \"%s\" not supported!"), - attr->values[0].string.text); - return; - } - else if (!strcmp(attr->name, "notify-natural-language") && - (attr->value_tag != IPP_TAG_LANGUAGE || - strcmp(attr->values[0].string.text, DefaultLanguage))) - { - send_ipp_status(con, IPP_CHARSET, - _("Language \"%s\" not supported!"), - attr->values[0].string.text); - return; - } - else if (!strcmp(attr->name, "notify-user-data") && - attr->value_tag == IPP_TAG_STRING) - { - if (attr->num_values > 1 || attr->values[0].unknown.length > 63) - { - send_ipp_status(con, IPP_REQUEST_VALUE, - _("The notify-user-data value is too large " - "(%d > 63 octets)!"), - attr->values[0].unknown.length); - return; - } - - user_data = attr; - } - else if (!strcmp(attr->name, "notify-events") && - attr->value_tag == IPP_TAG_KEYWORD) - { - for (i = 0; i < attr->num_values; i ++) - mask |= cupsdEventValue(attr->values[i].string.text); - } - else if (!strcmp(attr->name, "notify-lease-duration")) - { - send_ipp_status(con, IPP_BAD_REQUEST, - _("The notify-lease-duration attribute cannot be " - "used with job subscriptions.")); - return; - } - else if (!strcmp(attr->name, "notify-time-interval") && - attr->value_tag == IPP_TAG_INTEGER) - interval = attr->values[0].integer; - - attr = attr->next; - } + char mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2]; + /* MIME media type string */ - if (!recipient && !pullmethod) - break; - if (mask == CUPSD_EVENT_NONE) - mask = CUPSD_EVENT_JOB_COMPLETED; + snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super, + filetype->type); - sub = cupsdAddSubscription(mask, cupsdFindDest(job->dest), job, recipient, - 0); + send_ipp_status(con, IPP_DOCUMENT_FORMAT, + _("Unsupported format \'%s\'!"), mimetype); - sub->interval = interval; + ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE, + "document-format", NULL, mimetype); - cupsdSetString(&sub->owner, job->username); + return (NULL); + } - if (user_data) + if ((attr = ippFindAttribute(con->request, "copies", + IPP_TAG_INTEGER)) != NULL) + { + if (attr->values[0].integer < 1 || attr->values[0].integer > MaxCopies) { - sub->user_data_len = user_data->values[0].unknown.length; - memcpy(sub->user_data, user_data->values[0].unknown.data, - sub->user_data_len); + send_ipp_status(con, IPP_ATTRIBUTES, _("Bad copies value %d."), + attr->values[0].integer); + ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER, + "copies", attr->values[0].integer); + return (NULL); } - - ippAddSeparator(con->response); - ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, - "notify-subscription-id", sub->id); - - if (attr) - attr = attr->next; } - cupsdSaveAllSubscriptions(); - - /* - * Remove all of the subscription attributes from the job request... - */ - - for (attr = job->attrs->attrs, prev = NULL; attr; attr = next) + if ((attr = ippFindAttribute(con->request, "job-sheets", + IPP_TAG_ZERO)) != NULL) { - next = attr->next; + if (attr->value_tag != IPP_TAG_KEYWORD && + attr->value_tag != IPP_TAG_NAME) + { + send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-sheets value type!")); + return (NULL); + } - if (attr->group_tag == IPP_TAG_SUBSCRIPTION || - attr->group_tag == IPP_TAG_ZERO) + if (attr->num_values > 2) { - /* - * Free and remove this attribute... - */ + send_ipp_status(con, IPP_BAD_REQUEST, + _("Too many job-sheets values (%d > 2)!"), + attr->num_values); + return (NULL); + } - _ipp_free_attr(attr); + for (i = 0; i < attr->num_values; i ++) + if (strcmp(attr->values[i].string.text, "none") && + !cupsdFindBanner(attr->values[i].string.text)) + { + send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-sheets value \"%s\"!"), + attr->values[i].string.text); + return (NULL); + } + } - if (prev) - prev->next = next; - else - job->attrs->attrs = next; + if ((attr = ippFindAttribute(con->request, "number-up", + IPP_TAG_INTEGER)) != NULL) + { + if (attr->values[0].integer != 1 && + attr->values[0].integer != 2 && + attr->values[0].integer != 4 && + attr->values[0].integer != 6 && + attr->values[0].integer != 9 && + attr->values[0].integer != 16) + { + send_ipp_status(con, IPP_ATTRIBUTES, _("Bad number-up value %d."), + attr->values[0].integer); + ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER, + "number-up", attr->values[0].integer); + return (NULL); } - else - prev = attr; } - job->attrs->last = prev; - job->attrs->current = prev; -} - - -/* - * 'add_printer()' - Add a printer to the system. - */ - -static void -add_printer(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - URI of printer */ -{ - http_status_t status; /* Policy status */ - int i; /* Looping var */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - cupsd_printer_t *printer; /* Printer/class */ - ipp_attribute_t *attr; /* Printer attribute */ - cups_file_t *fp; /* Script/PPD file */ - char line[1024]; /* Line from file... */ - char srcfile[1024], /* Source Script/PPD file */ - dstfile[1024]; /* Destination Script/PPD file */ - int modify; /* Non-zero if we are modifying */ - + if ((attr = ippFindAttribute(con->request, "page-ranges", + IPP_TAG_RANGE)) != NULL) + { + for (i = 0, lowerpagerange = 1; i < attr->num_values; i ++) + { + if (attr->values[i].range.lower < lowerpagerange || + attr->values[i].range.lower > attr->values[i].range.upper) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Bad page-ranges values %d-%d."), + attr->values[i].range.lower, + attr->values[i].range.upper); + return (NULL); + } - cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_printer(%p[%d], %s)", con, - con->http.fd, uri->values[0].string.text); + lowerpagerange = attr->values[i].range.upper + 1; + } + } /* - * Do we have a valid URI? + * Make sure we aren't over our limit... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); + if (MaxJobs && cupsArrayCount(Jobs) >= MaxJobs) + cupsdCleanJobs(); - if (strncmp(resource, "/printers/", 10) || strlen(resource) == 10) + if (MaxJobs && cupsArrayCount(Jobs) >= MaxJobs) { - /* - * No, return an error... - */ + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Too many active jobs.")); + return (NULL); + } - send_ipp_status(con, IPP_BAD_REQUEST, - _("The printer-uri must be of the form " - "\"ipp://HOSTNAME/printers/PRINTERNAME\".")); - return; + if ((i = check_quotas(con, printer)) < 0) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, _("Quota limit reached.")); + return (NULL); + } + else if (i == 0) + { + send_ipp_status(con, IPP_NOT_AUTHORIZED, _("Not allowed to print.")); + return (NULL); } /* - * Do we have a valid printer name? + * Create the job and set things up... */ - if (!validate_name(resource + 10)) + if ((attr = ippFindAttribute(con->request, "job-priority", + IPP_TAG_INTEGER)) != NULL) + priority = attr->values[0].integer; + else { - /* - * No, return an error... - */ + if ((val = cupsGetOption("job-priority", printer->num_options, + printer->options)) != NULL) + priority = atoi(val); + else + priority = 50; - send_ipp_status(con, IPP_BAD_REQUEST, - _("The printer-uri \"%s\" contains invalid characters."), - uri->values[0].string.text); - return; + ippAddInteger(con->request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority", + priority); } - /* - * Check policy... - */ + if ((attr = ippFindAttribute(con->request, "job-name", + IPP_TAG_NAME)) != NULL) + title = attr->values[0].string.text; + else + ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, + title = "Untitled"); - if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + if ((job = cupsdAddJob(priority, printer->name)) == NULL) { - send_http_error(con, status); - return; + send_ipp_status(con, IPP_INTERNAL_ERROR, + _("Unable to add job for destination \"%s\"!"), + printer->name); + return (NULL); } - /* - * See if the printer already exists; if not, create a new printer... - */ + job->dtype = printer->type & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT | + CUPS_PRINTER_REMOTE); + job->attrs = con->request; + job->dirty = 1; + con->request = ippNewRequest(job->attrs->request.op.operation_id); - if ((printer = cupsdFindPrinter(resource + 10)) == NULL) - { - /* - * Printer doesn't exist; see if we have a class of the same name... - */ + cupsdMarkDirty(CUPSD_DIRTY_JOBS); - if ((printer = cupsdFindClass(resource + 10)) != NULL && - !(printer->type & CUPS_PRINTER_REMOTE)) - { - /* - * Yes, return an error... - */ + add_job_uuid(con, job); + apply_printer_defaults(printer, job); - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("A class named \"%s\" already exists!"), - resource + 10); - return; - } + attr = ippFindAttribute(job->attrs, "requesting-user-name", IPP_TAG_NAME); - /* - * No, add the printer... - */ + if (con->username[0]) + { + cupsdSetString(&job->username, con->username); - printer = cupsdAddPrinter(resource + 10); - modify = 0; + if (attr) + cupsdSetString(&attr->values[0].string.text, con->username); } - else if (printer->type & CUPS_PRINTER_IMPLICIT) + else if (attr) { - /* - * Rename the implicit printer to "AnyPrinter" or delete it... - */ - - if (ImplicitAnyClasses) - { - cupsArrayRemove(Printers, printer); - cupsdSetStringf(&printer->name, "Any%s", resource + 10); - cupsArrayAdd(Printers, printer); - } - else - cupsdDeletePrinter(printer, 1); - - /* - * Add the printer as a new local printer... - */ + cupsdLogMessage(CUPSD_LOG_DEBUG, + "add_job: requesting-user-name=\"%s\"", + attr->values[0].string.text); - printer = cupsdAddPrinter(resource + 10); - modify = 0; + cupsdSetString(&job->username, attr->values[0].string.text); } - else if (printer->type & CUPS_PRINTER_REMOTE) + else + cupsdSetString(&job->username, "anonymous"); + + if (!attr) + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, + "job-originating-user-name", NULL, job->username); + else { - /* - * Rename the remote printer to "Printer@server"... - */ + attr->group_tag = IPP_TAG_JOB; + _cupsStrFree(attr->name); + attr->name = _cupsStrAlloc("job-originating-user-name"); + } - cupsdDeletePrinterFilters(printer); - cupsArrayRemove(Printers, printer); - cupsdSetStringf(&printer->name, "%s@%s", resource + 10, printer->hostname); - cupsdSetPrinterAttrs(printer); - cupsArrayAdd(Printers, printer); + if (con->username[0] || auth_info) + { + save_auth_info(con, job, auth_info); /* - * Add the printer as a new local printer... + * Remove the auth-info attribute from the attribute data... */ - printer = cupsdAddPrinter(resource + 10); - modify = 0; + if (auth_info) + ippDeleteAttribute(job->attrs, auth_info); } - else - modify = 1; - - /* - * Look for attributes and copy them over as needed... - */ - - if ((attr = ippFindAttribute(con->request, "printer-location", - IPP_TAG_TEXT)) != NULL) - cupsdSetString(&printer->location, attr->values[0].string.text); - - if ((attr = ippFindAttribute(con->request, "printer-info", - IPP_TAG_TEXT)) != NULL) - cupsdSetString(&printer->info, attr->values[0].string.text); - if ((attr = ippFindAttribute(con->request, "device-uri", - IPP_TAG_URI)) != NULL) + if ((attr = ippFindAttribute(job->attrs, "job-originating-host-name", + IPP_TAG_ZERO)) != NULL) { /* - * Do we have a valid device URI? + * Request contains a job-originating-host-name attribute; validate it... */ - httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if (!strcmp(method, "file")) + if (attr->value_tag != IPP_TAG_NAME || + attr->num_values != 1 || + strcmp(con->http.hostname, "localhost")) { /* - * See if the administrator has enabled file devices... + * Can't override the value if we aren't connected via localhost. + * Also, we can only have 1 value and it must be a name value. */ - if (!FileDevice && strcmp(resource, "/dev/null")) + switch (attr->value_tag) { - /* - * File devices are disabled and the URL is not file:/dev/null... - */ + case IPP_TAG_STRING : + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_URI : + case IPP_TAG_URISCHEME : + case IPP_TAG_CHARSET : + case IPP_TAG_LANGUAGE : + case IPP_TAG_MIMETYPE : + /* + * Free old strings... + */ - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("File device URIs have been disabled! " - "To enable, see the FileDevice directive in " - "\"%s/cupsd.conf\"."), - ServerRoot); - return; + for (i = 0; i < attr->num_values; i ++) + { + _cupsStrFree(attr->values[i].string.text); + attr->values[i].string.text = NULL; + if (attr->values[i].string.charset) + { + _cupsStrFree(attr->values[i].string.charset); + attr->values[i].string.charset = NULL; + } + } + + default : + break; } - } - else - { + /* - * See if the backend exists and is executable... + * Use the default connection hostname instead... */ - snprintf(srcfile, sizeof(srcfile), "%s/backend/%s", ServerBin, method); - if (access(srcfile, X_OK)) - { - /* - * Could not find device in list! - */ - - send_ipp_status(con, IPP_NOT_POSSIBLE, _("Bad device-uri \"%s\"!"), - attr->values[0].string.text); - return; - } + attr->value_tag = IPP_TAG_NAME; + attr->num_values = 1; + attr->values[0].string.text = _cupsStrAlloc(con->http.hostname); } - cupsdLogMessage(CUPSD_LOG_INFO, - "Setting %s device-uri to \"%s\" (was \"%s\".)", - printer->name, - cupsdSanitizeURI(attr->values[0].string.text, line, - sizeof(line)), - cupsdSanitizeURI(printer->device_uri, resource, - sizeof(resource))); - - cupsdSetString(&printer->device_uri, attr->values[0].string.text); + attr->group_tag = IPP_TAG_JOB; } - - if ((attr = ippFindAttribute(con->request, "port-monitor", - IPP_TAG_KEYWORD)) != NULL) + else { - ipp_attribute_t *supported; /* port-monitor-supported attribute */ + /* + * No job-originating-host-name attribute, so use the hostname from + * the connection... + */ + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, + "job-originating-host-name", NULL, con->http.hostname); + } - supported = ippFindAttribute(printer->attrs, "port-monitor-supported", - IPP_TAG_KEYWORD); - for (i = 0; i < supported->num_values; i ++) - if (!strcmp(supported->values[i].string.text, - attr->values[0].string.text)) - break; + ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", + time(NULL)); + attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, + "time-at-processing", 0); + attr->value_tag = IPP_TAG_NOVALUE; + attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, + "time-at-completed", 0); + attr->value_tag = IPP_TAG_NOVALUE; - if (i >= supported->num_values) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, _("Bad port-monitor \"%s\"!"), - attr->values[0].string.text); - return; - } + /* + * Add remaining job attributes... + */ - cupsdLogMessage(CUPSD_LOG_INFO, - "Setting %s port-monitor to \"%s\" (was \"%s\".)", - printer->name, attr->values[0].string.text, - printer->port_monitor); + ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); + job->state = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_ENUM, + "job-state", IPP_JOB_STOPPED); + job->state_value = (ipp_jstate_t)job->state->values[0].integer; + job->sheets = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, + "job-media-sheets-completed", 0); + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, + printer->uri); + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, + title); - if (strcmp(attr->values[0].string.text, "none")) - cupsdSetString(&printer->port_monitor, attr->values[0].string.text); - else - cupsdClearString(&printer->port_monitor); - } + if ((attr = ippFindAttribute(job->attrs, "job-k-octets", + IPP_TAG_INTEGER)) != NULL) + attr->values[0].integer = 0; + else + ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-k-octets", 0); - if ((attr = ippFindAttribute(con->request, "printer-is-accepting-jobs", - IPP_TAG_BOOLEAN)) != NULL) + if ((attr = ippFindAttribute(job->attrs, "job-hold-until", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); + if (!attr) { - cupsdLogMessage(CUPSD_LOG_INFO, - "Setting %s printer-is-accepting-jobs to %d (was %d.)", - printer->name, attr->values[0].boolean, printer->accepting); + if ((val = cupsGetOption("job-hold-until", printer->num_options, + printer->options)) == NULL) + val = "no-hold"; - printer->accepting = attr->values[0].boolean; - cupsdAddPrinterHistory(printer); + attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-hold-until", NULL, val); } - - if ((attr = ippFindAttribute(con->request, "printer-is-shared", - IPP_TAG_BOOLEAN)) != NULL) + if (attr && strcmp(attr->values[0].string.text, "no-hold") && + !(printer->type & CUPS_PRINTER_REMOTE)) { - if (printer->shared && !attr->values[0].boolean) - cupsdSendBrowseDelete(printer); + /* + * Hold job until specified time... + */ - cupsdLogMessage(CUPSD_LOG_INFO, - "Setting %s printer-is-shared to %d (was %d.)", - printer->name, attr->values[0].boolean, printer->shared); + cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0); - printer->shared = attr->values[0].boolean; + job->state->values[0].integer = IPP_JOB_HELD; + job->state_value = IPP_JOB_HELD; } - - if ((attr = ippFindAttribute(con->request, "printer-state", - IPP_TAG_ENUM)) != NULL) + else if (job->attrs->request.op.operation_id == IPP_CREATE_JOB) { - if (attr->values[0].integer != IPP_PRINTER_IDLE && - attr->values[0].integer != IPP_PRINTER_STOPPED) - { - send_ipp_status(con, IPP_BAD_REQUEST, _("Bad printer-state value %d!"), - attr->values[0].integer); - return; - } - - cupsdLogMessage(CUPSD_LOG_INFO, "Setting %s printer-state to %d (was %d.)", printer->name, - attr->values[0].integer, printer->state); - - if (attr->values[0].integer == IPP_PRINTER_STOPPED) - cupsdStopPrinter(printer, 0); - else - cupsdSetPrinterState(printer, (ipp_pstate_t)(attr->values[0].integer), 0); - } - if ((attr = ippFindAttribute(con->request, "printer-state-message", - IPP_TAG_TEXT)) != NULL) - { - strlcpy(printer->state_message, attr->values[0].string.text, - sizeof(printer->state_message)); - cupsdAddPrinterHistory(printer); - } - if ((attr = ippFindAttribute(con->request, "job-sheets-default", - IPP_TAG_ZERO)) != NULL && - !Classification) - { - cupsdSetString(&printer->job_sheets[0], attr->values[0].string.text); - if (attr->num_values > 1) - cupsdSetString(&printer->job_sheets[1], attr->values[1].string.text); - else - cupsdSetString(&printer->job_sheets[1], "none"); + job->hold_until = time(NULL) + MultipleOperationTimeout; + job->state->values[0].integer = IPP_JOB_HELD; + job->state_value = IPP_JOB_HELD; } - if ((attr = ippFindAttribute(con->request, "requesting-user-name-allowed", - IPP_TAG_ZERO)) != NULL) + else { - cupsdFreePrinterUsers(printer); - - printer->deny_users = 0; - if (attr->value_tag == IPP_TAG_NAME && - (attr->num_values > 1 || - strcmp(attr->values[0].string.text, "all"))) - for (i = 0; i < attr->num_values; i ++) - cupsdAddPrinterUser(printer, attr->values[i].string.text); + job->state->values[0].integer = IPP_JOB_PENDING; + job->state_value = IPP_JOB_PENDING; } - else if ((attr = ippFindAttribute(con->request, "requesting-user-name-denied", - IPP_TAG_ZERO)) != NULL) - { - cupsdFreePrinterUsers(printer); - printer->deny_users = 1; - if (attr->value_tag == IPP_TAG_NAME && - (attr->num_values > 1 || - strcmp(attr->values[0].string.text, "none"))) - for (i = 0; i < attr->num_values; i ++) - cupsdAddPrinterUser(printer, attr->values[i].string.text); - } - if ((attr = ippFindAttribute(con->request, "job-quota-period", - IPP_TAG_INTEGER)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "Setting job-quota-period to %d...", - attr->values[0].integer); - cupsdFreeQuotas(printer); - printer->quota_period = attr->values[0].integer; - } - if ((attr = ippFindAttribute(con->request, "job-k-limit", - IPP_TAG_INTEGER)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "Setting job-k-limit to %d...", - attr->values[0].integer); - cupsdFreeQuotas(printer); - printer->k_limit = attr->values[0].integer; - } - if ((attr = ippFindAttribute(con->request, "job-page-limit", - IPP_TAG_INTEGER)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "Setting job-page-limit to %d...", - attr->values[0].integer); - cupsdFreeQuotas(printer); - printer->page_limit = attr->values[0].integer; - } - if ((attr = ippFindAttribute(con->request, "printer-op-policy", - IPP_TAG_NAME)) != NULL) + if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) || + Classification) { - cupsd_policy_t *p; /* Policy */ - + /* + * Add job sheets options... + */ - if ((p = cupsdFindPolicy(attr->values[0].string.text)) != NULL) + if ((attr = ippFindAttribute(job->attrs, "job-sheets", + IPP_TAG_ZERO)) == NULL) { cupsdLogMessage(CUPSD_LOG_DEBUG, - "Setting printer-op-policy to \"%s\"...", - attr->values[0].string.text); - cupsdSetString(&printer->op_policy, attr->values[0].string.text); - printer->op_policy_ptr = p; - } - else - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Unknown printer-op-policy \"%s\"."), - attr->values[0].string.text); - return; - } - } - if ((attr = ippFindAttribute(con->request, "printer-error-policy", - IPP_TAG_NAME)) != NULL) - { - if (strcmp(attr->values[0].string.text, "abort-job") && - strcmp(attr->values[0].string.text, "retry-job") && - strcmp(attr->values[0].string.text, "stop-printer")) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Unknown printer-error-policy \"%s\"."), - attr->values[0].string.text); - return; - } - - cupsdLogMessage(CUPSD_LOG_DEBUG, - "Setting printer-error-policy to \"%s\"...", - attr->values[0].string.text); - cupsdSetString(&printer->error_policy, attr->values[0].string.text); - } - - /* - * See if we have all required attributes... - */ + "Adding default job-sheets values \"%s,%s\"...", + printer->job_sheets[0], printer->job_sheets[1]); - if (!printer->device_uri) - cupsdSetString(&printer->device_uri, "file:/dev/null"); + attr = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-sheets", + 2, NULL, NULL); + attr->values[0].string.text = _cupsStrRetain(printer->job_sheets[0]); + attr->values[1].string.text = _cupsStrRetain(printer->job_sheets[1]); + } - /* - * See if we have an interface script or PPD file attached to the request... - */ + job->job_sheets = attr; - if (con->filename) - { - strlcpy(srcfile, con->filename, sizeof(srcfile)); + /* + * Enforce classification level if set... + */ - if ((fp = cupsFileOpen(srcfile, "rb"))) + if (Classification) { - /* - * Yes; get the first line from it... - */ - - line[0] = '\0'; - cupsFileGets(fp, line, sizeof(line)); - cupsFileClose(fp); - - /* - * Then see what kind of file it is... - */ - - snprintf(dstfile, sizeof(dstfile), "%s/interfaces/%s", ServerRoot, - printer->name); + cupsdLogMessage(CUPSD_LOG_INFO, + "Classification=\"%s\", ClassifyOverride=%d", + Classification ? Classification : "(null)", + ClassifyOverride); - if (!strncmp(line, "*PPD-Adobe", 10)) + if (ClassifyOverride) { - /* - * The new file is a PPD file, so remove any old interface script - * that might be lying around... - */ + if (!strcmp(attr->values[0].string.text, "none") && + (attr->num_values == 1 || + !strcmp(attr->values[1].string.text, "none"))) + { + /* + * Force the leading banner to have the classification on it... + */ - unlink(dstfile); - } - else - { - /* - * This must be an interface script, so move the file over to the - * interfaces directory and make it executable... - */ + cupsdSetString(&attr->values[0].string.text, Classification); - if (copy_file(srcfile, dstfile)) - { - send_ipp_status(con, IPP_INTERNAL_ERROR, - _("Unable to copy interface script - %s!"), - strerror(errno)); - return; - } - else - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "Copied interface script successfully!"); - chmod(dstfile, 0755); + cupsdLogJob(job, CUPSD_LOG_NOTICE, "CLASSIFICATION FORCED " + "job-sheets=\"%s,none\", " + "job-originating-user-name=\"%s\"", + Classification, job->username); } - } + else if (attr->num_values == 2 && + strcmp(attr->values[0].string.text, + attr->values[1].string.text) && + strcmp(attr->values[0].string.text, "none") && + strcmp(attr->values[1].string.text, "none")) + { + /* + * Can't put two different security markings on the same document! + */ - snprintf(dstfile, sizeof(dstfile), "%s/ppd/%s.ppd", ServerRoot, - printer->name); + cupsdSetString(&attr->values[1].string.text, attr->values[0].string.text); - if (!strncmp(line, "*PPD-Adobe", 10)) + cupsdLogJob(job, CUPSD_LOG_NOTICE, "CLASSIFICATION FORCED " + "job-sheets=\"%s,%s\", " + "job-originating-user-name=\"%s\"", + attr->values[0].string.text, + attr->values[1].string.text, job->username); + } + else if (strcmp(attr->values[0].string.text, Classification) && + strcmp(attr->values[0].string.text, "none") && + (attr->num_values == 1 || + (strcmp(attr->values[1].string.text, Classification) && + strcmp(attr->values[1].string.text, "none")))) + { + if (attr->num_values == 1) + cupsdLogJob(job, CUPSD_LOG_NOTICE, + "CLASSIFICATION OVERRIDDEN " + "job-sheets=\"%s\", " + "job-originating-user-name=\"%s\"", + attr->values[0].string.text, job->username); + else + cupsdLogJob(job, CUPSD_LOG_NOTICE, + "CLASSIFICATION OVERRIDDEN " + "job-sheets=\"%s,%s\",fffff " + "job-originating-user-name=\"%s\"", + attr->values[0].string.text, + attr->values[1].string.text, job->username); + } + } + else if (strcmp(attr->values[0].string.text, Classification) && + (attr->num_values == 1 || + strcmp(attr->values[1].string.text, Classification))) { /* - * The new file is a PPD file, so move the file over to the - * ppd directory and make it readable by all... + * Force the banner to have the classification on it... */ - if (copy_file(srcfile, dstfile)) + if (attr->num_values > 1 && + !strcmp(attr->values[0].string.text, attr->values[1].string.text)) { - send_ipp_status(con, IPP_INTERNAL_ERROR, - _("Unable to copy PPD file - %s!"), - strerror(errno)); - return; + cupsdSetString(&(attr->values[0].string.text), Classification); + cupsdSetString(&(attr->values[1].string.text), Classification); } - else + else { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "Copied PPD file successfully!"); - chmod(dstfile, 0644); - } - } - else - { - /* - * This must be an interface script, so remove any old PPD file that - * may be lying around... - */ + if (attr->num_values == 1 || + strcmp(attr->values[0].string.text, "none")) + cupsdSetString(&(attr->values[0].string.text), Classification); - unlink(dstfile); + if (attr->num_values > 1 && + strcmp(attr->values[1].string.text, "none")) + cupsdSetString(&(attr->values[1].string.text), Classification); + } + + if (attr->num_values > 1) + cupsdLogJob(job, CUPSD_LOG_NOTICE, + "CLASSIFICATION FORCED " + "job-sheets=\"%s,%s\", " + "job-originating-user-name=\"%s\"", + attr->values[0].string.text, + attr->values[1].string.text, job->username); + else + cupsdLogJob(job, CUPSD_LOG_NOTICE, + "CLASSIFICATION FORCED " + "job-sheets=\"%s\", " + "job-originating-user-name=\"%s\"", + Classification, job->username); } } - } - else if ((attr = ippFindAttribute(con->request, "ppd-name", - IPP_TAG_NAME)) != NULL) - { - if (!strcmp(attr->values[0].string.text, "raw")) - { - /* - * Raw driver, remove any existing PPD or interface script files. - */ - snprintf(dstfile, sizeof(dstfile), "%s/interfaces/%s", ServerRoot, - printer->name); - unlink(dstfile); + /* + * See if we need to add the starting sheet... + */ - snprintf(dstfile, sizeof(dstfile), "%s/ppd/%s.ppd", ServerRoot, - printer->name); - unlink(dstfile); - } - else + if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))) { - /* - * PPD model file... - */ - - snprintf(dstfile, sizeof(dstfile), "%s/interfaces/%s", ServerRoot, - printer->name); - unlink(dstfile); + cupsdLogJob(job, CUPSD_LOG_INFO, "Adding start banner page \"%s\".", + attr->values[0].string.text); - snprintf(dstfile, sizeof(dstfile), "%s/ppd/%s.ppd", ServerRoot, - printer->name); - - if (copy_model(con, attr->values[0].string.text, dstfile)) - { - send_ipp_status(con, IPP_INTERNAL_ERROR, _("Unable to copy PPD file!")); - return; - } - else + if ((kbytes = copy_banner(con, job, attr->values[0].string.text)) < 0) { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "Copied PPD file successfully!"); - chmod(dstfile, 0644); + cupsdSetJobState(job, IPP_JOB_ABORTED, CUPSD_JOB_PURGE, + "Aborting job because the start banner could not be " + "copied."); + return (NULL); } + + cupsdUpdateQuota(printer, job->username, 0, kbytes); } } + else if ((attr = ippFindAttribute(job->attrs, "job-sheets", + IPP_TAG_ZERO)) != NULL) + job->sheets = attr; /* - * Make this printer the default if there is none... + * Fill in the response info... */ - if (!DefaultPrinter) - DefaultPrinter = printer; + snprintf(job_uri, sizeof(job_uri), "http://%s:%d/jobs/%d", ServerName, + LocalPort, job->id); - /* - * Update the printer attributes and return... - */ + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, + job_uri); - cupsdSetPrinterAttrs(printer); - cupsdSaveAllPrinters(); + ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); - if (printer->job) - { - cupsd_job_t *job; - - /* - * Stop the current job and then restart it below... - */ + ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", + job->state_value); + add_job_state_reasons(con, job); - job = (cupsd_job_t *)printer->job; + con->response->request.status.status_code = IPP_OK; - cupsdStopJob(job, 1); - job->state->values[0].integer = IPP_JOB_PENDING; - } + /* + * Add any job subscriptions... + */ - cupsdCheckJobs(); + add_job_subscriptions(con, job); - cupsdWritePrintcap(); + /* + * Set all but the first two attributes to the job attributes group... + */ - if (modify) - { - cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED, printer, NULL, - "Printer \"%s\" modified by \"%s\".", printer->name, - con->username); + for (attr = job->attrs->attrs->next->next; attr; attr = attr->next) + attr->group_tag = IPP_TAG_JOB; - cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" modified by \"%s\".", - printer->name, con->username); - } - else - { - cupsdAddPrinterHistory(printer); + /* + * Fire the "job created" event... + */ - cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, printer, NULL, - "New printer \"%s\" added by \"%s\".", printer->name, - con->username); + cupsdAddEvent(CUPSD_EVENT_JOB_CREATED, printer, job, "Job created."); - cupsdLogMessage(CUPSD_LOG_INFO, "New printer \"%s\" added by \"%s\".", - printer->name, con->username); - } + /* + * Return the new job... + */ - con->response->request.status.status_code = IPP_OK; + return (job); } /* - * 'add_printer_state_reasons()' - Add the "printer-state-reasons" attribute - * based upon the printer state... + * 'add_job_state_reasons()' - Add the "job-state-reasons" attribute based + * upon the job and printer state... */ static void -add_printer_state_reasons( - cupsd_client_t *con, /* I - Client connection */ - cupsd_printer_t *p) /* I - Printer info */ +add_job_state_reasons( + cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job) /* I - Job info */ { - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "add_printer_state_reasons(%p[%d], %p[%s])", - con, con->http.fd, p, p->name); + cupsd_printer_t *dest; /* Destination printer */ - if (p->num_reasons == 0) - ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, - "printer-state-reasons", NULL, - p->state == IPP_PRINTER_STOPPED ? "paused" : "none"); - else - ippAddStrings(con->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, - "printer-state-reasons", p->num_reasons, NULL, - (const char * const *)p->reasons); -} + cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_job_state_reasons(%p[%d], %d)", + con, con->http.fd, job ? job->id : 0); -/* - * 'add_queued_job_count()' - Add the "queued-job-count" attribute for - * the specified printer or class. - */ + switch (job ? job->state_value : IPP_JOB_CANCELED) + { + case IPP_JOB_PENDING : + dest = cupsdFindDest(job->dest); -static void -add_queued_job_count( - cupsd_client_t *con, /* I - Client connection */ - cupsd_printer_t *p) /* I - Printer or class */ -{ - int count; /* Number of jobs on destination */ + if (dest && dest->state == IPP_PRINTER_STOPPED) + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "printer-stopped"); + else + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "none"); + break; + case IPP_JOB_HELD : + if (ippFindAttribute(job->attrs, "job-hold-until", + IPP_TAG_KEYWORD) != NULL || + ippFindAttribute(job->attrs, "job-hold-until", + IPP_TAG_NAME) != NULL) + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-hold-until-specified"); + else + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-incoming"); + break; - cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_queued_job_count(%p[%d], %p[%s])", - con, con->http.fd, p, p->name); + case IPP_JOB_PROCESSING : + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-printing"); + break; - count = cupsdGetPrinterJobCount(p->name); + case IPP_JOB_STOPPED : + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-stopped"); + break; - ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, - "queued-job-count", count); + case IPP_JOB_CANCELED : + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-canceled-by-user"); + break; + + case IPP_JOB_ABORTED : + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "aborted-by-system"); + break; + + case IPP_JOB_COMPLETED : + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_KEYWORD, + "job-state-reasons", NULL, "job-completed-successfully"); + break; + } } /* - * 'authenticate_job()' - Set job authentication info. + * 'add_job_subscriptions()' - Add any subscriptions for a job. */ static void -authenticate_job(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Job URI */ +add_job_subscriptions( + cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job) /* I - Newly created job */ { - ipp_attribute_t *attr; /* Job-id attribute */ - int jobid; /* Job ID */ - cupsd_job_t *job; /* Current job */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ - username[HTTP_MAX_URI], - /* Username portion of URI */ - host[HTTP_MAX_URI], - /* Host portion of URI */ - resource[HTTP_MAX_URI]; - /* Resource portion of URI */ - int port; /* Port portion of URI */ - + int i; /* Looping var */ + ipp_attribute_t *prev, /* Previous attribute */ + *next, /* Next attribute */ + *attr; /* Current attribute */ + cupsd_subscription_t *sub; /* Subscription object */ + const char *recipient, /* notify-recipient-uri */ + *pullmethod; /* notify-pull-method */ + ipp_attribute_t *user_data; /* notify-user-data */ + int interval; /* notify-time-interval */ + unsigned mask; /* notify-events */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "authenticate_job(%p[%d], %s)", - con, con->http.fd, uri->values[0].string.text); /* - * Start with "everything is OK" status... + * Find the first subscription group attribute; return if we have + * none... */ - con->response->request.status.status_code = IPP_OK; + for (attr = job->attrs->attrs; attr; attr = attr->next) + if (attr->group_tag == IPP_TAG_SUBSCRIPTION) + break; + + if (!attr) + return; /* - * See if we have a job URI or a printer URI... + * Process the subscription attributes in the request... */ - if (!strcmp(uri->name, "printer-uri")) - { - /* - * Got a printer URI; see if we also have a job-id attribute... - */ - - if ((attr = ippFindAttribute(con->request, "job-id", - IPP_TAG_INTEGER)) == NULL) - { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Got a printer-uri attribute but no job-id!")); - return; - } - - jobid = attr->values[0].integer; - } - else + while (attr) { - /* - * Got a job URI; parse it to get the job ID... - */ + recipient = NULL; + pullmethod = NULL; + user_data = NULL; + interval = 0; + mask = CUPSD_EVENT_NONE; - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if (strncmp(resource, "/jobs/", 6)) + while (attr && attr->group_tag != IPP_TAG_ZERO) { - /* - * Not a valid URI! - */ - - send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-uri attribute \"%s\"!"), - uri->values[0].string.text); - return; - } - - jobid = atoi(resource + 6); - } - - /* - * See if the job exists... - */ - - if ((job = cupsdFindJob(jobid)) == NULL) - { - /* - * Nope - return a "not found" error... - */ + if (!strcmp(attr->name, "notify-recipient-uri") && + attr->value_tag == IPP_TAG_URI) + { + /* + * Validate the recipient scheme against the ServerBin/notifier + * directory... + */ - send_ipp_status(con, IPP_NOT_FOUND, - _("Job #%d does not exist!"), jobid); - return; - } + char notifier[1024], /* Notifier filename */ + scheme[HTTP_MAX_URI], /* Scheme portion of URI */ + userpass[HTTP_MAX_URI], /* Username portion of URI */ + host[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ + int port; /* Port portion of URI */ - /* - * See if the job has been completed... - */ - if (job->state->values[0].integer != IPP_JOB_HELD) - { - /* - * Return a "not-possible" error... - */ + recipient = attr->values[0].string.text; - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job #%d is not held for authentication!"), - jobid); - return; - } + if (httpSeparateURI(HTTP_URI_CODING_ALL, recipient, + scheme, sizeof(scheme), userpass, sizeof(userpass), + host, sizeof(host), &port, + resource, sizeof(resource)) < HTTP_URI_OK) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Bad notify-recipient-uri URI \"%s\"!"), recipient); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_URI_SCHEME); + return; + } - /* - * See if we have already authenticated... - */ + snprintf(notifier, sizeof(notifier), "%s/notifier/%s", ServerBin, + scheme); + if (access(notifier, X_OK)) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("notify-recipient-uri URI \"%s\" uses unknown " + "scheme!"), recipient); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_URI_SCHEME); + return; + } - if (!con->username[0]) - { - send_ipp_status(con, IPP_NOT_AUTHORIZED, - _("No authentication information provided!")); - return; - } + if (!strcmp(scheme, "rss") && !check_rss_recipient(recipient)) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("notify-recipient-uri URI \"%s\" is already used!"), + recipient); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_ATTRIBUTES); + return; + } + } + else if (!strcmp(attr->name, "notify-pull-method") && + attr->value_tag == IPP_TAG_KEYWORD) + { + pullmethod = attr->values[0].string.text; - /* - * See if the job is owned by the requesting user... - */ + if (strcmp(pullmethod, "ippget")) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Bad notify-pull-method \"%s\"!"), pullmethod); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_ATTRIBUTES); + return; + } + } + else if (!strcmp(attr->name, "notify-charset") && + attr->value_tag == IPP_TAG_CHARSET && + strcmp(attr->values[0].string.text, "us-ascii") && + strcmp(attr->values[0].string.text, "utf-8")) + { + send_ipp_status(con, IPP_CHARSET, + _("Character set \"%s\" not supported!"), + attr->values[0].string.text); + return; + } + else if (!strcmp(attr->name, "notify-natural-language") && + (attr->value_tag != IPP_TAG_LANGUAGE || + strcmp(attr->values[0].string.text, DefaultLanguage))) + { + send_ipp_status(con, IPP_CHARSET, + _("Language \"%s\" not supported!"), + attr->values[0].string.text); + return; + } + else if (!strcmp(attr->name, "notify-user-data") && + attr->value_tag == IPP_TAG_STRING) + { + if (attr->num_values > 1 || attr->values[0].unknown.length > 63) + { + send_ipp_status(con, IPP_REQUEST_VALUE, + _("The notify-user-data value is too large " + "(%d > 63 octets)!"), + attr->values[0].unknown.length); + return; + } - if (!validate_user(job, con, job->username, username, sizeof(username))) + user_data = attr; + } + else if (!strcmp(attr->name, "notify-events") && + attr->value_tag == IPP_TAG_KEYWORD) + { + for (i = 0; i < attr->num_values; i ++) + mask |= cupsdEventValue(attr->values[i].string.text); + } + else if (!strcmp(attr->name, "notify-lease-duration")) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("The notify-lease-duration attribute cannot be " + "used with job subscriptions.")); + return; + } + else if (!strcmp(attr->name, "notify-time-interval") && + attr->value_tag == IPP_TAG_INTEGER) + interval = attr->values[0].integer; + + attr = attr->next; + } + + if (!recipient && !pullmethod) + break; + + if (mask == CUPSD_EVENT_NONE) + mask = CUPSD_EVENT_JOB_COMPLETED; + + if ((sub = cupsdAddSubscription(mask, cupsdFindDest(job->dest), job, + recipient, 0)) != NULL) + { + sub->interval = interval; + + cupsdSetString(&sub->owner, job->username); + + if (user_data) + { + sub->user_data_len = user_data->values[0].unknown.length; + memcpy(sub->user_data, user_data->values[0].unknown.data, + sub->user_data_len); + } + + ippAddSeparator(con->response); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-subscription-id", sub->id); + } + + if (attr) + attr = attr->next; + } + + cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS); + + /* + * Remove all of the subscription attributes from the job request... + * + * TODO: Optimize this since subscription groups have to come at the + * end of the request... + */ + + for (attr = job->attrs->attrs, prev = NULL; attr; attr = next) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to authenticate " - "job #%d owned by \"%s\"!"), - jobid, job->username); - return; + next = attr->next; + + if (attr->group_tag == IPP_TAG_SUBSCRIPTION || + attr->group_tag == IPP_TAG_ZERO) + { + /* + * Free and remove this attribute... + */ + + _ippFreeAttr(attr); + + if (prev) + prev->next = next; + else + job->attrs->attrs = next; + } + else + prev = attr; } + job->attrs->last = prev; + job->attrs->current = prev; +} + + +/* + * 'add_job_uuid()' - Add job-uuid attribute to a job. + * + * See RFC 4122 for the definition of UUIDs and the format. + */ + +static void +add_job_uuid(cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job) /* I - Job */ +{ + char uuid[1024]; /* job-uuid string */ + _cups_md5_state_t md5state; /* MD5 state */ + unsigned char md5sum[16]; /* MD5 digest/sum */ + + /* - * Save the authentication information for this job... + * First see if the job already has a job-uuid attribute; if so, return... */ - save_auth_info(con, job); + if (ippFindAttribute(job->attrs, "job-uuid", IPP_TAG_URI)) + return; /* - * Reset the job-hold-until value to "no-hold"... + * No job-uuid attribute, so build a version 3 UUID with the local job + * ID at the end; see RFC 4122 for details. Start with the MD5 sum of + * the ServerName, server name and port that the client connected to, + * and local job ID... */ - if ((attr = ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); + snprintf(uuid, sizeof(uuid), "%s:%s:%d:%d", ServerName, con->servername, + con->serverport, job->id); - if (attr) - { - attr->value_tag = IPP_TAG_KEYWORD; - cupsdSetString(&(attr->values[0].string.text), "no-hold"); - } + _cupsMD5Init(&md5state); + _cupsMD5Append(&md5state, (unsigned char *)uuid, strlen(uuid)); + _cupsMD5Finish(&md5state, md5sum); /* - * Release the job and return... + * Format the UUID URI using the MD5 sum and job ID. */ - cupsdReleaseJob(job); + snprintf(uuid, sizeof(uuid), + "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x", + md5sum[0], md5sum[1], md5sum[2], md5sum[3], md5sum[4], md5sum[5], + (md5sum[6] & 15) | 0x30, md5sum[7], (md5sum[8] & 0x3f) | 0x40, + md5sum[9], md5sum[10], md5sum[11], md5sum[12], md5sum[13], + md5sum[14], md5sum[15]); - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d was authenticated by \"%s\".", jobid, - con->username); + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-uuid", NULL, uuid); } /* - * 'cancel_all_jobs()' - Cancel all print jobs. + * 'add_printer()' - Add a printer to the system. */ static void -cancel_all_jobs(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Job or Printer URI */ +add_printer(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - URI of printer */ { http_status_t status; /* Policy status */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - userpass[HTTP_MAX_URI], /* Username portion of URI */ + int i; /* Looping var */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ + username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ int port; /* Port portion of URI */ - ipp_attribute_t *attr; /* Attribute in request */ - const char *username; /* Username */ - int purge; /* Purge? */ - cupsd_printer_t *printer; /* Printer */ + cupsd_printer_t *printer; /* Printer/class */ + ipp_attribute_t *attr; /* Printer attribute */ + cups_file_t *fp; /* Script/PPD file */ + char line[1024]; /* Line from file... */ + char srcfile[1024], /* Source Script/PPD file */ + dstfile[1024]; /* Destination Script/PPD file */ + int modify; /* Non-zero if we are modifying */ + char newname[IPP_MAX_NAME]; /* New printer name */ + int need_restart_job; /* Need to restart job? */ + int set_device_uri, /* Did we set the device URI? */ + set_port_monitor; /* Did we set the port monitor? */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cancel_all_jobs(%p[%d], %s)", con, + cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_printer(%p[%d], %s)", con, con->http.fd, uri->values[0].string.text); /* - * See if we have a printer URI... + * Do we have a valid URI? */ - if (strcmp(uri->name, "printer-uri")) + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); + + if (strncmp(resource, "/printers/", 10) || strlen(resource) == 10) { + /* + * No, return an error... + */ + send_ipp_status(con, IPP_BAD_REQUEST, - _("The printer-uri attribute is required!")); + _("The printer-uri must be of the form " + "\"ipp://HOSTNAME/printers/PRINTERNAME\".")); return; } /* - * Get the username (if any) for the jobs we want to cancel (only if - * "my-jobs" is specified... + * Do we have a valid printer name? */ - if ((attr = ippFindAttribute(con->request, "my-jobs", - IPP_TAG_BOOLEAN)) != NULL && - attr->values[0].boolean) + if (!validate_name(resource + 10)) { - if ((attr = ippFindAttribute(con->request, "requesting-user-name", - IPP_TAG_NAME)) != NULL) - username = attr->values[0].string.text; - else - { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Missing requesting-user-name attribute!")); - return; - } - } - else - username = NULL; - - /* - * Look for the "purge-jobs" attribute... - */ + /* + * No, return an error... + */ - if ((attr = ippFindAttribute(con->request, "purge-jobs", - IPP_TAG_BOOLEAN)) != NULL) - purge = attr->values[0].boolean; - else - purge = 1; + send_ipp_status(con, IPP_BAD_REQUEST, + _("The printer-uri \"%s\" contains invalid characters."), + uri->values[0].string.text); + return; + } /* - * And if the destination is valid... + * See if the printer already exists; if not, create a new printer... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), userpass, sizeof(userpass), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if ((printer = cupsdFindPrinter(resource + 10)) == NULL) { /* - * Bad URI? + * Printer doesn't exist; see if we have a class of the same name... */ - if (!strncmp(resource, "/printers/", 10) || - !strncmp(resource, "/classes/", 9)) + if ((printer = cupsdFindClass(resource + 10)) != NULL && + !(printer->type & CUPS_PRINTER_DISCOVERED)) { - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer or class was not found.")); + /* + * Yes, return an error... + */ + + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("A class named \"%s\" already exists!"), + resource + 10); return; } - else if (strcmp(resource, "/printers/")) + + /* + * No, check the default policy then add the printer... + */ + + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer-uri \"%s\" is not valid."), - uri->values[0].string.text); + send_http_error(con, status, NULL); return; } + printer = cupsdAddPrinter(resource + 10); + modify = 0; + } + else if (printer->type & CUPS_PRINTER_IMPLICIT) + { /* - * Check policy... + * Check the default policy, then rename the implicit printer to + * "AnyPrinter" or delete it... */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } + if (ImplicitAnyClasses) + { + snprintf(newname, sizeof(newname), "Any%s", resource + 10); + cupsdRenamePrinter(printer, newname); + } + else + cupsdDeletePrinter(printer, 1); + /* - * Cancel all jobs on all printers... + * Add the printer as a new local printer... */ - cupsdCancelJobs(NULL, username, purge); - - cupsdLogMessage(CUPSD_LOG_INFO, "All jobs were %s by \"%s\".", - purge ? "purged" : "cancelled", con->username); + printer = cupsdAddPrinter(resource + 10); + modify = 0; } - else + else if (printer->type & CUPS_PRINTER_DISCOVERED) { /* - * Check policy... + * Check the default policy, then rename the remote printer to + * "Printer@server"... */ - if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } + snprintf(newname, sizeof(newname), "%s@%s", resource + 10, + printer->hostname); + cupsdRenamePrinter(printer, newname); + /* - * Cancel all of the jobs on the named printer... + * Add the printer as a new local printer... */ - cupsdCancelJobs(dest, username, purge); - - cupsdLogMessage(CUPSD_LOG_INFO, "All jobs on \"%s\" were %s by \"%s\".", - dest, purge ? "purged" : "cancelled", con->username); + printer = cupsdAddPrinter(resource + 10); + modify = 0; } + else if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, + NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return; + } + else + modify = 1; - con->response->request.status.status_code = IPP_OK; -} - - -/* - * 'cancel_job()' - Cancel a print job. - */ + /* + * Look for attributes and copy them over as needed... + */ -static void -cancel_job(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Job or Printer URI */ -{ - ipp_attribute_t *attr; /* Current attribute */ - int jobid; /* Job ID */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - cupsd_job_t *job; /* Job information */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - cupsd_printer_t *printer; /* Printer data */ + need_restart_job = 0; + if ((attr = ippFindAttribute(con->request, "printer-location", + IPP_TAG_TEXT)) != NULL) + cupsdSetString(&printer->location, attr->values[0].string.text); - cupsdLogMessage(CUPSD_LOG_DEBUG2, "cancel_job(%p[%d], %s)", con, - con->http.fd, uri->values[0].string.text); + if ((attr = ippFindAttribute(con->request, "printer-info", + IPP_TAG_TEXT)) != NULL) + cupsdSetString(&printer->info, attr->values[0].string.text); - /* - * See if we have a job URI or a printer URI... - */ + set_device_uri = 0; - if (!strcmp(uri->name, "printer-uri")) + if ((attr = ippFindAttribute(con->request, "device-uri", + IPP_TAG_URI)) != NULL) { /* - * Got a printer URI; see if we also have a job-id attribute... + * Do we have a valid device URI? */ - if ((attr = ippFindAttribute(con->request, "job-id", - IPP_TAG_INTEGER)) == NULL) + http_uri_status_t uri_status; /* URI separation status */ + char old_device_uri[1024]; + /* Old device URI */ + + + need_restart_job = 1; + + uri_status = httpSeparateURI(HTTP_URI_CODING_ALL, + attr->values[0].string.text, + scheme, sizeof(scheme), + username, sizeof(username), + host, sizeof(host), &port, + resource, sizeof(resource)); + + if (uri_status < HTTP_URI_OK) { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Got a printer-uri attribute but no job-id!")); + send_ipp_status(con, IPP_NOT_POSSIBLE, _("Bad device-uri \"%s\"!"), + attr->values[0].string.text); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "add_printer: httpSeparateURI returned %d", uri_status); return; } - if ((jobid = attr->values[0].integer) == 0) + if (!strcmp(scheme, "file")) { /* - * Find the current job on the specified printer... + * See if the administrator has enabled file devices... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!FileDevice && strcmp(resource, "/dev/null")) { /* - * Bad URI... + * File devices are disabled and the URL is not file:/dev/null... */ - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer or class was not found.")); + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("File device URIs have been disabled! " + "To enable, see the FileDevice directive in " + "\"%s/cupsd.conf\"."), + ServerRoot); return; } - + } + else + { /* - * See if the printer is currently printing a job... + * See if the backend exists and is executable... */ - if (printer->job) - jobid = ((cupsd_job_t *)printer->job)->id; - else + snprintf(srcfile, sizeof(srcfile), "%s/backend/%s", ServerBin, scheme); + if (access(srcfile, X_OK)) { /* - * No, see if there are any pending jobs... + * Could not find device in list! */ - - for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); - job; - job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) - if (job->state->values[0].integer <= IPP_JOB_PROCESSING && - !strcasecmp(job->dest, dest)) - break; - if (job) - jobid = job->id; - else - { - send_ipp_status(con, IPP_NOT_POSSIBLE, _("No active jobs on %s!"), - dest); - return; - } + send_ipp_status(con, IPP_NOT_POSSIBLE, _("Bad device-uri scheme \"%s\"!"), + scheme); + return; } } + + if (printer->sanitized_device_uri) + strlcpy(old_device_uri, printer->sanitized_device_uri, + sizeof(old_device_uri)); + else + old_device_uri[0] = '\0'; + + cupsdSetDeviceURI(printer, attr->values[0].string.text); + + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s device-uri to \"%s\" (was \"%s\".)", + printer->name, printer->sanitized_device_uri, + old_device_uri); + + set_device_uri = 1; } - else + + set_port_monitor = 0; + + if ((attr = ippFindAttribute(con->request, "port-monitor", + IPP_TAG_NAME)) != NULL) { - /* - * Got a job URI; parse it to get the job ID... - */ + ipp_attribute_t *supported; /* port-monitor-supported attribute */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if (strncmp(resource, "/jobs/", 6)) + + need_restart_job = 1; + + supported = ippFindAttribute(printer->ppd_attrs, "port-monitor-supported", + IPP_TAG_NAME); + if (supported) { - /* - * Not a valid URI! - */ + for (i = 0; i < supported->num_values; i ++) + if (!strcmp(supported->values[i].string.text, + attr->values[0].string.text)) + break; + } - send_ipp_status(con, IPP_BAD_REQUEST, - _("Bad job-uri attribute \"%s\"!"), - uri->values[0].string.text); + if (!supported || i >= supported->num_values) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, _("Bad port-monitor \"%s\"!"), + attr->values[0].string.text); return; } - jobid = atoi(resource + 6); - } + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s port-monitor to \"%s\" (was \"%s\".)", + printer->name, attr->values[0].string.text, + printer->port_monitor ? printer->port_monitor : "none"); - /* - * See if the job exists... - */ + if (strcmp(attr->values[0].string.text, "none")) + cupsdSetString(&printer->port_monitor, attr->values[0].string.text); + else + cupsdClearString(&printer->port_monitor); - if ((job = cupsdFindJob(jobid)) == NULL) + set_port_monitor = 1; + } + + if ((attr = ippFindAttribute(con->request, "printer-is-accepting-jobs", + IPP_TAG_BOOLEAN)) != NULL) { - /* - * Nope - return a "not found" error... - */ + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s printer-is-accepting-jobs to %d (was %d.)", + printer->name, attr->values[0].boolean, printer->accepting); - send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist!"), jobid); - return; + printer->accepting = attr->values[0].boolean; + cupsdAddPrinterHistory(printer); } - /* - * See if the job is owned by the requesting user... - */ - - if (!validate_user(job, con, job->username, username, sizeof(username))) + if ((attr = ippFindAttribute(con->request, "printer-is-shared", + IPP_TAG_BOOLEAN)) != NULL) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to delete job #%d " - "owned by \"%s\"!"), - jobid, job->username); - return; - } + if (printer->shared && !attr->values[0].boolean) + cupsdDeregisterPrinter(printer, 1); - /* - * See if the job is already completed, cancelled, or aborted; if so, - * we can't cancel... - */ + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s printer-is-shared to %d (was %d.)", + printer->name, attr->values[0].boolean, printer->shared); - if (job->state->values[0].integer >= IPP_JOB_CANCELLED) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job #%d is already %s - can\'t cancel."), jobid, - job->state->values[0].integer == IPP_JOB_CANCELLED ? "cancelled" : - job->state->values[0].integer == IPP_JOB_ABORTED ? "aborted" : - "completed"); - return; + printer->shared = attr->values[0].boolean; } - /* - * Cancel the job and return... - */ - - cupsdAddEvent(CUPSD_EVENT_JOB_COMPLETED, job->printer, job, - "Job cancelled by \"%s\".", username); + if ((attr = ippFindAttribute(con->request, "printer-state", + IPP_TAG_ENUM)) != NULL) + { + if (attr->values[0].integer != IPP_PRINTER_IDLE && + attr->values[0].integer != IPP_PRINTER_STOPPED) + { + send_ipp_status(con, IPP_BAD_REQUEST, _("Bad printer-state value %d!"), + attr->values[0].integer); + return; + } - cupsdCancelJob(job, 0); - cupsdCheckJobs(); + cupsdLogMessage(CUPSD_LOG_INFO, "Setting %s printer-state to %d (was %d.)", + printer->name, attr->values[0].integer, printer->state); - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d was cancelled by \"%s\".", jobid, - username); + if (attr->values[0].integer == IPP_PRINTER_STOPPED) + cupsdStopPrinter(printer, 0); + else + { + need_restart_job = 1; + cupsdSetPrinterState(printer, (ipp_pstate_t)(attr->values[0].integer), 0); + } + } - con->response->request.status.status_code = IPP_OK; -} + if ((attr = ippFindAttribute(con->request, "printer-state-message", + IPP_TAG_TEXT)) != NULL) + { + strlcpy(printer->state_message, attr->values[0].string.text, + sizeof(printer->state_message)); + cupsdAddPrinterHistory(printer); + } + if ((attr = ippFindAttribute(con->request, "printer-state-reasons", + IPP_TAG_KEYWORD)) != NULL) + { + if (attr->num_values > + (int)(sizeof(printer->reasons) / sizeof(printer->reasons[0]))) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Too many printer-state-reasons values (%d > %d)!"), + attr->num_values, + (int)(sizeof(printer->reasons) / + sizeof(printer->reasons[0]))); + return; + } -/* - * 'cancel_subscription()' - Cancel a subscription. - */ + for (i = 0; i < printer->num_reasons; i ++) + _cupsStrFree(printer->reasons[i]); -static void -cancel_subscription( - cupsd_client_t *con, /* I - Client connection */ - int sub_id) /* I - Subscription ID */ -{ -} + printer->num_reasons = 0; + for (i = 0; i < attr->num_values; i ++) + { + if (!strcmp(attr->values[i].string.text, "none")) + continue; + printer->reasons[printer->num_reasons] = + _cupsStrRetain(attr->values[i].string.text); + printer->num_reasons ++; -/* - * 'check_quotas()' - Check quotas for a printer and user. - */ + if (!strcmp(attr->values[i].string.text, "paused") && + printer->state != IPP_PRINTER_STOPPED) + { + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s printer-state to %d (was %d.)", + printer->name, IPP_PRINTER_STOPPED, printer->state); + cupsdStopPrinter(printer, 0); + } + } -static int /* O - 1 if OK, 0 if not */ -check_quotas(cupsd_client_t *con, /* I - Client connection */ - cupsd_printer_t *p) /* I - Printer or class */ -{ - int i; /* Looping var */ - ipp_attribute_t *attr; /* Current attribute */ - char username[33]; /* Username */ - cupsd_quota_t *q; /* Quota data */ - struct passwd *pw; /* User password data */ + if (PrintcapFormat == PRINTCAP_PLIST) + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP); + } + set_printer_defaults(con, printer); - cupsdLogMessage(CUPSD_LOG_DEBUG2, "check_quotas(%p[%d], %p[%s])", - con, con->http.fd, p, p->name); + if ((attr = ippFindAttribute(con->request, "auth-info-required", + IPP_TAG_KEYWORD)) != NULL) + cupsdSetAuthInfoRequired(printer, NULL, attr); /* - * Check input... + * See if we have all required attributes... */ - if (!con || !p) - return (0); + if (!printer->device_uri) + cupsdSetString(&printer->device_uri, "file:///dev/null"); /* - * Figure out who is printing... + * See if we have an interface script or PPD file attached to the request... */ - attr = ippFindAttribute(con->request, "requesting-user-name", IPP_TAG_NAME); - - if (con->username[0]) - strlcpy(username, con->username, sizeof(username)); - else if (attr) + if (con->filename) { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "check_quotas: requesting-user-name = \"%s\"", - attr->values[0].string.text); - - strlcpy(username, attr->values[0].string.text, sizeof(username)); - } - else - strcpy(username, "anonymous"); - - /* - * Check global active job limits for printers and users... - */ + need_restart_job = 1; - if (MaxJobsPerPrinter) - { - /* - * Check if there are too many pending jobs on this printer... - */ + strlcpy(srcfile, con->filename, sizeof(srcfile)); - if (cupsdGetPrinterJobCount(p->name) >= MaxJobsPerPrinter) + if ((fp = cupsFileOpen(srcfile, "rb"))) { - cupsdLogMessage(CUPSD_LOG_INFO, "Too many jobs for printer \"%s\"...", - p->name); - return (0); - } - } + /* + * Yes; get the first line from it... + */ - if (MaxJobsPerUser) - { - /* - * Check if there are too many pending jobs for this user... - */ + line[0] = '\0'; + cupsFileGets(fp, line, sizeof(line)); + cupsFileClose(fp); - if (cupsdGetUserJobCount(username) >= MaxJobsPerUser) - { - cupsdLogMessage(CUPSD_LOG_INFO, "Too many jobs for user \"%s\"...", - username); - return (0); - } - } + /* + * Then see what kind of file it is... + */ - /* - * Check against users... - */ + snprintf(dstfile, sizeof(dstfile), "%s/interfaces/%s", ServerRoot, + printer->name); - if (p->num_users == 0 && p->k_limit == 0 && p->page_limit == 0) - return (1); + if (!strncmp(line, "*PPD-Adobe", 10)) + { + /* + * The new file is a PPD file, so remove any old interface script + * that might be lying around... + */ - if (p->num_users) - { - pw = getpwnam(username); - endpwent(); + unlink(dstfile); + } + else + { + /* + * This must be an interface script, so move the file over to the + * interfaces directory and make it executable... + */ - for (i = 0; i < p->num_users; i ++) - if (p->users[i][0] == '@') + if (copy_file(srcfile, dstfile)) + { + send_ipp_status(con, IPP_INTERNAL_ERROR, + _("Unable to copy interface script - %s!"), + strerror(errno)); + return; + } + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Copied interface script successfully!"); + chmod(dstfile, 0755); + } + + snprintf(dstfile, sizeof(dstfile), "%s/ppd/%s.ppd", ServerRoot, + printer->name); + + if (!strncmp(line, "*PPD-Adobe", 10)) { /* - * Check group membership... + * The new file is a PPD file, so move the file over to the + * ppd directory and make it readable by all... */ - if (cupsdCheckGroup(username, pw, p->users[i] + 1)) - break; + if (copy_file(srcfile, dstfile)) + { + send_ipp_status(con, IPP_INTERNAL_ERROR, + _("Unable to copy PPD file - %s!"), + strerror(errno)); + return; + } + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Copied PPD file successfully!"); + chmod(dstfile, 0644); + +#ifdef __APPLE__ + /* + * (Re)register color profiles... + */ + + if (!RunUser) + { + apple_unregister_profiles(printer); + apple_register_profiles(printer); + } +#endif /* __APPLE__ */ } - else if (!strcasecmp(username, p->users[i])) - break; + else + { + /* + * This must be an interface script, so remove any old PPD file that + * may be lying around... + */ - if ((i < p->num_users) == p->deny_users) + unlink(dstfile); + } + } + } + else if ((attr = ippFindAttribute(con->request, "ppd-name", + IPP_TAG_NAME)) != NULL) + { + need_restart_job = 1; + + if (!strcmp(attr->values[0].string.text, "raw")) { - cupsdLogMessage(CUPSD_LOG_INFO, - "Denying user \"%s\" access to printer \"%s\"...", - username, p->name); - return (0); + /* + * Raw driver, remove any existing PPD or interface script files. + */ + + snprintf(dstfile, sizeof(dstfile), "%s/interfaces/%s", ServerRoot, + printer->name); + unlink(dstfile); + + snprintf(dstfile, sizeof(dstfile), "%s/ppd/%s.ppd", ServerRoot, + printer->name); + unlink(dstfile); + } + else + { + /* + * PPD model file... + */ + + snprintf(dstfile, sizeof(dstfile), "%s/interfaces/%s", ServerRoot, + printer->name); + unlink(dstfile); + + snprintf(dstfile, sizeof(dstfile), "%s/ppd/%s.ppd", ServerRoot, + printer->name); + + if (copy_model(con, attr->values[0].string.text, dstfile)) + { + send_ipp_status(con, IPP_INTERNAL_ERROR, _("Unable to copy PPD file!")); + return; + } + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Copied PPD file successfully!"); + chmod(dstfile, 0644); + +#ifdef __APPLE__ + /* + * (Re)register color profiles... + */ + + if (!RunUser) + { + apple_unregister_profiles(printer); + apple_register_profiles(printer); + } +#endif /* __APPLE__ */ } } /* - * Check quotas... + * If we set the device URI but not the port monitor, check which port + * monitor to use by default... */ - if (p->k_limit || p->page_limit) + if (set_device_uri && !set_port_monitor) { - if ((q = cupsdUpdateQuota(p, username, 0, 0)) == NULL) - { - cupsdLogMessage(CUPSD_LOG_ERROR, - "Unable to allocate quota data for user \"%s\"!", - username); - return (0); - } + ppd_file_t *ppd; /* PPD file */ + ppd_attr_t *ppdattr; /* cupsPortMonitor attribute */ - if ((q->k_count >= p->k_limit && p->k_limit) || - (q->page_count >= p->page_limit && p->page_limit)) + + httpSeparateURI(HTTP_URI_CODING_ALL, printer->device_uri, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); + + snprintf(srcfile, sizeof(srcfile), "%s/ppd/%s.ppd", ServerRoot, + printer->name); + if ((ppd = ppdOpenFile(srcfile)) != NULL) { - cupsdLogMessage(CUPSD_LOG_INFO, "User \"%s\" is over the quota limit...", - username); - return (0); + for (ppdattr = ppdFindAttr(ppd, "cupsPortMonitor", NULL); + ppdattr; + ppdattr = ppdFindNextAttr(ppd, "cupsPortMonitor", NULL)) + if (!strcmp(scheme, ppdattr->spec)) + { + cupsdLogMessage(CUPSD_LOG_INFO, + "Setting %s port-monitor to \"%s\" (was \"%s\".)", + printer->name, ppdattr->value, + printer->port_monitor ? printer->port_monitor + : "none"); + + if (strcmp(ppdattr->value, "none")) + cupsdSetString(&printer->port_monitor, ppdattr->value); + else + cupsdClearString(&printer->port_monitor); + + break; + } + + ppdClose(ppd); } } /* - * If we have gotten this far, we're done! + * Update the printer attributes and return... */ - return (1); + cupsdSetPrinterAttrs(printer); + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); + + if (need_restart_job && printer->job) + { + /* + * Restart the current job... + */ + + cupsdSetJobState(printer->job, IPP_JOB_PENDING, CUPSD_JOB_FORCE, + "Job restarted because the printer was modified."); + } + + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP); + + if (modify) + { + cupsdAddEvent(CUPSD_EVENT_PRINTER_MODIFIED, printer, NULL, + "Printer \"%s\" modified by \"%s\".", printer->name, + get_username(con)); + + cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" modified by \"%s\".", + printer->name, get_username(con)); + } + else + { + cupsdAddPrinterHistory(printer); + + cupsdAddEvent(CUPSD_EVENT_PRINTER_ADDED, printer, NULL, + "New printer \"%s\" added by \"%s\".", printer->name, + get_username(con)); + + cupsdLogMessage(CUPSD_LOG_INFO, "New printer \"%s\" added by \"%s\".", + printer->name, get_username(con)); + } + + con->response->request.status.status_code = IPP_OK; } /* - * 'copy_attribute()' - Copy a single attribute. + * 'add_printer_state_reasons()' - Add the "printer-state-reasons" attribute + * based upon the printer state... */ -static ipp_attribute_t * /* O - New attribute */ -copy_attribute( - ipp_t *to, /* O - Destination request/response */ - ipp_attribute_t *attr, /* I - Attribute to copy */ - int quickcopy) /* I - Do a quick copy? */ +static void +add_printer_state_reasons( + cupsd_client_t *con, /* I - Client connection */ + cupsd_printer_t *p) /* I - Printer info */ { - int i; /* Looping var */ - ipp_attribute_t *toattr; /* Destination attribute */ + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "add_printer_state_reasons(%p[%d], %p[%s])", + con, con->http.fd, p, p->name); + if (p->num_reasons == 0) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "printer-state-reasons", NULL, "none"); + else + ippAddStrings(con->response, IPP_TAG_PRINTER, IPP_TAG_KEYWORD, + "printer-state-reasons", p->num_reasons, NULL, + (const char * const *)p->reasons); +} - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "copy_attribute(%p, %p[%s,%x,%x])", to, attr, - attr->name ? attr->name : "(null)", attr->group_tag, - attr->value_tag); - switch (attr->value_tag & ~IPP_TAG_COPY) - { - case IPP_TAG_ZERO : - toattr = ippAddSeparator(to); - break; +/* + * 'add_queued_job_count()' - Add the "queued-job-count" attribute for + * the specified printer or class. + */ - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - toattr = ippAddIntegers(to, attr->group_tag, attr->value_tag, - attr->name, attr->num_values, NULL); +static void +add_queued_job_count( + cupsd_client_t *con, /* I - Client connection */ + cupsd_printer_t *p) /* I - Printer or class */ +{ + int count; /* Number of jobs on destination */ - for (i = 0; i < attr->num_values; i ++) - toattr->values[i].integer = attr->values[i].integer; - break; - case IPP_TAG_BOOLEAN : - toattr = ippAddBooleans(to, attr->group_tag, attr->name, - attr->num_values, NULL); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "add_queued_job_count(%p[%d], %p[%s])", + con, con->http.fd, p, p->name); - for (i = 0; i < attr->num_values; i ++) - toattr->values[i].boolean = attr->values[i].boolean; - break; + count = cupsdGetPrinterJobCount(p->name); - case IPP_TAG_STRING : - case IPP_TAG_TEXT : - case IPP_TAG_NAME : - case IPP_TAG_KEYWORD : - case IPP_TAG_URI : - case IPP_TAG_URISCHEME : - case IPP_TAG_CHARSET : - case IPP_TAG_LANGUAGE : - case IPP_TAG_MIMETYPE : - toattr = ippAddStrings(to, attr->group_tag, - (ipp_tag_t)(attr->value_tag | quickcopy), - attr->name, attr->num_values, NULL, NULL); - - if (quickcopy) - { - for (i = 0; i < attr->num_values; i ++) - toattr->values[i].string.text = attr->values[i].string.text; - } - else - { - for (i = 0; i < attr->num_values; i ++) - toattr->values[i].string.text = strdup(attr->values[i].string.text); - } - break; - - case IPP_TAG_DATE : - toattr = ippAddDate(to, attr->group_tag, attr->name, - attr->values[0].date); - break; - - case IPP_TAG_RESOLUTION : - toattr = ippAddResolutions(to, attr->group_tag, attr->name, - attr->num_values, IPP_RES_PER_INCH, - NULL, NULL); - - for (i = 0; i < attr->num_values; i ++) - { - toattr->values[i].resolution.xres = attr->values[i].resolution.xres; - toattr->values[i].resolution.yres = attr->values[i].resolution.yres; - toattr->values[i].resolution.units = attr->values[i].resolution.units; - } - break; + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "queued-job-count", count); +} - case IPP_TAG_RANGE : - toattr = ippAddRanges(to, attr->group_tag, attr->name, - attr->num_values, NULL, NULL); - for (i = 0; i < attr->num_values; i ++) - { - toattr->values[i].range.lower = attr->values[i].range.lower; - toattr->values[i].range.upper = attr->values[i].range.upper; - } - break; +#ifdef __APPLE__ +/* + * 'apple_init_profile()' - Initialize a color profile. + */ - case IPP_TAG_TEXTLANG : - case IPP_TAG_NAMELANG : - toattr = ippAddStrings(to, attr->group_tag, - (ipp_tag_t)(attr->value_tag | quickcopy), - attr->name, attr->num_values, NULL, NULL); +static void +apple_init_profile( + ppd_file_t *ppd, /* I - PPD file */ + cups_array_t *languages, /* I - Languages in the PPD file */ + CMDeviceProfileInfo *profile, /* I - Profile record */ + unsigned id, /* I - Profile ID */ + const char *name, /* I - Profile name */ + const char *text, /* I - Profile UI text */ + const char *iccfile) /* I - ICC filename */ +{ + char url[1024]; /* URL for profile filename */ + CFMutableDictionaryRef dict; /* Dictionary for name */ + char *language; /* Current language */ + ppd_attr_t *attr; /* Profile attribute */ + CFStringRef cflang, /* Language string */ + cftext; /* Localized text */ - if (quickcopy) - { - for (i = 0; i < attr->num_values; i ++) - { - toattr->values[i].string.charset = attr->values[i].string.charset; - toattr->values[i].string.text = attr->values[i].string.text; - } - } - else - { - for (i = 0; i < attr->num_values; i ++) - { - if (!i) - toattr->values[i].string.charset = - strdup(attr->values[i].string.charset); - else - toattr->values[i].string.charset = - toattr->values[0].string.charset; - toattr->values[i].string.text = strdup(attr->values[i].string.text); - } - } - break; + /* + * Build the profile name dictionary... + */ - case IPP_TAG_BEGIN_COLLECTION : - toattr = ippAddCollections(to, attr->group_tag, attr->name, - attr->num_values, NULL); + dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); - for (i = 0; i < attr->num_values; i ++) - { - toattr->values[i].collection = ippNew(); - copy_attrs(toattr->values[i].collection, attr->values[i].collection, - NULL, IPP_TAG_ZERO, 0); - } - break; + cftext = CFStringCreateWithCString(kCFAllocatorDefault, text, + kCFStringEncodingUTF8); - default : - toattr = ippAddIntegers(to, attr->group_tag, attr->value_tag, - attr->name, attr->num_values, NULL); + if (cftext) + { + CFDictionarySetValue(dict, CFSTR("en"), cftext); + CFRelease(cftext); + } - for (i = 0; i < attr->num_values; i ++) - { - toattr->values[i].unknown.length = attr->values[i].unknown.length; + if (languages) + { + /* + * Find localized names for the color profiles... + */ - if (toattr->values[i].unknown.length > 0) - { - if ((toattr->values[i].unknown.data = - malloc(toattr->values[i].unknown.length)) == NULL) - toattr->values[i].unknown.length = 0; - else - memcpy(toattr->values[i].unknown.data, - attr->values[i].unknown.data, - toattr->values[i].unknown.length); - } - } - break; /* anti-compiler-warning-code */ - } + cupsArraySave(ppd->sorted_attrs); - return (toattr); -} + for (language = (char *)cupsArrayFirst(languages); + language; + language = (char *)cupsArrayNext(languages)) + { + if (iccfile) + { + if ((attr = _ppdLocalizedAttr(ppd, "cupsICCProfile", name, + language)) == NULL) + attr = _ppdLocalizedAttr(ppd, "APTiogaProfile", name, language); + } + else + attr = _ppdLocalizedAttr(ppd, "ColorModel", name, language); + if (attr && attr->text[0]) + { + cflang = CFStringCreateWithCString(kCFAllocatorDefault, language, + kCFStringEncodingUTF8); + cftext = CFStringCreateWithCString(kCFAllocatorDefault, attr->text, + kCFStringEncodingUTF8); -/* - * 'copy_attrs()' - Copy attributes from one request to another. - */ + if (cflang && cftext) + CFDictionarySetValue(dict, cflang, cftext); -static void -copy_attrs(ipp_t *to, /* I - Destination request */ - ipp_t *from, /* I - Source request */ - cups_array_t *ra, /* I - Requested attributes */ - ipp_tag_t group, /* I - Group to copy */ - int quickcopy) /* I - Do a quick copy? */ -{ - ipp_attribute_t *fromattr; /* Source attribute */ + if (cflang) + CFRelease(cflang); + if (cftext) + CFRelease(cftext); + } + } - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "copy_attrs(to=%p, from=%p, ra=%p, group=%x, quickcopy=%d)", - to, from, ra, group, quickcopy); + cupsArrayRestore(ppd->sorted_attrs); + } - if (!to || !from) - return; + /* + * Fill in the profile data... + */ - for (fromattr = from->attrs; fromattr; fromattr = fromattr->next) - { - /* - * Filter attributes as needed... - */ + if (iccfile) + httpAssembleURI(HTTP_URI_CODING_ALL, url, sizeof(url), "file", NULL, "", 0, + iccfile); - if (group != IPP_TAG_ZERO && fromattr->group_tag != group && - fromattr->group_tag != IPP_TAG_ZERO && !fromattr->name) - continue; + profile->dataVersion = cmDeviceProfileInfoVersion1; + profile->profileID = id; + profile->profileLoc.locType = iccfile ? cmPathBasedProfile : cmNoProfileBase; + profile->profileName = dict; - if (!ra || cupsArrayFind(ra, fromattr->name)) - copy_attribute(to, fromattr, quickcopy); - } + if (iccfile) + strlcpy(profile->profileLoc.u.pathLoc.path, iccfile, + sizeof(profile->profileLoc.u.pathLoc.path)); } /* - * 'copy_banner()' - Copy a banner file to the requests directory for the - * specified job. + * 'apple_register_profiles()' - Register color profiles for a printer. */ -static int /* O - Size of banner file in kbytes */ -copy_banner(cupsd_client_t *con, /* I - Client connection */ - cupsd_job_t *job, /* I - Job information */ - const char *name) /* I - Name of banner */ +static void +apple_register_profiles( + cupsd_printer_t *p) /* I - Printer */ { - int i; /* Looping var */ - int kbytes; /* Size of banner file in kbytes */ - char filename[1024]; /* Job filename */ - cupsd_banner_t *banner; /* Pointer to banner */ - cups_file_t *in; /* Input file */ - cups_file_t *out; /* Output file */ - int ch; /* Character from file */ - char attrname[255], /* Name of attribute */ - *s; /* Pointer into name */ - ipp_attribute_t *attr; /* Attribute */ - - - cupsdLogMessage(CUPSD_LOG_DEBUG2, "copy_banner(%p[%d], %p[%d], %s)", - con, con->http.fd, job, job->id, name ? name : "(null)"); + int i; /* Looping var */ + char ppdfile[1024], /* PPD filename */ + iccfile[1024], /* ICC filename */ + selector[PPD_MAX_NAME]; + /* Profile selection string */ + ppd_file_t *ppd; /* PPD file */ + ppd_attr_t *attr, /* Profile attributes */ + *profileid_attr,/* cupsProfileID attribute */ + *q1_attr, /* ColorModel (or other) qualifier */ + *q2_attr, /* MediaType (or other) qualifier */ + *q3_attr; /* Resolution (or other) qualifier */ + char q_keyword[PPD_MAX_NAME]; + /* Qualifier keyword */ + const char *q1_choice, /* ColorModel (or other) choice */ + *q2_choice, /* MediaType (or other) choice */ + *q3_choice; /* Resolution (or other) choice */ + const char *profile_key; /* Profile keyword */ + ppd_option_t *cm_option; /* Color model option */ + ppd_choice_t *cm_choice; /* Color model choice */ + int num_profiles; /* Number of profiles */ + CMError error; /* Last error */ + unsigned device_id, /* Printer device ID */ + profile_id, /* Profile ID */ + default_profile_id = 0; + /* Default profile ID */ + CFMutableDictionaryRef device_name; /* Printer device name dictionary */ + CFStringRef printer_name; /* Printer name string */ + CMDeviceScope scope = /* Scope of the registration */ + { + kCFPreferencesAnyUser, + kCFPreferencesCurrentHost + }; + CMDeviceProfileArrayPtr profiles; /* Profiles */ + CMDeviceProfileInfo *profile; /* Current profile */ + cups_array_t *languages; /* Languages array */ + + + /* + * Make sure ColorSync is available... + */ + + if (CMRegisterColorDevice == NULL) + return; /* - * Find the banner; return if not found or "none"... + * Try opening the PPD file for this printer... */ - if (!name || !strcmp(name, "none") || - (banner = cupsdFindBanner(name)) == NULL) - return (0); + snprintf(ppdfile, sizeof(ppdfile), "%s/ppd/%s.ppd", ServerRoot, p->name); + if ((ppd = ppdOpenFile(ppdfile)) == NULL) + return; /* - * Open the banner and job files... + * See if we have any profiles... */ - if (add_file(con, job, banner->filetype, 0)) - return (0); - - snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, - job->num_files); - if ((out = cupsFileOpen(filename, "w")) == NULL) + if ((attr = ppdFindAttr(ppd, "APTiogaProfile", NULL)) != NULL) + profile_key = "APTiogaProfile"; + else { - cupsdLogMessage(CUPSD_LOG_ERROR, - "copy_banner: Unable to create banner job file %s - %s", - filename, strerror(errno)); - job->num_files --; - return (0); + attr = ppdFindAttr(ppd, "cupsICCProfile", NULL); + profile_key = "cupsICCProfile"; } - fchmod(cupsFileNumber(out), 0640); - fchown(cupsFileNumber(out), RunUser, Group); + for (num_profiles = 0; attr; attr = ppdFindNextAttr(ppd, profile_key, NULL)) + if (attr->spec[0] && attr->value && attr->value[0]) + { + if (attr->value[0] != '/') + snprintf(iccfile, sizeof(iccfile), "%s/profiles/%s", DataDir, + attr->value); + else + strlcpy(iccfile, attr->value, sizeof(iccfile)); + + if (access(iccfile, 0)) + continue; + + num_profiles ++; + } + /* - * Try the localized banner file under the subdirectory... + * If we have profiles, add them... */ - strlcpy(attrname, con->request->attrs->next->values[0].string.text, - sizeof(attrname)); - if (strlen(attrname) > 2 && attrname[2] == '-') + if (num_profiles > 0) { - /* - * Convert ll-cc to ll_CC... - */ + if (profile_key[0] == 'A') + { + /* + * For Tioga PPDs, get the default profile using the DefaultAPTiogaProfile + * attribute... + */ - attrname[2] = '_'; - attrname[3] = toupper(attrname[3] & 255); - attrname[4] = toupper(attrname[4] & 255); - } + if ((attr = ppdFindAttr(ppd, "DefaultAPTiogaProfile", NULL)) != NULL && + attr->value) + default_profile_id = atoi(attr->value); - snprintf(filename, sizeof(filename), "%s/banners/%s/%s", DataDir, - attrname, name); + q1_choice = q2_choice = q3_choice = NULL; + } + else + { + /* + * For CUPS PPDs, figure out the default profile selector values... + */ - if (access(filename, 0) && strlen(attrname) > 2) - { - /* - * Wasn't able to find "ll_CC" locale file; try the non-national - * localization banner directory. - */ + if ((attr = ppdFindAttr(ppd, "cupsICCQualifier1", NULL)) != NULL && + attr->value && attr->value[0]) + { + snprintf(q_keyword, sizeof(q_keyword), "Default%s", attr->value); + q1_attr = ppdFindAttr(ppd, q_keyword, NULL); + } + else if ((q1_attr = ppdFindAttr(ppd, "DefaultColorModel", NULL)) == NULL) + q1_attr = ppdFindAttr(ppd, "DefaultColorSpace", NULL); - attrname[2] = '\0'; + if (q1_attr && q1_attr->value && q1_attr->value[0]) + q1_choice = q1_attr->value; + else + q1_choice = ""; - snprintf(filename, sizeof(filename), "%s/banners/%s/%s", DataDir, - attrname, name); - } + if ((attr = ppdFindAttr(ppd, "cupsICCQualifier2", NULL)) != NULL && + attr->value && attr->value[0]) + { + snprintf(q_keyword, sizeof(q_keyword), "Default%s", attr->value); + q2_attr = ppdFindAttr(ppd, q_keyword, NULL); + } + else + q2_attr = ppdFindAttr(ppd, "DefaultMediaType", NULL); + + if (q2_attr && q2_attr->value && q2_attr->value[0]) + q2_choice = q2_attr->value; + else + q2_choice = NULL; + + if ((attr = ppdFindAttr(ppd, "cupsICCQualifier3", NULL)) != NULL && + attr->value && attr->value[0]) + { + snprintf(q_keyword, sizeof(q_keyword), "Default%s", attr->value); + q3_attr = ppdFindAttr(ppd, q_keyword, NULL); + } + else + q3_attr = ppdFindAttr(ppd, "DefaultResolution", NULL); + + if (q3_attr && q3_attr->value && q3_attr->value[0]) + q3_choice = q3_attr->value; + else + q3_choice = NULL; + } - if (access(filename, 0)) - { /* - * Use the non-localized banner file. + * Build the array of profiles... + * + * Note: This calloc actually requests slightly more memory than needed. */ - snprintf(filename, sizeof(filename), "%s/banners/%s", DataDir, name); - } + if ((profiles = calloc(num_profiles, sizeof(CMDeviceProfileArray))) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to allocate memory for %d profiles!", + num_profiles); + ppdClose(ppd); + return; + } - if ((in = cupsFileOpen(filename, "r")) == NULL) - { - cupsFileClose(out); - unlink(filename); - cupsdLogMessage(CUPSD_LOG_ERROR, - "copy_banner: Unable to open banner template file %s - %s", - filename, strerror(errno)); - job->num_files --; - return (0); - } + profiles->profileCount = num_profiles; + languages = _ppdGetLanguages(ppd); - /* - * Parse the file to the end... - */ + for (profile = profiles->profiles, + attr = ppdFindAttr(ppd, profile_key, NULL); + attr; + attr = ppdFindNextAttr(ppd, profile_key, NULL)) + if (attr->spec[0] && attr->value && attr->value[0]) + { + /* + * Add this profile... + */ - while ((ch = cupsFileGetChar(in)) != EOF) - if (ch == '{') - { - /* - * Get an attribute name... - */ + if (attr->value[0] != '/') + snprintf(iccfile, sizeof(iccfile), "%s/profiles/%s", DataDir, + attr->value); + else + strlcpy(iccfile, attr->value, sizeof(iccfile)); - for (s = attrname; (ch = cupsFileGetChar(in)) != EOF;) - if (!isalpha(ch & 255) && ch != '-' && ch != '?') - break; - else if (s < (attrname + sizeof(attrname) - 1)) - *s++ = ch; - else - break; + if (access(iccfile, 0)) + continue; - *s = '\0'; + if (profile_key[0] == 'c') + { + cupsArraySave(ppd->sorted_attrs); - if (ch != '}') - { - /* - * Ignore { followed by stuff that is not an attribute name... - */ + if ((profileid_attr = ppdFindAttr(ppd, "cupsProfileID", + attr->spec)) != NULL && + profileid_attr->value && isdigit(profileid_attr->value[0] & 255)) + profile_id = (unsigned)strtoul(profileid_attr->value, NULL, 10); + else + profile_id = _ppdHashName(attr->spec); - cupsFilePrintf(out, "{%s%c", attrname, ch); - continue; - } + cupsArrayRestore(ppd->sorted_attrs); + } + else + profile_id = atoi(attr->spec); - /* - * See if it is defined... - */ + apple_init_profile(ppd, languages, profile, profile_id, attr->spec, + attr->text[0] ? attr->text : attr->spec, iccfile); - if (attrname[0] == '?') - s = attrname + 1; - else - s = attrname; + profile ++; - if (!strcmp(s, "printer-name")) - { - cupsFilePuts(out, job->dest); - continue; - } - else if ((attr = ippFindAttribute(job->attrs, s, IPP_TAG_ZERO)) == NULL) - { /* - * See if we have a leading question mark... + * See if this is the default profile... */ - if (attrname[0] != '?') + if (!default_profile_id) { - /* - * Nope, write to file as-is; probably a PostScript procedure... - */ + if (q2_choice) + { + if (q3_choice) + { + snprintf(selector, sizeof(selector), "%s.%s.%s", + q1_choice, q2_choice, q3_choice); + if (!strcmp(selector, attr->spec)) + default_profile_id = profile_id; + } - cupsFilePrintf(out, "{%s}", attrname); - } + if (!default_profile_id) + { + snprintf(selector, sizeof(selector), "%s.%s.", q1_choice, + q2_choice); + if (!strcmp(selector, attr->spec)) + default_profile_id = profile_id; + } + } - continue; + if (!default_profile_id && q3_choice) + { + snprintf(selector, sizeof(selector), "%s..%s", q1_choice, + q3_choice); + if (!strcmp(selector, attr->spec)) + default_profile_id = profile_id; + } + + if (!default_profile_id) + { + snprintf(selector, sizeof(selector), "%s..", q1_choice); + if (!strcmp(selector, attr->spec)) + default_profile_id = profile_id; + } + } } - /* - * Output value(s)... - */ + _ppdFreeLanguages(languages); + } + else if ((cm_option = ppdFindOption(ppd, "ColorModel")) != NULL) + { + /* + * Extract profiles from ColorModel option... + */ - for (i = 0; i < attr->num_values; i ++) - { - if (i) - cupsFilePutChar(out, ','); + const char *profile_name; /* Name of generic profile */ - switch (attr->value_tag) - { - case IPP_TAG_INTEGER : - case IPP_TAG_ENUM : - if (!strncmp(s, "time-at-", 8)) - cupsFilePuts(out, cupsdGetDateTime(attr->values[i].integer)); - else - cupsFilePrintf(out, "%d", attr->values[i].integer); - break; - case IPP_TAG_BOOLEAN : - cupsFilePrintf(out, "%d", attr->values[i].boolean); - break; + num_profiles = cm_option->num_choices; - case IPP_TAG_NOVALUE : - cupsFilePuts(out, "novalue"); - break; + if ((profiles = calloc(num_profiles, sizeof(CMDeviceProfileArray))) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to allocate memory for %d profiles!", + num_profiles); + ppdClose(ppd); + return; + } - case IPP_TAG_RANGE : - cupsFilePrintf(out, "%d-%d", attr->values[i].range.lower, - attr->values[i].range.upper); - break; + profiles->profileCount = num_profiles; - case IPP_TAG_RESOLUTION : - cupsFilePrintf(out, "%dx%d%s", attr->values[i].resolution.xres, - attr->values[i].resolution.yres, - attr->values[i].resolution.units == IPP_RES_PER_INCH ? - "dpi" : "dpc"); - break; + for (profile = profiles->profiles, i = cm_option->num_choices, + cm_choice = cm_option->choices; + i > 0; + i --, cm_choice ++, profile ++) + { + if (!strcmp(cm_choice->choice, "Gray") || + !strcmp(cm_choice->choice, "Black")) + profile_name = "Gray"; + else if (!strcmp(cm_choice->choice, "RGB") || + !strcmp(cm_choice->choice, "CMY")) + profile_name = "RGB"; + else if (!strcmp(cm_choice->choice, "CMYK") || + !strcmp(cm_choice->choice, "KCMY")) + profile_name = "CMYK"; + else + profile_name = "DeviceN"; - case IPP_TAG_URI : - case IPP_TAG_STRING : - case IPP_TAG_TEXT : - case IPP_TAG_NAME : - case IPP_TAG_KEYWORD : - case IPP_TAG_CHARSET : - case IPP_TAG_LANGUAGE : - if (!strcasecmp(banner->filetype->type, "postscript")) - { - /* - * Need to quote strings for PS banners... - */ + snprintf(selector, sizeof(selector), "%s..", profile_name); + profile_id = _ppdHashName(selector); - const char *p; + apple_init_profile(ppd, NULL, profile, profile_id, cm_choice->choice, + cm_choice->text, NULL); - for (p = attr->values[i].string.text; *p; p ++) - { - if (*p == '(' || *p == ')' || *p == '\\') - { - cupsFilePutChar(out, '\\'); - cupsFilePutChar(out, *p); - } - else if (*p < 32 || *p > 126) - cupsFilePrintf(out, "\\%03o", *p & 255); - else - cupsFilePutChar(out, *p); - } - } - else - cupsFilePuts(out, attr->values[i].string.text); - break; + if (cm_choice->marked) + default_profile_id = profile_id; + } + } + else + { + /* + * Use the default colorspace... + */ - default : - break; /* anti-compiler-warning-code */ - } - } + attr = ppdFindAttr(ppd, "DefaultColorSpace", NULL); + + num_profiles = (attr && ppd->colorspace == PPD_CS_GRAY) ? 1 : 2; + + if ((profiles = calloc(num_profiles, sizeof(CMDeviceProfileArray))) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to allocate memory for %d profiles!", + num_profiles); + ppdClose(ppd); + return; } - else if (ch == '\\') /* Quoted char */ + + profiles->profileCount = num_profiles; + + apple_init_profile(ppd, NULL, profiles->profiles, _ppdHashName("Gray.."), + "Gray", "Gray", NULL); + + switch (ppd->colorspace) { - ch = cupsFileGetChar(in); + case PPD_CS_RGB : + case PPD_CS_CMY : + apple_init_profile(ppd, NULL, profiles->profiles + 1, + _ppdHashName("RGB.."), "RGB", "RGB", NULL); + break; + case PPD_CS_RGBK : + case PPD_CS_CMYK : + apple_init_profile(ppd, NULL, profiles->profiles + 1, + _ppdHashName("CMYK.."), "CMYK", "CMYK", NULL); + break; - if (ch != '{') /* Only do special handling for \{ */ - cupsFilePutChar(out, '\\'); + case PPD_CS_GRAY : + if (attr) + break; - cupsFilePutChar(out, ch); + case PPD_CS_N : + apple_init_profile(ppd, NULL, profiles->profiles + 1, + _ppdHashName("DeviceN.."), "DeviceN", "DeviceN", + NULL); + break; + } + } + + if (num_profiles > 0) + { + /* + * Make sure we have a default profile ID... + */ + + if (!default_profile_id) + default_profile_id = profiles->profiles[num_profiles - 1].profileID; + + /* + * Get the device ID hash and pathelogical name dictionary. + */ + + cupsdLogMessage(CUPSD_LOG_INFO, "Registering ICC color profiles for \"%s\"", + p->name); + + device_id = _ppdHashName(p->name); + device_name = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + printer_name = CFStringCreateWithCString(kCFAllocatorDefault, + p->name, kCFStringEncodingUTF8); + + if (device_name && printer_name) + { + CFDictionarySetValue(device_name, CFSTR("en"), printer_name); + + /* + * Register the device with ColorSync... + */ + + error = CMRegisterColorDevice(cmPrinterDeviceClass, device_id, + device_name, &scope); + + /* + * Register the profiles... + */ + + if (error == noErr) + error = CMSetDeviceFactoryProfiles(cmPrinterDeviceClass, device_id, + default_profile_id, profiles); } else - cupsFilePutChar(out, ch); + error = 1000; - cupsFileClose(in); + /* + * Clean up... + */ - kbytes = (cupsFileTell(out) + 1023) / 1024; + if (error != noErr) + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to register ICC color profiles for \"%s\" - %d", + p->name, (int)error); - if ((attr = ippFindAttribute(job->attrs, "job-k-octets", - IPP_TAG_INTEGER)) != NULL) - attr->values[0].integer += kbytes; + for (profile = profiles->profiles; + num_profiles > 0; + profile ++, num_profiles --) + CFRelease(profile->profileName); - cupsFileClose(out); + free(profiles); - return (kbytes); + if (printer_name) + CFRelease(printer_name); + + if (device_name) + CFRelease(device_name); + } + + ppdClose(ppd); } /* - * 'copy_file()' - Copy a PPD file or interface script... + * 'apple_unregister_profiles()' - Remove color profiles for the specified + * printer. */ -static int /* O - 0 = success, -1 = error */ -copy_file(const char *from, /* I - Source file */ - const char *to) /* I - Destination file */ +static void +apple_unregister_profiles( + cupsd_printer_t *p) /* I - Printer */ { - cups_file_t *src, /* Source file */ - *dst; /* Destination file */ - int bytes; /* Bytes to read/write */ - char buffer[2048]; /* Copy buffer */ - - - cupsdLogMessage(CUPSD_LOG_DEBUG2, "copy_file(\"%s\", \"%s\")", from, to); - /* - * Open the source and destination file for a copy... + * Make sure ColorSync is available... */ - if ((src = cupsFileOpen(from, "rb")) == NULL) - return (-1); + if (CMUnregisterColorDevice != NULL) + CMUnregisterColorDevice(cmPrinterDeviceClass, _ppdHashName(p->name)); +} +#endif /* __APPLE__ */ + +/* + * 'apply_printer_defaults()' - Apply printer default options to a job. + */ + +static void +apply_printer_defaults( + cupsd_printer_t *printer, /* I - Printer */ + cupsd_job_t *job) /* I - Job */ +{ + int i, /* Looping var */ + num_options; /* Number of default options */ + cups_option_t *options, /* Default options */ + *option; /* Current option */ - if ((dst = cupsFileOpen(to, "wb")) == NULL) - { - cupsFileClose(src); - return (-1); - } /* - * Copy the source file to the destination... + * Collect all of the default options and add the missing ones to the + * job object... */ - while ((bytes = cupsFileRead(src, buffer, sizeof(buffer))) > 0) - if (cupsFileWrite(dst, buffer, bytes) < bytes) + for (i = printer->num_options, num_options = 0, options = NULL, + option = printer->options; + i > 0; + i --, option ++) + if (!ippFindAttribute(job->attrs, option->name, IPP_TAG_ZERO)) { - cupsFileClose(src); - cupsFileClose(dst); - return (-1); + num_options = cupsAddOption(option->name, option->value, num_options, + &options); } /* - * Close both files and return... + * Encode these options as attributes in the job object... */ - cupsFileClose(src); - - return (cupsFileClose(dst)); + cupsEncodeOptions2(job->attrs, num_options, options, IPP_TAG_JOB); + cupsFreeOptions(num_options, options); } /* - * 'copy_model()' - Copy a PPD model file, substituting default values - * as needed... + * 'authenticate_job()' - Set job authentication info. */ -static int /* O - 0 = success, -1 = error */ -copy_model(cupsd_client_t *con, /* I - Client connection */ - const char *from, /* I - Source file */ - const char *to) /* I - Destination file */ +static void +authenticate_job(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Job URI */ { - fd_set *input; /* select() input set */ - struct timeval timeout; /* select() timeout */ - int maxfd; /* Maximum file descriptor for select() */ - char tempfile[1024]; /* Temporary PPD file */ - int tempfd; /* Temporary PPD file descriptor */ - int temppid; /* Process ID of cups-driverd */ - int temppipe[2]; /* Temporary pipes */ - char *argv[4], /* Command-line arguments */ - *envp[100]; /* Environment */ - cups_file_t *src, /* Source file */ - *dst; /* Destination file */ - int bytes, /* Bytes from pipe */ - total; /* Total bytes from pipe */ - char buffer[2048], /* Copy buffer */ - *ptr; /* Pointer into buffer */ - int i; /* Looping var */ - char option[PPD_MAX_NAME], /* Option name */ - choice[PPD_MAX_NAME]; /* Choice name */ - int num_defaults; /* Number of default options */ - ppd_default_t *defaults; /* Default options */ - char cups_protocol[PPD_MAX_LINE]; - /* cupsProtocol attribute */ - int have_letter, /* Have Letter size */ - have_a4; /* Have A4 size */ -#ifdef HAVE_LIBPAPER - char *paper_result; /* Paper size name from libpaper */ - char system_paper[64]; /* Paper size name buffer */ -#endif /* HAVE_LIBPAPER */ + ipp_attribute_t *attr, /* job-id attribute */ + *auth_info; /* auth-info attribute */ + int jobid; /* Job ID */ + cupsd_job_t *job; /* Current job */ + char scheme[HTTP_MAX_URI], + /* Method portion of URI */ + username[HTTP_MAX_URI], + /* Username portion of URI */ + host[HTTP_MAX_URI], + /* Host portion of URI */ + resource[HTTP_MAX_URI]; + /* Resource portion of URI */ + int port; /* Port portion of URI */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, - "copy_model(con=%p, from=\"%s\", to=\"%s\")", - con, from, to); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "authenticate_job(%p[%d], %s)", + con, con->http.fd, uri->values[0].string.text); /* - * Run cups-driverd to get the PPD file... + * Start with "everything is OK" status... */ - argv[0] = "cups-driverd"; - argv[1] = "cat"; - argv[2] = (char *)from; - argv[3] = NULL; - - cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); - - snprintf(buffer, sizeof(buffer), "%s/daemon/cups-driverd", ServerBin); - snprintf(tempfile, sizeof(tempfile), "%s/%d.ppd", TempDir, con->http.fd); - tempfd = open(tempfile, O_WRONLY | O_CREAT | O_TRUNC, 0600); - if (tempfd < 0) - return (-1); - - cupsdOpenPipe(temppipe); - - if ((input = calloc(1, SetSize)) == NULL) - { - close(tempfd); - unlink(tempfile); - - cupsdLogMessage(CUPSD_LOG_ERROR, - "copy_model: Unable to allocate %d bytes for select()...", - SetSize); - return (-1); - } - - cupsdLogMessage(CUPSD_LOG_DEBUG, - "copy_model: Running \"cups-driverd cat %s\"...", from); - - if (!cupsdStartProcess(buffer, argv, envp, -1, temppipe[1], CGIPipes[1], - -1, 0, &temppid)) - { - close(tempfd); - unlink(tempfile); - return (-1); - } - - close(temppipe[1]); + con->response->request.status.status_code = IPP_OK; /* - * Wait up to 30 seconds for the PPD file to be copied... + * See if we have a job URI or a printer URI... */ - total = 0; - - if (temppipe[0] > CGIPipes[0]) - maxfd = temppipe[0] + 1; - else - maxfd = CGIPipes[0] + 1; - - for (;;) + if (!strcmp(uri->name, "printer-uri")) { /* - * See if we have data ready... + * Got a printer URI; see if we also have a job-id attribute... */ - bytes = 0; - - FD_SET(temppipe[0], input); - FD_SET(CGIPipes[0], input); - - timeout.tv_sec = 30; - timeout.tv_usec = 0; - - if ((i = select(maxfd, input, NULL, NULL, &timeout)) < 0) + if ((attr = ippFindAttribute(con->request, "job-id", + IPP_TAG_INTEGER)) == NULL) { - if (errno == EINTR) - continue; - else - break; + send_ipp_status(con, IPP_BAD_REQUEST, + _("Got a printer-uri attribute but no job-id!")); + return; } - else if (i == 0) - { - /* - * We have timed out... - */ - break; - } + jobid = attr->values[0].integer; + } + else + { + /* + * Got a job URI; parse it to get the job ID... + */ + + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); - if (FD_ISSET(temppipe[0], input)) + if (strncmp(resource, "/jobs/", 6)) { /* - * Read the PPD file from the pipe, and write it to the PPD file. + * Not a valid URI! */ - if ((bytes = read(temppipe[0], buffer, sizeof(buffer))) > 0) - { - if (write(tempfd, buffer, bytes) < bytes) - break; - - total += bytes; - } - else - break; + send_ipp_status(con, IPP_BAD_REQUEST, _("Bad job-uri attribute \"%s\"!"), + uri->values[0].string.text); + return; } - if (FD_ISSET(CGIPipes[0], input)) - cupsdUpdateCGI(); + jobid = atoi(resource + 6); } - close(temppipe[0]); - close(tempfd); + /* + * See if the job exists... + */ - if (!total) + if ((job = cupsdFindJob(jobid)) == NULL) { /* - * No data from cups-deviced... + * Nope - return a "not found" error... */ - cupsdLogMessage(CUPSD_LOG_ERROR, "copy_model: empty PPD file!"); - unlink(tempfile); - return (-1); + send_ipp_status(con, IPP_NOT_FOUND, + _("Job #%d does not exist!"), jobid); + return; } /* - * Read the source file and see what page sizes are supported... + * See if the job has been completed... */ - if ((src = cupsFileOpen(tempfile, "rb")) == NULL) + if (job->state_value != IPP_JOB_HELD) { - unlink(tempfile); - return (-1); - } - - have_letter = 0; - have_a4 = 0; - - while (cupsFileGets(src, buffer, sizeof(buffer))) - if (!strncmp(buffer, "*PageSize ", 10)) - { - /* - * Strip UI text and command data from the end of the line... - */ - - if ((ptr = strchr(buffer + 10, '/')) != NULL) - *ptr = '\0'; - if ((ptr = strchr(buffer + 10, ':')) != NULL) - *ptr = '\0'; - - for (ptr = buffer + 10; isspace(*ptr); ptr ++); - - /* - * Look for Letter and A4 page sizes... - */ - - if (!strcmp(ptr, "Letter")) - have_letter = 1; - - if (!strcmp(ptr, "A4")) - have_a4 = 1; - } + /* + * Return a "not-possible" error... + */ - cupsFileRewind(src); + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job #%d is not held for authentication!"), + jobid); + return; + } /* - * Open the destination (if possible) and set the default options... + * See if we have already authenticated... */ - num_defaults = 0; - defaults = NULL; - cups_protocol[0] = '\0'; + auth_info = ippFindAttribute(con->request, "auth-info", IPP_TAG_TEXT); - if ((dst = cupsFileOpen(to, "rb")) != NULL) + if (!con->username[0] && !auth_info) { - /* - * Read all of the default lines from the old PPD... - */ - - while (cupsFileGets(dst, buffer, sizeof(buffer))) - if (!strncmp(buffer, "*Default", 8)) - { - /* - * Add the default option... - */ + cupsd_printer_t *printer; /* Job destination */ - if (!ppd_parse_line(buffer, option, sizeof(option), - choice, sizeof(choice))) - num_defaults = ppd_add_default(option, choice, num_defaults, - &defaults); - } - else if (!strncmp(buffer, "*cupsProtocol:", 14)) - strlcpy(cups_protocol, buffer, sizeof(cups_protocol)); - cupsFileClose(dst); - } -#ifdef HAVE_LIBPAPER - else if ((paper_result = systempapername()) != NULL) - { /* - * Set the default media sizes from the systemwide default... + * No auth data. If we need to authenticate via Kerberos, send a + * HTTP auth challenge, otherwise just return an IPP error... */ - strlcpy(system_paper, paper_result, sizeof(system_paper)); - system_paper[0] = toupper(system_paper[0] & 255); + printer = cupsdFindDest(job->dest); - if ((!strcmp(system_paper, "Letter") && have_letter) || - (!strcmp(system_paper, "A4") && have_a4)) - { - num_defaults = ppd_add_default("PageSize", system_paper, - num_defaults, &defaults); - num_defaults = ppd_add_default("PageRegion", system_paper, - num_defaults, &defaults); - num_defaults = ppd_add_default("PaperDimension", system_paper, - num_defaults, &defaults); - num_defaults = ppd_add_default("ImageableArea", system_paper, - num_defaults, &defaults); - } + if (printer && printer->num_auth_info_required > 0 && + !strcmp(printer->auth_info_required[0], "negotiate")) + send_http_error(con, HTTP_UNAUTHORIZED, printer); + else + send_ipp_status(con, IPP_NOT_AUTHORIZED, + _("No authentication information provided!")); + return; } -#endif /* HAVE_LIBPAPER */ - else - { - /* - * Add the default media sizes... - * - * Note: These values are generally not valid for large-format devices - * like plotters, however it is probably safe to say that those - * users will configure the media size after initially adding - * the device anyways... - */ - - if (!DefaultLanguage || - !strcasecmp(DefaultLanguage, "C") || - !strcasecmp(DefaultLanguage, "POSIX") || - !strcasecmp(DefaultLanguage, "en") || - !strncasecmp(DefaultLanguage, "en_US", 5) || - !strncasecmp(DefaultLanguage, "en_CA", 5) || - !strncasecmp(DefaultLanguage, "fr_CA", 5)) - { - /* - * These are the only locales that will default to "letter" size... - */ - if (have_letter) - { - num_defaults = ppd_add_default("PageSize", "Letter", num_defaults, - &defaults); - num_defaults = ppd_add_default("PageRegion", "Letter", num_defaults, - &defaults); - num_defaults = ppd_add_default("PaperDimension", "Letter", num_defaults, - &defaults); - num_defaults = ppd_add_default("ImageableArea", "Letter", num_defaults, - &defaults); - } - } - else if (have_a4) - { - /* - * The rest default to "a4" size... - */ + /* + * See if the job is owned by the requesting user... + */ - num_defaults = ppd_add_default("PageSize", "A4", num_defaults, - &defaults); - num_defaults = ppd_add_default("PageRegion", "A4", num_defaults, - &defaults); - num_defaults = ppd_add_default("PaperDimension", "A4", num_defaults, - &defaults); - num_defaults = ppd_add_default("ImageableArea", "A4", num_defaults, - &defaults); - } + if (!validate_user(job, con, job->username, username, sizeof(username))) + { + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); + return; } /* - * Open the destination file for a copy... + * Save the authentication information for this job... */ - if ((dst = cupsFileOpen(to, "wb")) == NULL) - { - if (num_defaults > 0) - free(defaults); + save_auth_info(con, job, auth_info); - cupsFileClose(src); - unlink(tempfile); - return (-1); + /* + * Reset the job-hold-until value to "no-hold"... + */ + + if ((attr = ippFindAttribute(job->attrs, "job-hold-until", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); + + if (attr) + { + attr->value_tag = IPP_TAG_KEYWORD; + cupsdSetString(&(attr->values[0].string.text), "no-hold"); } /* - * Copy the source file to the destination... + * Release the job and return... */ - while (cupsFileGets(src, buffer, sizeof(buffer))) - { - if (!strncmp(buffer, "*Default", 8)) - { - /* - * Check for an previous default option choice... - */ + cupsdReleaseJob(job); - if (!ppd_parse_line(buffer, option, sizeof(option), - choice, sizeof(choice))) - { - for (i = 0; i < num_defaults; i ++) - if (!strcmp(option, defaults[i].option)) - { - /* - * Substitute the previous choice... - */ - - snprintf(buffer, sizeof(buffer), "*Default%s: %s", option, - defaults[i].choice); - break; - } - } - } - - cupsFilePrintf(dst, "%s\n", buffer); - } - - if (cups_protocol[0]) - cupsFilePrintf(dst, "%s\n", cups_protocol); - - if (num_defaults > 0) - free(defaults); - - /* - * Close both files and return... - */ - - cupsFileClose(src); - - unlink(tempfile); - - return (cupsFileClose(dst)); + cupsdLogJob(job, CUPSD_LOG_INFO, "Authenticated by \"%s\".", con->username); } /* - * 'copy_job_attrs()' - Copy job attributes. + * 'cancel_all_jobs()' - Cancel all print jobs. */ static void -copy_job_attrs(cupsd_client_t *con, /* I - Client connection */ - cupsd_job_t *job, /* I - Job */ - cups_array_t *ra) /* I - Requested attributes array */ +cancel_all_jobs(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Job or Printer URI */ { - char job_uri[HTTP_MAX_URI]; /* Job URI */ + http_status_t status; /* Policy status */ + cups_ptype_t dtype; /* Destination type */ + char scheme[HTTP_MAX_URI], /* Scheme portion of URI */ + userpass[HTTP_MAX_URI], /* Username portion of URI */ + hostname[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ + int port; /* Port portion of URI */ + ipp_attribute_t *attr; /* Attribute in request */ + const char *username; /* Username */ + int purge; /* Purge? */ + cupsd_printer_t *printer; /* Printer */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cancel_all_jobs(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); /* - * Send the requested attributes for each job... + * See if we have a printer URI... */ - httpAssembleURIf(HTTP_URI_CODING_ALL, job_uri, sizeof(job_uri), "ipp", NULL, - con->servername, con->serverport, "/jobs/%d", - job->id); - - if (!ra || cupsArrayFind(ra, "job-more-info")) - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, - "job-more-info", NULL, job_uri); - - if (job->state->values[0].integer > IPP_JOB_PROCESSING && - (!ra || cupsArrayFind(ra, "job-preserved"))) + if (strcmp(uri->name, "printer-uri")) { - char filename[1024]; /* Job data file */ - - - snprintf(filename, sizeof(filename), "%s/d%05d-001", RequestRoot, - job->id); - ippAddBoolean(con->response, IPP_TAG_JOB, "job-preserved", - !access(filename, 0)); + send_ipp_status(con, IPP_BAD_REQUEST, + _("The printer-uri attribute is required!")); + return; } - if (!ra || cupsArrayFind(ra, "job-printer-up-time")) - ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, - "job-printer-up-time", time(NULL)); - - if (!ra || cupsArrayFind(ra, "job-state-reasons")) - add_job_state_reasons(con, job); - - if (!ra || cupsArrayFind(ra, "job-uri")) - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, - "job-uri", NULL, job_uri); - - copy_attrs(con->response, job->attrs, ra, IPP_TAG_JOB, 0); -} - - -/* - * 'copy_printer_attrs()' - Copy printer attributes. - */ - -static void -copy_printer_attrs( - cupsd_client_t *con, /* I - Client connection */ - cupsd_printer_t *printer, /* I - Printer */ - cups_array_t *ra) /* I - Requested attributes array */ -{ - char printer_uri[HTTP_MAX_URI]; - /* Printer URI */ - time_t curtime; /* Current time */ - int i; /* Looping var */ - ipp_attribute_t *history; /* History collection */ + /* + * Get the username (if any) for the jobs we want to cancel (only if + * "my-jobs" is specified... + */ + if ((attr = ippFindAttribute(con->request, "my-jobs", + IPP_TAG_BOOLEAN)) != NULL && + attr->values[0].boolean) + { + if ((attr = ippFindAttribute(con->request, "requesting-user-name", + IPP_TAG_NAME)) != NULL) + username = attr->values[0].string.text; + else + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Missing requesting-user-name attribute!")); + return; + } + } + else + username = NULL; /* - * Copy the printer attributes to the response using requested-attributes - * and document-format attributes that may be provided by the client. + * Look for the "purge-jobs" attribute... */ - curtime = time(NULL); + if ((attr = ippFindAttribute(con->request, "purge-jobs", + IPP_TAG_BOOLEAN)) != NULL) + purge = attr->values[0].boolean; + else + purge = 1; - if (!ra || cupsArrayFind(ra, "printer-current-time")) - ippAddDate(con->response, IPP_TAG_PRINTER, "printer-current-time", - ippTimeToDate(curtime)); + /* + * And if the destination is valid... + */ - if (!ra || cupsArrayFind(ra, "printer-error-policy")) - ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME, - "printer-error-policy", NULL, printer->error_policy); + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) + { + /* + * Bad URI? + */ - if (!ra || cupsArrayFind(ra, "printer-is-accepting-jobs")) - ippAddBoolean(con->response, IPP_TAG_PRINTER, "printer-is-accepting-jobs", - printer->accepting); + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, + scheme, sizeof(scheme), userpass, sizeof(userpass), + hostname, sizeof(hostname), &port, + resource, sizeof(resource)); - if (!ra || cupsArrayFind(ra, "printer-is-shared")) - ippAddBoolean(con->response, IPP_TAG_PRINTER, "printer-is-shared", - printer->shared); + if ((!strncmp(resource, "/printers/", 10) && resource[10]) || + (!strncmp(resource, "/classes/", 9) && resource[9])) + { + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } - if (!ra || cupsArrayFind(ra, "printer-op-policy")) - ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME, - "printer-op-policy", NULL, printer->op_policy); + /* + * Check policy... + */ - if (!ra || cupsArrayFind(ra, "printer-state")) - ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", - printer->state); + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, NULL); + return; + } - if (!ra || cupsArrayFind(ra, "printer-state-change-time")) - ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, - "printer-state-change-time", printer->state_time); - - if (MaxPrinterHistory > 0 && printer->num_history > 0 && - cupsArrayFind(ra, "printer-state-history")) - { /* - * Printer history is only sent if specifically requested, so that - * older CUPS/IPP clients won't barf on the collection attributes. + * Cancel all jobs on all printers... */ - history = ippAddCollections(con->response, IPP_TAG_PRINTER, - "printer-state-history", - printer->num_history, NULL); + cupsdCancelJobs(NULL, username, purge); - for (i = 0; i < printer->num_history; i ++) - copy_attrs(history->values[i].collection = ippNew(), printer->history[i], - NULL, IPP_TAG_ZERO, 0); + cupsdLogMessage(CUPSD_LOG_INFO, "All jobs were %s by \"%s\".", + purge ? "purged" : "canceled", get_username(con)); } + else + { + /* + * Check policy... + */ - if (!ra || cupsArrayFind(ra, "printer-state-message")) - ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_TEXT, - "printer-state-message", NULL, printer->state_message); + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, + NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return; + } - if (!ra || cupsArrayFind(ra, "printer-state-reasons")) - add_printer_state_reasons(con, printer); + /* + * Cancel all of the jobs on the named printer... + */ - if (!ra || cupsArrayFind(ra, "printer-up-time")) - ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, - "printer-up-time", curtime); + cupsdCancelJobs(printer->name, username, purge); - if ((!ra || cupsArrayFind(ra, "printer-uri-supported")) && - !ippFindAttribute(printer->attrs, "printer-uri-supported", - IPP_TAG_URI)) - { - httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), - "ipp", NULL, con->servername, con->serverport, - "/printers/%s", printer->name); - ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_URI, - "printer-uri-supported", NULL, printer_uri); - cupsdLogMessage(CUPSD_LOG_DEBUG2, "printer-uri-supported=\"%s\"", - printer_uri); + cupsdLogMessage(CUPSD_LOG_INFO, "All jobs on \"%s\" were %s by \"%s\".", + printer->name, purge ? "purged" : "canceled", + get_username(con)); } - if (!ra || cupsArrayFind(ra, "queued-job-count")) - add_queued_job_count(con, printer); - - copy_attrs(con->response, printer->attrs, ra, IPP_TAG_ZERO, 0); - copy_attrs(con->response, CommonData, ra, IPP_TAG_ZERO, IPP_TAG_COPY); + con->response->request.status.status_code = IPP_OK; } /* - * 'copy_subscription_attrs()' - Copy subscription attributes. + * 'cancel_job()' - Cancel a print job. */ static void -copy_subscription_attrs( - cupsd_client_t *con, /* I - Client connection */ - cupsd_subscription_t *sub, /* I - Subscription */ - cups_array_t *ra) /* I - Requested attributes array */ +cancel_job(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Job or Printer URI */ { - ipp_attribute_t *attr; /* Current attribute */ - char printer_uri[HTTP_MAX_URI]; - /* Printer URI */ - int count; /* Number of events */ - unsigned mask; /* Current event mask */ - const char *name; /* Current event name */ + ipp_attribute_t *attr; /* Current attribute */ + int jobid; /* Job ID */ + char scheme[HTTP_MAX_URI], /* Scheme portion of URI */ + username[HTTP_MAX_URI], /* Username portion of URI */ + host[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ + int port; /* Port portion of URI */ + cupsd_job_t *job; /* Job information */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + cupsd_printer_t *printer; /* Printer data */ + int purge; /* Purge the job? */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "cancel_job(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); /* - * Copy the subscription attributes to the response using the - * requested-attributes attribute that may be provided by the client. + * See if we have a job URI or a printer URI... */ - if (!ra || cupsArrayFind(ra, "notify-events")) + if (!strcmp(uri->name, "printer-uri")) { - if ((name = cupsdEventName((cupsd_eventmask_t)sub->mask)) != NULL) - { - /* - * Simple event list... - */ + /* + * Got a printer URI; see if we also have a job-id attribute... + */ - ippAddString(con->response, IPP_TAG_SUBSCRIPTION, - IPP_TAG_KEYWORD | IPP_TAG_COPY, - "notify-events", NULL, name); - } - else - { + if ((attr = ippFindAttribute(con->request, "job-id", + IPP_TAG_INTEGER)) == NULL) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Got a printer-uri attribute but no job-id!")); + return; + } + + if ((jobid = attr->values[0].integer) == 0) + { /* - * Complex event list... + * Find the current job on the specified printer... */ - for (mask = 1, count = 0; mask < CUPSD_EVENT_ALL; mask <<= 1) - if (sub->mask & mask) - count ++; + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) + { + /* + * Bad URI... + */ - attr = ippAddStrings(con->response, IPP_TAG_SUBSCRIPTION, - IPP_TAG_KEYWORD | IPP_TAG_COPY, - "notify-events", count, NULL, NULL); + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } - for (mask = 1, count = 0; mask < CUPSD_EVENT_ALL; mask <<= 1) - if (sub->mask & mask) - { - attr->values[count].string.text = - (char *)cupsdEventName((cupsd_eventmask_t)mask); + /* + * See if there are any pending jobs... + */ - count ++; - } - } - } + for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); + job; + job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) + if (job->state_value <= IPP_JOB_PROCESSING && + !strcasecmp(job->dest, printer->name)) + break; - if (sub->job && (!ra || cupsArrayFind(ra, "notify-job-id"))) - ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, - "notify-job-id", sub->job->id); + if (job) + jobid = job->id; + else + { + /* + * No, try stopped jobs... + */ - if (!sub->job && (!ra || cupsArrayFind(ra, "notify-lease-duration"))) - ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, - "notify-lease-duration", sub->lease); + for (job = (cupsd_job_t *)cupsArrayFirst(ActiveJobs); + job; + job = (cupsd_job_t *)cupsArrayNext(ActiveJobs)) + if (job->state_value == IPP_JOB_STOPPED && + !strcasecmp(job->dest, printer->name)) + break; - if (sub->dest && (!ra || cupsArrayFind(ra, "notify-printer-uri"))) - { - httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), - "ipp", NULL, con->servername, con->serverport, - "/printers/%s", sub->dest->name); - ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, - "notify-printer-uri", NULL, printer_uri); + if (job) + jobid = job->id; + else + { + send_ipp_status(con, IPP_NOT_POSSIBLE, _("No active jobs on %s!"), + printer->name); + return; + } + } + } } + else + { + /* + * Got a job URI; parse it to get the job ID... + */ - if (sub->recipient && (!ra || cupsArrayFind(ra, "notify-recipient-uri"))) - ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, - "notify-recipient-uri", NULL, sub->recipient); - else if (!ra || cupsArrayFind(ra, "notify-pull-method")) - ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, - "notify-pull-method", NULL, "ippget"); - - if (!ra || cupsArrayFind(ra, "notify-subscriber-user-name")) - ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_NAME, - "notify-subscriber-user-name", NULL, sub->owner); - - if (!ra || cupsArrayFind(ra, "notify-subscription-id")) - ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, - "notify-subscription-id", sub->id); - - if (!ra || cupsArrayFind(ra, "notify-time-interval")) - ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, - "notify-time-interval", sub->interval); - - if (sub->user_data_len > 0 && (!ra || cupsArrayFind(ra, "notify-user-data"))) - ippAddOctetString(con->response, IPP_TAG_SUBSCRIPTION, "notify-user-data", - sub->user_data, sub->user_data_len); -} + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); + if (strncmp(resource, "/jobs/", 6)) + { + /* + * Not a valid URI! + */ -/* - * 'create_job()' - Print a file to a printer or class. - */ + send_ipp_status(con, IPP_BAD_REQUEST, + _("Bad job-uri attribute \"%s\"!"), + uri->values[0].string.text); + return; + } -static void -create_job(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Printer URI */ -{ - http_status_t status; /* Policy status */ - ipp_attribute_t *attr; /* Current attribute */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - int priority; /* Job priority */ - char *title; /* Job name/title */ - cupsd_job_t *job; /* Current job */ - char job_uri[HTTP_MAX_URI], /* Job URI */ - method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - cupsd_printer_t *printer; /* Printer data */ - int kbytes; /* Size of print file */ - int i; /* Looping var */ - int lowerpagerange; /* Page range bound */ + jobid = atoi(resource + 6); + } + /* + * Look for the "purge-job" attribute... + */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "create_job(%p[%d], %s)", con, - con->http.fd, uri->values[0].string.text); + if ((attr = ippFindAttribute(con->request, "purge-job", + IPP_TAG_BOOLEAN)) != NULL) + purge = attr->values[0].boolean; + else + purge = 0; /* - * Is the destination valid? + * See if the job exists... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if ((job = cupsdFindJob(jobid)) == NULL) { /* - * Bad URI... + * Nope - return a "not found" error... */ - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer or class was not found.")); + send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist!"), jobid); return; } /* - * Check remote printing to non-shared printer... + * See if the job is owned by the requesting user... */ - if (!printer->shared && - strcasecmp(con->http.hostname, "localhost") && - strcasecmp(con->http.hostname, ServerName)) + if (!validate_user(job, con, job->username, username, sizeof(username))) { - send_ipp_status(con, IPP_NOT_AUTHORIZED, - _("The printer or class is not shared!")); + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); return; } /* - * Check policy... + * See if the job is already completed, canceled, or aborted; if so, + * we can't cancel... */ - if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + if (job->state_value >= IPP_JOB_CANCELED && !purge) { - send_http_error(con, status); - return; - } - else if ((printer->type & CUPS_PRINTER_AUTHENTICATED) && !con->username[0]) - { - send_http_error(con, HTTP_UNAUTHORIZED); - return; - } + switch (job->state_value) + { + case IPP_JOB_CANCELED : + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job #%d is already canceled - can\'t cancel."), + jobid); + break; - /* - * See if the printer is accepting jobs... - */ + case IPP_JOB_ABORTED : + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job #%d is already aborted - can\'t cancel."), + jobid); + break; + + default : + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job #%d is already completed - can\'t cancel."), + jobid); + break; + } - if (!printer->accepting) - { - send_ipp_status(con, IPP_NOT_ACCEPTING, - _("Destination \"%s\" is not accepting jobs."), - dest); return; } /* - * Validate job template attributes; for now just copies and page-ranges... + * Cancel the job and return... */ - if ((attr = ippFindAttribute(con->request, "copies", - IPP_TAG_INTEGER)) != NULL) - { - if (attr->values[0].integer < 1 || attr->values[0].integer > MaxCopies) - { - send_ipp_status(con, IPP_ATTRIBUTES, _("Bad copies value %d."), - attr->values[0].integer); - ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER, - "copies", attr->values[0].integer); - return; - } - } + cupsdSetJobState(job, IPP_JOB_CANCELED, purge, + purge ? "Job purged by \"%s\"" : "Job canceled by \"%s\"", + username); + cupsdCheckJobs(); - if ((attr = ippFindAttribute(con->request, "page-ranges", - IPP_TAG_RANGE)) != NULL) - { - for (i = 0, lowerpagerange = 1; i < attr->num_values; i ++) - { - if (attr->values[i].range.lower < lowerpagerange || - attr->values[i].range.lower > attr->values[i].range.upper) - { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Bad page-ranges values %d-%d."), - attr->values[i].range.lower, - attr->values[i].range.upper); - return; - } + if (purge) + cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Purged by \"%s\".", jobid, + username); + else + cupsdLogMessage(CUPSD_LOG_INFO, "[Job %d] Canceled by \"%s\".", jobid, + username); - lowerpagerange = attr->values[i].range.upper + 1; - } - } + con->response->request.status.status_code = IPP_OK; +} + + +/* + * 'cancel_subscription()' - Cancel a subscription. + */ + +static void +cancel_subscription( + cupsd_client_t *con, /* I - Client connection */ + int sub_id) /* I - Subscription ID */ +{ + http_status_t status; /* Policy status */ + cupsd_subscription_t *sub; /* Subscription */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "cancel_subscription(con=%p[%d], sub_id=%d)", + con, con->http.fd, sub_id); /* - * Make sure we aren't over our limit... + * Is the subscription ID valid? */ - if (cupsArrayCount(Jobs) >= MaxJobs && MaxJobs) - cupsdCleanJobs(); - - if (cupsArrayCount(Jobs) >= MaxJobs && MaxJobs) + if ((sub = cupsdFindSubscription(sub_id)) == NULL) { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Too many active jobs.")); + /* + * Bad subscription ID... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("notify-subscription-id %d no good!"), sub_id); return; } - if (!check_quotas(con, printer)) + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(sub->dest ? sub->dest->op_policy_ptr : + DefaultPolicyPtr, + con, sub->owner)) != HTTP_OK) { - send_ipp_status(con, IPP_NOT_POSSIBLE, _("Quota limit reached.")); + send_http_error(con, status, sub->dest); return; } /* - * Create the job and set things up... + * Cancel the subscription... */ - if ((attr = ippFindAttribute(con->request, "job-priority", - IPP_TAG_INTEGER)) != NULL) - priority = attr->values[0].integer; - else - ippAddInteger(con->request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority", - priority = 50); + cupsdDeleteSubscription(sub, 1); - if ((attr = ippFindAttribute(con->request, "job-name", - IPP_TAG_NAME)) != NULL) - title = attr->values[0].string.text; - else - ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, - title = "Untitled"); - - if ((job = cupsdAddJob(priority, printer->name)) == NULL) - { - send_ipp_status(con, IPP_INTERNAL_ERROR, - _("Unable to add job for destination \"%s\"!"), dest); - return; - } - - job->dtype = dtype; - job->attrs = con->request; - con->request = NULL; - - attr = ippFindAttribute(job->attrs, "requesting-user-name", IPP_TAG_NAME); - - if (con->username[0]) - { - cupsdSetString(&job->username, con->username); - - if (attr) - cupsdSetString(&attr->values[0].string.text, con->username); + con->response->request.status.status_code = IPP_OK; +} - save_auth_info(con, job); - } - else if (attr) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "create_job: requesting-user-name = \"%s\"", - attr->values[0].string.text); - cupsdSetString(&job->username, attr->values[0].string.text); - } - else - cupsdSetString(&job->username, "anonymous"); +/* + * 'check_rss_recipient()' - Check that we do not have a duplicate RSS feed URI. + */ - if (!attr) - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, - "job-originating-user-name", NULL, job->username); - else - { - attr->group_tag = IPP_TAG_JOB; - cupsdSetString(&attr->name, "job-originating-user-name"); - } +static int /* O - 1 if OK, 0 if not */ +check_rss_recipient( + const char *recipient) /* I - Recipient URI */ +{ + cupsd_subscription_t *sub; /* Current subscription */ - if ((attr = ippFindAttribute(job->attrs, "job-originating-host-name", - IPP_TAG_ZERO)) != NULL) - { - /* - * Request contains a job-originating-host-name attribute; validate it... - */ - if (attr->value_tag != IPP_TAG_NAME || - attr->num_values != 1 || - strcmp(con->http.hostname, "localhost")) + for (sub = (cupsd_subscription_t *)cupsArrayFirst(Subscriptions); + sub; + sub = (cupsd_subscription_t *)cupsArrayNext(Subscriptions)) + if (sub->recipient) { /* - * Can't override the value if we aren't connected via localhost. - * Also, we can only have 1 value and it must be a name value. + * Compare the URIs up to the first ?... */ - switch (attr->value_tag) - { - case IPP_TAG_STRING : - case IPP_TAG_TEXTLANG : - case IPP_TAG_NAMELANG : - case IPP_TAG_TEXT : - case IPP_TAG_NAME : - case IPP_TAG_KEYWORD : - case IPP_TAG_URI : - case IPP_TAG_URISCHEME : - case IPP_TAG_CHARSET : - case IPP_TAG_LANGUAGE : - case IPP_TAG_MIMETYPE : - /* - * Free old strings... - */ + const char *r1, *r2; - for (i = 0; i < attr->num_values; i ++) - { - free(attr->values[i].string.text); - attr->values[i].string.text = NULL; - if (attr->values[i].string.charset) - { - free(attr->values[i].string.charset); - attr->values[i].string.charset = NULL; - } - } + for (r1 = recipient, r2 = sub->recipient; + *r1 == *r2 && *r1 && *r1 != '?' && *r2 && *r2 != '?'; + r1 ++, r2 ++); - default : - break; - } + if (*r1 == *r2) + return (0); + } - /* - * Use the default connection hostname instead... - */ + return (1); +} - attr->value_tag = IPP_TAG_NAME; - attr->num_values = 1; - attr->values[0].string.text = strdup(con->http.hostname); - } - attr->group_tag = IPP_TAG_JOB; - } - else - { - /* - * No job-originating-host-name attribute, so use the hostname from - * the connection... - */ +/* + * 'check_quotas()' - Check quotas for a printer and user. + */ - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, - "job-originating-host-name", NULL, con->http.hostname); - } +static int /* O - 1 if OK, 0 if not */ +check_quotas(cupsd_client_t *con, /* I - Client connection */ + cupsd_printer_t *p) /* I - Printer or class */ +{ + int i; /* Looping var */ + char username[33]; /* Username */ + cupsd_quota_t *q; /* Quota data */ +#ifdef HAVE_MBR_UID_TO_UUID + /* + * Use Apple membership APIs which require that all names represent + * valid user account or group records accessible by the server. + */ + + uuid_t usr_uuid; /* UUID for job requesting user */ + uuid_t usr2_uuid; /* UUID for ACL user name entry */ + uuid_t grp_uuid; /* UUID for ACL group name entry */ + int mbr_err; /* Error from membership function */ + int is_member; /* Is this user a member? */ +#else + /* + * Use standard POSIX APIs for checking users and groups... + */ + + struct passwd *pw; /* User password data */ +#endif /* HAVE_MBR_UID_TO_UUID */ - ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", - time(NULL)); - attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "time-at-processing", 0); - attr->value_tag = IPP_TAG_NOVALUE; - attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "time-at-completed", 0); - attr->value_tag = IPP_TAG_NOVALUE; + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "check_quotas(%p[%d], %p[%s])", + con, con->http.fd, p, p->name); /* - * Add remaining job attributes... + * Figure out who is printing... */ - ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); - job->state = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_ENUM, - "job-state", IPP_JOB_STOPPED); - job->sheets = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "job-media-sheets-completed", 0); - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, - printer->uri); - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, - title); + strlcpy(username, get_username(con), sizeof(username)); - if ((attr = ippFindAttribute(job->attrs, "job-k-octets", - IPP_TAG_INTEGER)) != NULL) - attr->values[0].integer = 0; - else - attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "job-k-octets", 0); + /* + * Check global active job limits for printers and users... + */ - if ((attr = ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); - if (!attr) - attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-hold-until", NULL, "no-hold"); - if (attr && strcmp(attr->values[0].string.text, "no-hold") && - !(printer->type & CUPS_PRINTER_REMOTE)) + if (MaxJobsPerPrinter) { /* - * Hold job until specified time... + * Check if there are too many pending jobs on this printer... */ - cupsdSetJobHoldUntil(job, attr->values[0].string.text); + if (cupsdGetPrinterJobCount(p->name) >= MaxJobsPerPrinter) + { + cupsdLogMessage(CUPSD_LOG_INFO, "Too many jobs for printer \"%s\"...", + p->name); + return (0); + } } - else - job->hold_until = time(NULL) + 60; - job->state->values[0].integer = IPP_JOB_HELD; - - if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) || - Classification) + if (MaxJobsPerUser) { /* - * Add job sheets options... + * Check if there are too many pending jobs for this user... */ - if ((attr = ippFindAttribute(job->attrs, "job-sheets", - IPP_TAG_ZERO)) == NULL) + if (cupsdGetUserJobCount(username) >= MaxJobsPerUser) { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "Adding default job-sheets values \"%s,%s\"...", - printer->job_sheets[0], printer->job_sheets[1]); - - attr = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-sheets", - 2, NULL, NULL); - attr->values[0].string.text = strdup(printer->job_sheets[0]); - attr->values[1].string.text = strdup(printer->job_sheets[1]); + cupsdLogMessage(CUPSD_LOG_INFO, "Too many jobs for user \"%s\"...", + username); + return (0); } + } - job->job_sheets = attr; + /* + * Check against users... + */ + + if (p->num_users == 0 && p->k_limit == 0 && p->page_limit == 0) + return (1); + if (p->num_users) + { +#ifdef HAVE_MBR_UID_TO_UUID /* - * Enforce classification level if set... + * Get UUID for job requesting user... */ - if (Classification) + if (mbr_user_name_to_uuid((char *)username, usr_uuid)) { + /* + * Unknown user... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "check_quotas: UUID lookup failed for user \"%s\"", + username); cupsdLogMessage(CUPSD_LOG_INFO, - "Classification=\"%s\", ClassifyOverride=%d", - Classification ? Classification : "(null)", - ClassifyOverride); + "Denying user \"%s\" access to printer \"%s\" " + "(unknown user)...", + username, p->name); + return (0); + } +#else + /* + * Get UID and GID of requesting user... + */ - if (ClassifyOverride) + pw = getpwnam(username); + endpwent(); +#endif /* HAVE_MBR_UID_TO_UUID */ + + for (i = 0; i < p->num_users; i ++) + if (p->users[i][0] == '@') { - if (!strcmp(attr->values[0].string.text, "none") && - (attr->num_values == 1 || - !strcmp(attr->values[1].string.text, "none"))) - { + /* + * Check group membership... + */ + +#ifdef HAVE_MBR_UID_TO_UUID + if (p->users[i][1] == '#') + { + if (uuid_parse((char *)p->users[i] + 2, grp_uuid)) + uuid_clear(grp_uuid); + } + else if ((mbr_err = mbr_group_name_to_uuid((char *)p->users[i] + 1, + grp_uuid)) != 0) + { /* - * Force the leading banner to have the classification on it... + * Invalid ACL entries are ignored for matching; just record a + * warning in the log... */ - cupsdSetString(&attr->values[0].string.text, Classification); - - cupsdLogMessage(CUPSD_LOG_NOTICE, "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s,none\", " - "job-originating-user-name=\"%s\"", - job->id, Classification, job->username); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "check_quotas: UUID lookup failed for ACL entry " + "\"%s\" (err=%d)", p->users[i], mbr_err); + cupsdLogMessage(CUPSD_LOG_WARN, + "Access control entry \"%s\" not a valid group name; " + "entry ignored", p->users[i]); } - else if (attr->num_values == 2 && - strcmp(attr->values[0].string.text, - attr->values[1].string.text) && - strcmp(attr->values[0].string.text, "none") && - strcmp(attr->values[1].string.text, "none")) - { + + if ((mbr_err = mbr_check_membership(usr_uuid, grp_uuid, + &is_member)) != 0) + { /* - * Can't put two different security markings on the same document! + * At this point, there should be no errors, but check anyways... */ - cupsdSetString(&attr->values[1].string.text, attr->values[0].string.text); - - cupsdLogMessage(CUPSD_LOG_NOTICE, "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s,%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - attr->values[1].string.text, job->username); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "check_quotas: group \"%s\" membership check " + "failed (err=%d)", p->users[i] + 1, mbr_err); + is_member = 0; } - else if (strcmp(attr->values[0].string.text, Classification) && - strcmp(attr->values[0].string.text, "none") && - (attr->num_values == 1 || - (strcmp(attr->values[1].string.text, Classification) && - strcmp(attr->values[1].string.text, "none")))) - { - if (attr->num_values == 1) - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION OVERRIDDEN " - "job-sheets=\"%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, job->username); - else - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION OVERRIDDEN " - "job-sheets=\"%s,%s\",fffff " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - attr->values[1].string.text, job->username); - } - } - else if (strcmp(attr->values[0].string.text, Classification) && - (attr->num_values == 1 || - strcmp(attr->values[1].string.text, Classification))) - { + /* - * Force the banner to have the classification on it... + * Stop if we found a match... */ - if (attr->num_values > 1 && - !strcmp(attr->values[0].string.text, attr->values[1].string.text)) - { - cupsdSetString(&(attr->values[0].string.text), Classification); - cupsdSetString(&(attr->values[1].string.text), Classification); - } + if (is_member) + break; + +#else + if (cupsdCheckGroup(username, pw, p->users[i] + 1)) + break; +#endif /* HAVE_MBR_UID_TO_UUID */ + } +#ifdef HAVE_MBR_UID_TO_UUID + else + { + if (p->users[i][0] == '#') + { + if (uuid_parse((char *)p->users[i] + 1, usr2_uuid)) + uuid_clear(usr2_uuid); + } + else if ((mbr_err = mbr_user_name_to_uuid((char *)p->users[i], + usr2_uuid)) != 0) + { + /* + * Invalid ACL entries are ignored for matching; just record a + * warning in the log... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "check_quotas: UUID lookup failed for ACL entry " + "\"%s\" (err=%d)", p->users[i], mbr_err); + cupsdLogMessage(CUPSD_LOG_WARN, + "Access control entry \"%s\" not a valid user name; " + "entry ignored", p->users[i]); + } + + if (!uuid_compare(usr_uuid, usr2_uuid)) + break; + } +#else + else if (!strcasecmp(username, p->users[i])) + break; +#endif /* HAVE_MBR_UID_TO_UUID */ + + if ((i < p->num_users) == p->deny_users) + { + cupsdLogMessage(CUPSD_LOG_INFO, + "Denying user \"%s\" access to printer \"%s\"...", + username, p->name); + return (0); + } + } + + /* + * Check quotas... + */ + +#ifdef __APPLE__ + if (AppleQuotas && (q = cupsdFindQuota(p, username)) != NULL) + { + /* + * TODO: Define these special page count values as constants! + */ + + if (q->page_count == -4) /* special case: unlimited user */ + { + cupsdLogMessage(CUPSD_LOG_INFO, + "User \"%s\" request approved for printer %s (%s): " + "unlimited quota.", + username, p->name, p->info); + q->page_count = 0; /* allow user to print */ + return (1); + } + else if (q->page_count == -3) /* quota exceeded */ + { + cupsdLogMessage(CUPSD_LOG_INFO, + "User \"%s\" request denied for printer %s (%s): " + "quota limit exceeded.", + username, p->name, p->info); + q->page_count = 2; /* force quota exceeded failure */ + return (-1); + } + else if (q->page_count == -2) /* quota disabled for user */ + { + cupsdLogMessage(CUPSD_LOG_INFO, + "User \"%s\" request denied for printer %s (%s): " + "printing disabled for user.", + username, p->name, p->info); + q->page_count = 2; /* force quota exceeded failure */ + return (-1); + } + else if (q->page_count == -1) /* quota access error */ + { + cupsdLogMessage(CUPSD_LOG_INFO, + "User \"%s\" request denied for printer %s (%s): " + "unable to determine quota limit.", + username, p->name, p->info); + q->page_count = 2; /* force quota exceeded failure */ + return (-1); + } + else if (q->page_count < 0) /* user not found or other error */ + { + cupsdLogMessage(CUPSD_LOG_INFO, + "User \"%s\" request denied for printer %s (%s): " + "user disabled / missing quota.", + username, p->name, p->info); + q->page_count = 2; /* force quota exceeded failure */ + return (-1); + } + else /* page within user limits */ + { + q->page_count = 0; /* allow user to print */ + return (1); + } + } + else +#endif /* __APPLE__ */ + if (p->k_limit || p->page_limit) + { + if ((q = cupsdUpdateQuota(p, username, 0, 0)) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to allocate quota data for user \"%s\"!", + username); + return (-1); + } + + if ((q->k_count >= p->k_limit && p->k_limit) || + (q->page_count >= p->page_limit && p->page_limit)) + { + cupsdLogMessage(CUPSD_LOG_INFO, "User \"%s\" is over the quota limit...", + username); + return (-1); + } + } + + /* + * If we have gotten this far, we're done! + */ + + return (1); +} + + +/* + * 'copy_attribute()' - Copy a single attribute. + */ + +static ipp_attribute_t * /* O - New attribute */ +copy_attribute( + ipp_t *to, /* O - Destination request/response */ + ipp_attribute_t *attr, /* I - Attribute to copy */ + int quickcopy) /* I - Do a quick copy? */ +{ + int i; /* Looping var */ + ipp_attribute_t *toattr; /* Destination attribute */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "copy_attribute(%p, %p[%s,%x,%x])", to, attr, + attr->name ? attr->name : "(null)", attr->group_tag, + attr->value_tag); + + switch (attr->value_tag & ~IPP_TAG_COPY) + { + case IPP_TAG_ZERO : + toattr = ippAddSeparator(to); + break; + + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + toattr = ippAddIntegers(to, attr->group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].integer = attr->values[i].integer; + break; + + case IPP_TAG_BOOLEAN : + toattr = ippAddBooleans(to, attr->group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].boolean = attr->values[i].boolean; + break; + + case IPP_TAG_STRING : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_URI : + case IPP_TAG_URISCHEME : + case IPP_TAG_CHARSET : + case IPP_TAG_LANGUAGE : + case IPP_TAG_MIMETYPE : + toattr = ippAddStrings(to, attr->group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = attr->values[i].string.text; + } + else if (attr->value_tag & IPP_TAG_COPY) + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + else + { + for (i = 0; i < attr->num_values; i ++) + toattr->values[i].string.text = + _cupsStrRetain(attr->values[i].string.text); + } + break; + + case IPP_TAG_DATE : + toattr = ippAddDate(to, attr->group_tag, attr->name, + attr->values[0].date); + break; + + case IPP_TAG_RESOLUTION : + toattr = ippAddResolutions(to, attr->group_tag, attr->name, + attr->num_values, IPP_RES_PER_INCH, + NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].resolution.xres = attr->values[i].resolution.xres; + toattr->values[i].resolution.yres = attr->values[i].resolution.yres; + toattr->values[i].resolution.units = attr->values[i].resolution.units; + } + break; + + case IPP_TAG_RANGE : + toattr = ippAddRanges(to, attr->group_tag, attr->name, + attr->num_values, NULL, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].range.lower = attr->values[i].range.lower; + toattr->values[i].range.upper = attr->values[i].range.upper; + } + break; + + case IPP_TAG_TEXTLANG : + case IPP_TAG_NAMELANG : + toattr = ippAddStrings(to, attr->group_tag, + (ipp_tag_t)(attr->value_tag | quickcopy), + attr->name, attr->num_values, NULL, NULL); + + if (quickcopy) + { + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].string.charset = attr->values[i].string.charset; + toattr->values[i].string.text = attr->values[i].string.text; + } + } + else if (attr->value_tag & IPP_TAG_COPY) + { + for (i = 0; i < attr->num_values; i ++) + { + if (!i) + toattr->values[i].string.charset = + _cupsStrAlloc(attr->values[i].string.charset); + else + toattr->values[i].string.charset = + toattr->values[0].string.charset; + + toattr->values[i].string.text = + _cupsStrAlloc(attr->values[i].string.text); + } + } + else + { + for (i = 0; i < attr->num_values; i ++) + { + if (!i) + toattr->values[i].string.charset = + _cupsStrRetain(attr->values[i].string.charset); + else + toattr->values[i].string.charset = + toattr->values[0].string.charset; + + toattr->values[i].string.text = + _cupsStrRetain(attr->values[i].string.text); + } + } + break; + + case IPP_TAG_BEGIN_COLLECTION : + toattr = ippAddCollections(to, attr->group_tag, attr->name, + attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].collection = ippNew(); + copy_attrs(toattr->values[i].collection, attr->values[i].collection, + NULL, IPP_TAG_ZERO, 0); + } + break; + + default : + toattr = ippAddIntegers(to, attr->group_tag, attr->value_tag, + attr->name, attr->num_values, NULL); + + for (i = 0; i < attr->num_values; i ++) + { + toattr->values[i].unknown.length = attr->values[i].unknown.length; + + if (toattr->values[i].unknown.length > 0) + { + if ((toattr->values[i].unknown.data = + malloc(toattr->values[i].unknown.length)) == NULL) + toattr->values[i].unknown.length = 0; + else + memcpy(toattr->values[i].unknown.data, + attr->values[i].unknown.data, + toattr->values[i].unknown.length); + } + } + break; /* anti-compiler-warning-code */ + } + + return (toattr); +} + + +/* + * 'copy_attrs()' - Copy attributes from one request to another. + */ + +static void +copy_attrs(ipp_t *to, /* I - Destination request */ + ipp_t *from, /* I - Source request */ + cups_array_t *ra, /* I - Requested attributes */ + ipp_tag_t group, /* I - Group to copy */ + int quickcopy) /* I - Do a quick copy? */ +{ + ipp_attribute_t *fromattr; /* Source attribute */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "copy_attrs(to=%p, from=%p, ra=%p, group=%x, quickcopy=%d)", + to, from, ra, group, quickcopy); + + if (!to || !from) + return; + + for (fromattr = from->attrs; fromattr; fromattr = fromattr->next) + { + /* + * Filter attributes as needed... + */ + + if ((group != IPP_TAG_ZERO && fromattr->group_tag != group && + fromattr->group_tag != IPP_TAG_ZERO) || !fromattr->name) + continue; + + if (!ra || cupsArrayFind(ra, fromattr->name)) + { + /* + * Don't send collection attributes by default to IPP/1.x clients + * since many do not support collections... + */ + + if (fromattr->value_tag == IPP_TAG_BEGIN_COLLECTION && + !ra && to->request.status.version[0] == 1) + continue; + + copy_attribute(to, fromattr, quickcopy); + } + } +} + + +/* + * 'copy_banner()' - Copy a banner file to the requests directory for the + * specified job. + */ + +static int /* O - Size of banner file in kbytes */ +copy_banner(cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job, /* I - Job information */ + const char *name) /* I - Name of banner */ +{ + int i; /* Looping var */ + int kbytes; /* Size of banner file in kbytes */ + char filename[1024]; /* Job filename */ + cupsd_banner_t *banner; /* Pointer to banner */ + cups_file_t *in; /* Input file */ + cups_file_t *out; /* Output file */ + int ch; /* Character from file */ + char attrname[255], /* Name of attribute */ + *s; /* Pointer into name */ + ipp_attribute_t *attr; /* Attribute */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "copy_banner(con=%p[%d], job=%p[%d], name=\"%s\")", + con, con ? con->http.fd : -1, job, job->id, + name ? name : "(null)"); + + /* + * Find the banner; return if not found or "none"... + */ + + if (!name || !strcmp(name, "none") || + (banner = cupsdFindBanner(name)) == NULL) + return (0); + + /* + * Open the banner and job files... + */ + + if (add_file(con, job, banner->filetype, 0)) + return (-1); + + snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, + job->num_files); + if ((out = cupsFileOpen(filename, "w")) == NULL) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to create banner job file %s - %s", + filename, strerror(errno)); + job->num_files --; + return (0); + } + + fchmod(cupsFileNumber(out), 0640); + fchown(cupsFileNumber(out), RunUser, Group); + + /* + * Try the localized banner file under the subdirectory... + */ + + strlcpy(attrname, job->attrs->attrs->next->values[0].string.text, + sizeof(attrname)); + if (strlen(attrname) > 2 && attrname[2] == '-') + { + /* + * Convert ll-cc to ll_CC... + */ + + attrname[2] = '_'; + attrname[3] = toupper(attrname[3] & 255); + attrname[4] = toupper(attrname[4] & 255); + } + + snprintf(filename, sizeof(filename), "%s/banners/%s/%s", DataDir, + attrname, name); + + if (access(filename, 0) && strlen(attrname) > 2) + { + /* + * Wasn't able to find "ll_CC" locale file; try the non-national + * localization banner directory. + */ + + attrname[2] = '\0'; + + snprintf(filename, sizeof(filename), "%s/banners/%s/%s", DataDir, + attrname, name); + } + + if (access(filename, 0)) + { + /* + * Use the non-localized banner file. + */ + + snprintf(filename, sizeof(filename), "%s/banners/%s", DataDir, name); + } + + if ((in = cupsFileOpen(filename, "r")) == NULL) + { + cupsFileClose(out); + unlink(filename); + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to open banner template file %s - %s", + filename, strerror(errno)); + job->num_files --; + return (0); + } + + /* + * Parse the file to the end... + */ + + while ((ch = cupsFileGetChar(in)) != EOF) + if (ch == '{') + { + /* + * Get an attribute name... + */ + + for (s = attrname; (ch = cupsFileGetChar(in)) != EOF;) + if (!isalpha(ch & 255) && ch != '-' && ch != '?') + break; + else if (s < (attrname + sizeof(attrname) - 1)) + *s++ = ch; + else + break; + + *s = '\0'; + + if (ch != '}') + { + /* + * Ignore { followed by stuff that is not an attribute name... + */ + + cupsFilePrintf(out, "{%s%c", attrname, ch); + continue; + } + + /* + * See if it is defined... + */ + + if (attrname[0] == '?') + s = attrname + 1; + else + s = attrname; + + if (!strcmp(s, "printer-name")) + { + cupsFilePuts(out, job->dest); + continue; + } + else if ((attr = ippFindAttribute(job->attrs, s, IPP_TAG_ZERO)) == NULL) + { + /* + * See if we have a leading question mark... + */ + + if (attrname[0] != '?') + { + /* + * Nope, write to file as-is; probably a PostScript procedure... + */ + + cupsFilePrintf(out, "{%s}", attrname); + } + + continue; + } + + /* + * Output value(s)... + */ + + for (i = 0; i < attr->num_values; i ++) + { + if (i) + cupsFilePutChar(out, ','); + + switch (attr->value_tag) + { + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + if (!strncmp(s, "time-at-", 8)) + { + struct timeval tv = { attr->values[i].integer, 0 }; + cupsFilePuts(out, cupsdGetDateTime(&tv, CUPSD_TIME_STANDARD)); + } + else + cupsFilePrintf(out, "%d", attr->values[i].integer); + break; + + case IPP_TAG_BOOLEAN : + cupsFilePrintf(out, "%d", attr->values[i].boolean); + break; + + case IPP_TAG_NOVALUE : + cupsFilePuts(out, "novalue"); + break; + + case IPP_TAG_RANGE : + cupsFilePrintf(out, "%d-%d", attr->values[i].range.lower, + attr->values[i].range.upper); + break; + + case IPP_TAG_RESOLUTION : + cupsFilePrintf(out, "%dx%d%s", attr->values[i].resolution.xres, + attr->values[i].resolution.yres, + attr->values[i].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + break; + + case IPP_TAG_URI : + case IPP_TAG_STRING : + case IPP_TAG_TEXT : + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_CHARSET : + case IPP_TAG_LANGUAGE : + if (!strcasecmp(banner->filetype->type, "postscript")) + { + /* + * Need to quote strings for PS banners... + */ + + const char *p; + + for (p = attr->values[i].string.text; *p; p ++) + { + if (*p == '(' || *p == ')' || *p == '\\') + { + cupsFilePutChar(out, '\\'); + cupsFilePutChar(out, *p); + } + else if (*p < 32 || *p > 126) + cupsFilePrintf(out, "\\%03o", *p & 255); + else + cupsFilePutChar(out, *p); + } + } + else + cupsFilePuts(out, attr->values[i].string.text); + break; + + default : + break; /* anti-compiler-warning-code */ + } + } + } + else if (ch == '\\') /* Quoted char */ + { + ch = cupsFileGetChar(in); + + if (ch != '{') /* Only do special handling for \{ */ + cupsFilePutChar(out, '\\'); + + cupsFilePutChar(out, ch); + } + else + cupsFilePutChar(out, ch); + + cupsFileClose(in); + + kbytes = (cupsFileTell(out) + 1023) / 1024; + + if ((attr = ippFindAttribute(job->attrs, "job-k-octets", + IPP_TAG_INTEGER)) != NULL) + attr->values[0].integer += kbytes; + + cupsFileClose(out); + + return (kbytes); +} + + +/* + * 'copy_file()' - Copy a PPD file or interface script... + */ + +static int /* O - 0 = success, -1 = error */ +copy_file(const char *from, /* I - Source file */ + const char *to) /* I - Destination file */ +{ + cups_file_t *src, /* Source file */ + *dst; /* Destination file */ + int bytes; /* Bytes to read/write */ + char buffer[2048]; /* Copy buffer */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "copy_file(\"%s\", \"%s\")", from, to); + + /* + * Open the source and destination file for a copy... + */ + + if ((src = cupsFileOpen(from, "rb")) == NULL) + return (-1); + + if ((dst = cupsFileOpen(to, "wb")) == NULL) + { + cupsFileClose(src); + return (-1); + } + + /* + * Copy the source file to the destination... + */ + + while ((bytes = cupsFileRead(src, buffer, sizeof(buffer))) > 0) + if (cupsFileWrite(dst, buffer, bytes) < bytes) + { + cupsFileClose(src); + cupsFileClose(dst); + return (-1); + } + + /* + * Close both files and return... + */ + + cupsFileClose(src); + + return (cupsFileClose(dst)); +} + + +/* + * 'copy_model()' - Copy a PPD model file, substituting default values + * as needed... + */ + +static int /* O - 0 = success, -1 = error */ +copy_model(cupsd_client_t *con, /* I - Client connection */ + const char *from, /* I - Source file */ + const char *to) /* I - Destination file */ +{ + fd_set input; /* select() input set */ + struct timeval timeout; /* select() timeout */ + int maxfd; /* Max file descriptor for select() */ + char tempfile[1024]; /* Temporary PPD file */ + int tempfd; /* Temporary PPD file descriptor */ + int temppid; /* Process ID of cups-driverd */ + int temppipe[2]; /* Temporary pipes */ + char *argv[4], /* Command-line arguments */ + *envp[MAX_ENV]; /* Environment */ + cups_file_t *src, /* Source file */ + *dst; /* Destination file */ + ppd_file_t *ppd; /* PPD file */ + int bytes, /* Bytes from pipe */ + total; /* Total bytes from pipe */ + char buffer[2048]; /* Copy buffer */ + int i; /* Looping var */ + char option[PPD_MAX_NAME], /* Option name */ + choice[PPD_MAX_NAME]; /* Choice name */ + int num_defaults; /* Number of default options */ + cups_option_t *defaults; /* Default options */ + char cups_protocol[PPD_MAX_LINE]; + /* cupsProtocol attribute */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "copy_model(con=%p, from=\"%s\", to=\"%s\")", + con, from, to); + + /* + * Run cups-driverd to get the PPD file... + */ + + argv[0] = "cups-driverd"; + argv[1] = "cat"; + argv[2] = (char *)from; + argv[3] = NULL; + + cupsdLoadEnv(envp, (int)(sizeof(envp) / sizeof(envp[0]))); + + snprintf(buffer, sizeof(buffer), "%s/daemon/cups-driverd", ServerBin); + snprintf(tempfile, sizeof(tempfile), "%s/%d.ppd", TempDir, con->http.fd); + tempfd = open(tempfile, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (tempfd < 0) + return (-1); + + cupsdOpenPipe(temppipe); + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "copy_model: Running \"cups-driverd cat %s\"...", from); + + if (!cupsdStartProcess(buffer, argv, envp, -1, temppipe[1], CGIPipes[1], + -1, -1, 0, DefaultProfile, NULL, &temppid)) + { + close(tempfd); + unlink(tempfile); + + return (-1); + } + + close(temppipe[1]); + + /* + * Wait up to 30 seconds for the PPD file to be copied... + */ + + total = 0; + + if (temppipe[0] > CGIPipes[0]) + maxfd = temppipe[0] + 1; + else + maxfd = CGIPipes[0] + 1; + + for (;;) + { + /* + * See if we have data ready... + */ + + FD_ZERO(&input); + FD_SET(temppipe[0], &input); + FD_SET(CGIPipes[0], &input); + + timeout.tv_sec = 30; + timeout.tv_usec = 0; + + if ((i = select(maxfd, &input, NULL, NULL, &timeout)) < 0) + { + if (errno == EINTR) + continue; + else + break; + } + else if (i == 0) + { + /* + * We have timed out... + */ + + break; + } + + if (FD_ISSET(temppipe[0], &input)) + { + /* + * Read the PPD file from the pipe, and write it to the PPD file. + */ + + if ((bytes = read(temppipe[0], buffer, sizeof(buffer))) > 0) + { + if (write(tempfd, buffer, bytes) < bytes) + break; + + total += bytes; + } + else + break; + } + + if (FD_ISSET(CGIPipes[0], &input)) + cupsdUpdateCGI(); + } + + close(temppipe[0]); + close(tempfd); + + if (!total) + { + /* + * No data from cups-deviced... + */ + + cupsdLogMessage(CUPSD_LOG_ERROR, "copy_model: empty PPD file!"); + unlink(tempfile); + return (-1); + } + + /* + * Read the source file and see what page sizes are supported... + */ + + if ((ppd = ppdOpenFile(tempfile)) == NULL) + { + unlink(tempfile); + return (-1); + } + + /* + * Open the destination (if possible) and set the default options... + */ + + num_defaults = 0; + defaults = NULL; + cups_protocol[0] = '\0'; + + if ((dst = cupsFileOpen(to, "rb")) != NULL) + { + /* + * Read all of the default lines from the old PPD... + */ + + while (cupsFileGets(dst, buffer, sizeof(buffer))) + if (!strncmp(buffer, "*Default", 8)) + { + /* + * Add the default option... + */ + + if (!ppd_parse_line(buffer, option, sizeof(option), + choice, sizeof(choice))) + { + ppd_option_t *ppdo; /* PPD option */ + + + /* + * Only add the default if the default hasn't already been + * set and the choice exists in the new PPD... + */ + + if (!cupsGetOption(option, num_defaults, defaults) && + (ppdo = ppdFindOption(ppd, option)) != NULL && + ppdFindChoice(ppdo, choice)) + num_defaults = cupsAddOption(option, choice, num_defaults, + &defaults); + } + } + else if (!strncmp(buffer, "*cupsProtocol:", 14)) + strlcpy(cups_protocol, buffer, sizeof(cups_protocol)); + + cupsFileClose(dst); + } + else if (ppdPageSize(ppd, DefaultPaperSize)) + { + /* + * Add the default media sizes... + */ + + num_defaults = cupsAddOption("PageSize", DefaultPaperSize, + num_defaults, &defaults); + num_defaults = cupsAddOption("PageRegion", DefaultPaperSize, + num_defaults, &defaults); + num_defaults = cupsAddOption("PaperDimension", DefaultPaperSize, + num_defaults, &defaults); + num_defaults = cupsAddOption("ImageableArea", DefaultPaperSize, + num_defaults, &defaults); + } + + ppdClose(ppd); + + /* + * Open the source file for a copy... + */ + + if ((src = cupsFileOpen(tempfile, "rb")) == NULL) + { + cupsFreeOptions(num_defaults, defaults); + unlink(tempfile); + return (-1); + } + + /* + * Open the destination file for a copy... + */ + + if ((dst = cupsFileOpen(to, "wb")) == NULL) + { + cupsFreeOptions(num_defaults, defaults); + cupsFileClose(src); + unlink(tempfile); + return (-1); + } + + /* + * Copy the source file to the destination... + */ + + while (cupsFileGets(src, buffer, sizeof(buffer))) + { + if (!strncmp(buffer, "*Default", 8)) + { + /* + * Check for an previous default option choice... + */ + + if (!ppd_parse_line(buffer, option, sizeof(option), + choice, sizeof(choice))) + { + const char *val; /* Default option value */ + + + if ((val = cupsGetOption(option, num_defaults, defaults)) != NULL) + { + /* + * Substitute the previous choice... + */ + + snprintf(buffer, sizeof(buffer), "*Default%s: %s", option, val); + } + } + } + + cupsFilePrintf(dst, "%s\n", buffer); + } + + if (cups_protocol[0]) + cupsFilePrintf(dst, "%s\n", cups_protocol); + + cupsFreeOptions(num_defaults, defaults); + + /* + * Close both files and return... + */ + + cupsFileClose(src); + + unlink(tempfile); + + return (cupsFileClose(dst)); +} + + +/* + * 'copy_job_attrs()' - Copy job attributes. + */ + +static void +copy_job_attrs(cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job, /* I - Job */ + cups_array_t *ra) /* I - Requested attributes array */ +{ + char job_uri[HTTP_MAX_URI]; /* Job URI */ + + + /* + * Send the requested attributes for each job... + */ + + httpAssembleURIf(HTTP_URI_CODING_ALL, job_uri, sizeof(job_uri), "ipp", NULL, + con->servername, con->serverport, "/jobs/%d", + job->id); + + if (!ra || cupsArrayFind(ra, "document-count")) + ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, + "document-count", job->num_files); + + if (!ra || cupsArrayFind(ra, "job-media-progress")) + ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, + "job-media-progress", job->progress); + + if (!ra || cupsArrayFind(ra, "job-more-info")) + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, + "job-more-info", NULL, job_uri); + + if (job->state_value > IPP_JOB_PROCESSING && + (!ra || cupsArrayFind(ra, "job-preserved"))) + ippAddBoolean(con->response, IPP_TAG_JOB, "job-preserved", + job->num_files > 0); + + if (!ra || cupsArrayFind(ra, "job-printer-up-time")) + ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, + "job-printer-up-time", time(NULL)); + + if (!ra || cupsArrayFind(ra, "job-state-reasons")) + add_job_state_reasons(con, job); + + if (!ra || cupsArrayFind(ra, "job-uri")) + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, + "job-uri", NULL, job_uri); + + copy_attrs(con->response, job->attrs, ra, IPP_TAG_JOB, 0); +} + + +/* + * 'copy_printer_attrs()' - Copy printer attributes. + */ + +static void +copy_printer_attrs( + cupsd_client_t *con, /* I - Client connection */ + cupsd_printer_t *printer, /* I - Printer */ + cups_array_t *ra) /* I - Requested attributes array */ +{ + char printer_uri[HTTP_MAX_URI]; + /* Printer URI */ + time_t curtime; /* Current time */ + int i; /* Looping var */ + ipp_attribute_t *history; /* History collection */ + + + /* + * Copy the printer attributes to the response using requested-attributes + * and document-format attributes that may be provided by the client. + */ + + curtime = time(NULL); + +#ifdef __APPLE__ + if ((!ra || cupsArrayFind(ra, "com.apple.print.recoverable-message")) && + printer->recoverable) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "com.apple.print.recoverable-message", NULL, + printer->recoverable); +#endif /* __APPLE__ */ + + if (!ra || cupsArrayFind(ra, "marker-change-time")) + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "marker-change-time", printer->marker_time); + + if (printer->num_printers > 0 && + (!ra || cupsArrayFind(ra, "member-uris"))) + { + ipp_attribute_t *member_uris; /* member-uris attribute */ + cupsd_printer_t *p2; /* Printer in class */ + ipp_attribute_t *p2_uri; /* printer-uri-supported for class printer */ + + + if ((member_uris = ippAddStrings(con->response, IPP_TAG_PRINTER, + IPP_TAG_URI, "member-uris", + printer->num_printers, NULL, + NULL)) != NULL) + { + for (i = 0; i < printer->num_printers; i ++) + { + p2 = printer->printers[i]; + + if ((p2_uri = ippFindAttribute(p2->attrs, "printer-uri-supported", + IPP_TAG_URI)) != NULL) + member_uris->values[i].string.text = + _cupsStrRetain(p2_uri->values[0].string.text); else { - if (attr->num_values == 1 || - strcmp(attr->values[0].string.text, "none")) - cupsdSetString(&(attr->values[0].string.text), Classification); + httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, + sizeof(printer_uri), "ipp", NULL, con->servername, + con->serverport, + (p2->type & CUPS_PRINTER_CLASS) ? + "/classes/%s" : "/printers/%s", p2->name); + member_uris->values[i].string.text = _cupsStrAlloc(printer_uri); + } + } + } + } + + if (printer->alert && (!ra || cupsArrayFind(ra, "printer-alert"))) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_STRING, + "printer-alert", NULL, printer->alert); + + if (printer->alert_description && + (!ra || cupsArrayFind(ra, "printer-alert-description"))) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-alert-description", NULL, + printer->alert_description); + + if (!ra || cupsArrayFind(ra, "printer-current-time")) + ippAddDate(con->response, IPP_TAG_PRINTER, "printer-current-time", + ippTimeToDate(curtime)); + +#ifdef HAVE_DNSSD + if (!ra || cupsArrayFind(ra, "printer-dns-sd-name")) + { + if (printer->reg_name) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME, + "printer-dns-sd-name", NULL, printer->reg_name); + else + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_NOVALUE, + "printer-dns-sd-name", 0); + } +#endif /* HAVE_DNSSD */ + + if (!ra || cupsArrayFind(ra, "printer-error-policy")) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME, + "printer-error-policy", NULL, printer->error_policy); + + if (!ra || cupsArrayFind(ra, "printer-error-policy-supported")) + { + static const char * const errors[] =/* printer-error-policy-supported values */ + { + "abort-job", + "retry-current-job", + "retry-job", + "stop-printer" + }; + + if (printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS)) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "printer-error-policy-supported", NULL, "retry-current-job"); + else + ippAddStrings(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME | IPP_TAG_COPY, + "printer-error-policy-supported", + sizeof(errors) / sizeof(errors[0]), NULL, errors); + } + + if (!ra || cupsArrayFind(ra, "printer-is-accepting-jobs")) + ippAddBoolean(con->response, IPP_TAG_PRINTER, "printer-is-accepting-jobs", + printer->accepting); + + if (!ra || cupsArrayFind(ra, "printer-is-shared")) + ippAddBoolean(con->response, IPP_TAG_PRINTER, "printer-is-shared", + printer->shared); + + if ((!ra || cupsArrayFind(ra, "printer-more-info")) && + !(printer->type & CUPS_PRINTER_DISCOVERED)) + { + httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), + "http", NULL, con->servername, con->serverport, + (printer->type & CUPS_PRINTER_CLASS) ? + "/classes/%s" : "/printers/%s", printer->name); + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-more-info", NULL, printer_uri); + } + + if (!ra || cupsArrayFind(ra, "printer-op-policy")) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_NAME, + "printer-op-policy", NULL, printer->op_policy); + + if (!ra || cupsArrayFind(ra, "printer-state")) + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-state", + printer->state); + + if (!ra || cupsArrayFind(ra, "printer-state-change-time")) + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "printer-state-change-time", printer->state_time); + + if (MaxPrinterHistory > 0 && printer->num_history > 0 && + cupsArrayFind(ra, "printer-state-history")) + { + /* + * Printer history is only sent if specifically requested, so that + * older CUPS/IPP clients won't barf on the collection attributes. + */ + + history = ippAddCollections(con->response, IPP_TAG_PRINTER, + "printer-state-history", + printer->num_history, NULL); + + for (i = 0; i < printer->num_history; i ++) + copy_attrs(history->values[i].collection = ippNew(), printer->history[i], + NULL, IPP_TAG_ZERO, 0); + } + + if (!ra || cupsArrayFind(ra, "printer-state-message")) + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_TEXT, + "printer-state-message", NULL, printer->state_message); + + if (!ra || cupsArrayFind(ra, "printer-state-reasons")) + add_printer_state_reasons(con, printer); + + if (!ra || cupsArrayFind(ra, "printer-type")) + { + int type; /* printer-type value */ + + + /* + * Add the CUPS-specific printer-type attribute... + */ + + type = printer->type; + + if (printer == DefaultPrinter) + type |= CUPS_PRINTER_DEFAULT; + + if (!printer->accepting) + type |= CUPS_PRINTER_REJECTING; + + if (!printer->shared) + type |= CUPS_PRINTER_NOT_SHARED; + + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ENUM, "printer-type", + type); + } + + if (!ra || cupsArrayFind(ra, "printer-up-time")) + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_INTEGER, + "printer-up-time", curtime); + + if ((!ra || cupsArrayFind(ra, "printer-uri-supported")) && + !(printer->type & CUPS_PRINTER_DISCOVERED)) + { + httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), + "ipp", NULL, con->servername, con->serverport, + (printer->type & CUPS_PRINTER_CLASS) ? + "/classes/%s" : "/printers/%s", printer->name); + ippAddString(con->response, IPP_TAG_PRINTER, IPP_TAG_URI, + "printer-uri-supported", NULL, printer_uri); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "printer-uri-supported=\"%s\"", + printer_uri); + } + + if (!ra || cupsArrayFind(ra, "queued-job-count")) + add_queued_job_count(con, printer); + + copy_attrs(con->response, printer->attrs, ra, IPP_TAG_ZERO, 0); + if (printer->ppd_attrs) + copy_attrs(con->response, printer->ppd_attrs, ra, IPP_TAG_ZERO, 0); + copy_attrs(con->response, CommonData, ra, IPP_TAG_ZERO, IPP_TAG_COPY); +} + + +/* + * 'copy_subscription_attrs()' - Copy subscription attributes. + */ + +static void +copy_subscription_attrs( + cupsd_client_t *con, /* I - Client connection */ + cupsd_subscription_t *sub, /* I - Subscription */ + cups_array_t *ra) /* I - Requested attributes array */ +{ + ipp_attribute_t *attr; /* Current attribute */ + char printer_uri[HTTP_MAX_URI]; + /* Printer URI */ + int count; /* Number of events */ + unsigned mask; /* Current event mask */ + const char *name; /* Current event name */ + + + /* + * Copy the subscription attributes to the response using the + * requested-attributes attribute that may be provided by the client. + */ - if (attr->num_values > 1 && - strcmp(attr->values[1].string.text, "none")) - cupsdSetString(&(attr->values[1].string.text), Classification); - } + if (!ra || cupsArrayFind(ra, "notify-events")) + { + if ((name = cupsdEventName((cupsd_eventmask_t)sub->mask)) != NULL) + { + /* + * Simple event list... + */ - if (attr->num_values > 1) - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s,%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - attr->values[1].string.text, job->username); - else - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s\", " - "job-originating-user-name=\"%s\"", - job->id, Classification, job->username); - } + ippAddString(con->response, IPP_TAG_SUBSCRIPTION, + (ipp_tag_t)(IPP_TAG_KEYWORD | IPP_TAG_COPY), + "notify-events", NULL, name); } + else + { + /* + * Complex event list... + */ - /* - * See if we need to add the starting sheet... - */ + for (mask = 1, count = 0; mask < CUPSD_EVENT_ALL; mask <<= 1) + if (sub->mask & mask) + count ++; - if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))) - { - cupsdLogMessage(CUPSD_LOG_INFO, - "Adding start banner page \"%s\" to job %d.", - attr->values[0].string.text, job->id); + attr = ippAddStrings(con->response, IPP_TAG_SUBSCRIPTION, + (ipp_tag_t)(IPP_TAG_KEYWORD | IPP_TAG_COPY), + "notify-events", count, NULL, NULL); - kbytes = copy_banner(con, job, attr->values[0].string.text); + for (mask = 1, count = 0; mask < CUPSD_EVENT_ALL; mask <<= 1) + if (sub->mask & mask) + { + attr->values[count].string.text = + (char *)cupsdEventName((cupsd_eventmask_t)mask); - cupsdUpdateQuota(printer, job->username, 0, kbytes); + count ++; + } } } - else if ((attr = ippFindAttribute(job->attrs, "job-sheets", - IPP_TAG_ZERO)) != NULL) - job->sheets = attr; - /* - * Fill in the response info... - */ + if (sub->job && (!ra || cupsArrayFind(ra, "notify-job-id"))) + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-job-id", sub->job->id); - snprintf(job_uri, sizeof(job_uri), "http://%s:%d/jobs/%d", ServerName, - LocalPort, job->id); + if (!sub->job && (!ra || cupsArrayFind(ra, "notify-lease-duration"))) + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-lease-duration", sub->lease); - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, job_uri); + if (sub->dest && (!ra || cupsArrayFind(ra, "notify-printer-uri"))) + { + httpAssembleURIf(HTTP_URI_CODING_ALL, printer_uri, sizeof(printer_uri), + "ipp", NULL, con->servername, con->serverport, + "/printers/%s", sub->dest->name); + ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, + "notify-printer-uri", NULL, printer_uri); + } - ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); + if (sub->recipient && (!ra || cupsArrayFind(ra, "notify-recipient-uri"))) + ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_URI, + "notify-recipient-uri", NULL, sub->recipient); + else if (!ra || cupsArrayFind(ra, "notify-pull-method")) + ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_KEYWORD, + "notify-pull-method", NULL, "ippget"); - ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", - job->state->values[0].integer); + if (!ra || cupsArrayFind(ra, "notify-subscriber-user-name")) + ippAddString(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_NAME, + "notify-subscriber-user-name", NULL, sub->owner); - con->response->request.status.status_code = IPP_OK; + if (!ra || cupsArrayFind(ra, "notify-subscription-id")) + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-subscription-id", sub->id); + + if (!ra || cupsArrayFind(ra, "notify-time-interval")) + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-time-interval", sub->interval); + + if (sub->user_data_len > 0 && (!ra || cupsArrayFind(ra, "notify-user-data"))) + ippAddOctetString(con->response, IPP_TAG_SUBSCRIPTION, "notify-user-data", + sub->user_data, sub->user_data_len); +} + + +/* + * 'create_job()' - Print a file to a printer or class. + */ + +static void +create_job(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Printer URI */ +{ + cupsd_printer_t *printer; /* Printer */ + cupsd_job_t *job; /* New job */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "create_job(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); /* - * Add any job subscriptions... + * Is the destination valid? */ - add_job_subscriptions(con, job); + if (!cupsdValidateDest(uri->values[0].string.text, NULL, &printer)) + { + /* + * Bad URI... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } /* - * Set all but the first two attributes to the job attributes group... + * Create the job object... */ - for (attr = job->attrs->attrs->next->next; attr; attr = attr->next) - attr->group_tag = IPP_TAG_JOB; + if ((job = add_job(con, printer, NULL)) == NULL) + return; + + job->pending_timeout = 1; /* * Save and log the job... */ - - cupsdSaveJob(job); - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d created on \"%s\" by \"%s\".", - job->id, job->dest, job->username); - - cupsdAddEvent(CUPSD_EVENT_JOB_CREATED, printer, job, "Job created."); + cupsdLogJob(job, CUPSD_LOG_INFO, "Queued on \"%s\" by \"%s\".", + job->dest, job->username); } @@ -4462,6 +5864,7 @@ create_requested_array(ipp_t *request) /* I - IPP request */ cupsArrayAdd(ra, "job-impressions-completed"); cupsArrayAdd(ra, "job-k-octets"); cupsArrayAdd(ra, "job-k-octets-processed"); + cupsArrayAdd(ra, "job-media-progress"); cupsArrayAdd(ra, "job-media-sheets"); cupsArrayAdd(ra, "job-media-sheets-completed"); cupsArrayAdd(ra, "job-message-from-operator"); @@ -4494,6 +5897,7 @@ create_requested_array(ipp_t *request) /* I - IPP request */ cupsArrayAdd(ra, "job-impressions-supported"); cupsArrayAdd(ra, "job-k-octets-supported"); cupsArrayAdd(ra, "job-media-sheets-supported"); + cupsArrayAdd(ra, "job-settable-attributes-supported"); cupsArrayAdd(ra, "multiple-document-jobs-supported"); cupsArrayAdd(ra, "multiple-operation-time-out"); cupsArrayAdd(ra, "natural-language-configured"); @@ -4501,16 +5905,20 @@ create_requested_array(ipp_t *request) /* I - IPP request */ cupsArrayAdd(ra, "notify-lease-duration-default"); cupsArrayAdd(ra, "notify-lease-duration-supported"); cupsArrayAdd(ra, "notify-max-events-supported"); - cupsArrayAdd(ra, "notify-notify-events-default"); - cupsArrayAdd(ra, "notify-notify-events-supported"); + cupsArrayAdd(ra, "notify-events-default"); + cupsArrayAdd(ra, "notify-events-supported"); cupsArrayAdd(ra, "notify-pull-method-supported"); cupsArrayAdd(ra, "notify-schemes-supported"); cupsArrayAdd(ra, "operations-supported"); cupsArrayAdd(ra, "pages-per-minute"); cupsArrayAdd(ra, "pages-per-minute-color"); cupsArrayAdd(ra, "pdl-override-supported"); + cupsArrayAdd(ra, "printer-alert"); + cupsArrayAdd(ra, "printer-alert-description"); + cupsArrayAdd(ra, "printer-commands"); cupsArrayAdd(ra, "printer-current-time"); cupsArrayAdd(ra, "printer-driver-installer"); + cupsArrayAdd(ra, "printer-dns-sd-name"); cupsArrayAdd(ra, "printer-info"); cupsArrayAdd(ra, "printer-is-accepting-jobs"); cupsArrayAdd(ra, "printer-location"); @@ -4522,6 +5930,8 @@ create_requested_array(ipp_t *request) /* I - IPP request */ cupsArrayAdd(ra, "printer-state"); cupsArrayAdd(ra, "printer-state-message"); cupsArrayAdd(ra, "printer-state-reasons"); + cupsArrayAdd(ra, "printer-settable-attributes-supported"); + cupsArrayAdd(ra, "printer-type"); cupsArrayAdd(ra, "printer-up-time"); cupsArrayAdd(ra, "printer-uri-supported"); cupsArrayAdd(ra, "queued-job-count"); @@ -4529,6 +5939,16 @@ create_requested_array(ipp_t *request) /* I - IPP request */ cupsArrayAdd(ra, "uri-authentication-supported"); cupsArrayAdd(ra, "uri-security-supported"); } + else if (!strcmp(value, "printer-defaults")) + { + char *name; /* Option name */ + + + for (name = (char *)cupsArrayFirst(CommonDefaults); + name; + name = (char *)cupsArrayNext(CommonDefaults)) + cupsArrayAdd(ra, name); + } else if (!strcmp(value, "subscription-template")) { cupsArrayAdd(ra, "notify-attributes"); @@ -4561,10 +5981,9 @@ create_subscription( http_status_t status; /* Policy status */ int i; /* Looping var */ ipp_attribute_t *attr; /* Current attribute */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + char scheme[HTTP_MAX_URI], + /* Scheme portion of URI */ userpass[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], @@ -4576,14 +5995,28 @@ create_subscription( cupsd_job_t *job; /* Job */ int jobid; /* Job ID */ cupsd_subscription_t *sub; /* Subscription object */ - const char *username, /* requesting-user-name or authenticated username */ + const char *username, /* requesting-user-name or + authenticated username */ *recipient, /* notify-recipient-uri */ *pullmethod; /* notify-pull-method */ ipp_attribute_t *user_data; /* notify-user-data */ int interval, /* notify-time-interval */ lease; /* notify-lease-duration */ unsigned mask; /* notify-events */ + ipp_attribute_t *notify_events,/* notify-events(-default) */ + *notify_lease; /* notify-lease-duration(-default) */ + +#ifdef DEBUG + for (attr = con->request->attrs; attr; attr = attr->next) + { + if (attr->group_tag != IPP_TAG_ZERO) + cupsdLogMessage(CUPSD_LOG_DEBUG2, "g%04x v%04x %s", attr->group_tag, + attr->value_tag, attr->name); + else + cupsdLogMessage(CUPSD_LOG_DEBUG2, "----SEP----"); + } +#endif /* DEBUG */ /* * Is the destination valid? @@ -4593,29 +6026,26 @@ create_subscription( "cupsdCreateSubscription(con=%p(%d), uri=\"%s\")", con, con->http.fd, uri->values[0].string.text); - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), userpass, sizeof(userpass), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), userpass, sizeof(userpass), host, sizeof(host), &port, resource, sizeof(resource)); if (!strcmp(resource, "/")) { - dest = NULL; dtype = (cups_ptype_t)0; printer = NULL; } else if (!strncmp(resource, "/printers", 9) && strlen(resource) <= 10) { - dest = NULL; dtype = (cups_ptype_t)0; printer = NULL; } else if (!strncmp(resource, "/classes", 8) && strlen(resource) <= 9) { - dest = NULL; dtype = CUPS_PRINTER_CLASS; printer = NULL; } - else if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + else if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -4632,15 +6062,16 @@ create_subscription( if (printer) { - if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, + NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } } else if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -4648,19 +6079,7 @@ create_subscription( * Get the user that is requesting the subscription... */ - if (con->username[0]) - username = con->username; - else if ((attr = ippFindAttribute(con->request, "requesting-user-name", - IPP_TAG_NAME)) != NULL) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "create_subscription: requesting-user-name = \"%s\"", - attr->values[0].string.text); - - username = attr->values[0].string.text; - } - else - username = "anonymous"; + username = get_username(con); /* * Find the first subscription group attribute; return if we have @@ -4694,14 +6113,86 @@ create_subscription( jobid = 0; mask = CUPSD_EVENT_NONE; + if (printer) + { + notify_events = ippFindAttribute(printer->attrs, "notify-events-default", + IPP_TAG_KEYWORD); + notify_lease = ippFindAttribute(printer->attrs, + "notify-lease-duration-default", + IPP_TAG_INTEGER); + + if (notify_lease) + lease = notify_lease->values[0].integer; + } + else + { + notify_events = NULL; + notify_lease = NULL; + } + while (attr && attr->group_tag != IPP_TAG_ZERO) { - if (!strcmp(attr->name, "notify-recipient") && + if (!strcmp(attr->name, "notify-recipient-uri") && attr->value_tag == IPP_TAG_URI) + { + /* + * Validate the recipient scheme against the ServerBin/notifier + * directory... + */ + + char notifier[1024]; /* Notifier filename */ + + recipient = attr->values[0].string.text; + + if (httpSeparateURI(HTTP_URI_CODING_ALL, recipient, + scheme, sizeof(scheme), userpass, sizeof(userpass), + host, sizeof(host), &port, + resource, sizeof(resource)) < HTTP_URI_OK) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Bad notify-recipient-uri URI \"%s\"!"), recipient); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_URI_SCHEME); + return; + } + + snprintf(notifier, sizeof(notifier), "%s/notifier/%s", ServerBin, + scheme); + if (access(notifier, X_OK)) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("notify-recipient-uri URI \"%s\" uses unknown " + "scheme!"), recipient); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_URI_SCHEME); + return; + } + + if (!strcmp(scheme, "rss") && !check_rss_recipient(recipient)) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("notify-recipient-uri URI \"%s\" is already used!"), + recipient); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_ATTRIBUTES); + return; + } + } else if (!strcmp(attr->name, "notify-pull-method") && attr->value_tag == IPP_TAG_KEYWORD) + { pullmethod = attr->values[0].string.text; + + if (strcmp(pullmethod, "ippget")) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Bad notify-pull-method \"%s\"!"), pullmethod); + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_ENUM, + "notify-status-code", IPP_ATTRIBUTES); + return; + } + } else if (!strcmp(attr->name, "notify-charset") && attr->value_tag == IPP_TAG_CHARSET && strcmp(attr->values[0].string.text, "us-ascii") && @@ -4737,10 +6228,7 @@ create_subscription( } else if (!strcmp(attr->name, "notify-events") && attr->value_tag == IPP_TAG_KEYWORD) - { - for (i = 0; i < attr->num_values; i ++) - mask |= cupsdEventValue(attr->values[i].string.text); - } + notify_events = attr; else if (!strcmp(attr->name, "notify-lease-duration") && attr->value_tag == IPP_TAG_INTEGER) lease = attr->values[0].integer; @@ -4754,8 +6242,18 @@ create_subscription( attr = attr->next; } - cupsdLogMessage(CUPSD_LOG_DEBUG, "recipient=\"%s\"", recipient); - cupsdLogMessage(CUPSD_LOG_DEBUG, "pullmethod=\"%s\"", pullmethod); + if (notify_events) + { + for (i = 0; i < notify_events->num_values; i ++) + mask |= cupsdEventValue(notify_events->values[i].string.text); + } + + if (recipient) + cupsdLogMessage(CUPSD_LOG_DEBUG, "recipient=\"%s\"", recipient); + if (pullmethod) + cupsdLogMessage(CUPSD_LOG_DEBUG, "pullmethod=\"%s\"", pullmethod); + cupsdLogMessage(CUPSD_LOG_DEBUG, "notify-lease-duration=%d", lease); + cupsdLogMessage(CUPSD_LOG_DEBUG, "notify-time-interval=%d", interval); if (!recipient && !pullmethod) break; @@ -4774,7 +6272,7 @@ create_subscription( } } - if (MaxLeaseDuration && lease > MaxLeaseDuration) + if (MaxLeaseDuration && (lease == 0 || lease > MaxLeaseDuration)) { cupsdLogMessage(CUPSD_LOG_INFO, "create_subscription: Limiting notify-lease-duration to " @@ -4794,7 +6292,12 @@ create_subscription( else job = NULL; - sub = cupsdAddSubscription(mask, printer, job, recipient, 0); + if ((sub = cupsdAddSubscription(mask, printer, job, recipient, 0)) == NULL) + { + send_ipp_status(con, IPP_TOO_MANY_SUBSCRIPTIONS, + _("There are too many subscriptions.")); + return; + } if (job) cupsdLogMessage(CUPSD_LOG_DEBUG, "Added subscription %d for job %d", @@ -4809,7 +6312,7 @@ create_subscription( sub->interval = interval; sub->lease = lease; - sub->expire = time(NULL) + lease; + sub->expire = lease ? time(NULL) + lease : 0; cupsdSetString(&sub->owner, username); @@ -4830,8 +6333,7 @@ create_subscription( attr = attr->next; } - cupsdSaveAllSubscriptions(); - + cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS); } @@ -4844,13 +6346,7 @@ delete_printer(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - URI of printer or class */ { http_status_t status; /* Policy status */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cupsd_printer_t *printer; /* Printer/class */ char filename[1024]; /* Script/PPD filename */ @@ -4862,11 +6358,7 @@ delete_printer(cupsd_client_t *con, /* I - Client connection */ * Do we have a valid URI? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -4883,7 +6375,7 @@ delete_printer(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -4891,7 +6383,7 @@ delete_printer(cupsd_client_t *con, /* I - Client connection */ * Remove old jobs... */ - cupsdCancelJobs(dest, NULL, 1); + cupsdCancelJobs(printer->name, NULL, 1); /* * Remove old subscriptions and send a "deleted printer" event... @@ -4900,38 +6392,51 @@ delete_printer(cupsd_client_t *con, /* I - Client connection */ cupsdAddEvent(CUPSD_EVENT_PRINTER_DELETED, printer, NULL, "%s \"%s\" deleted by \"%s\".", (dtype & CUPS_PRINTER_CLASS) ? "Class" : "Printer", - dest, con->username); + printer->name, get_username(con)); cupsdExpireSubscriptions(printer, NULL); - + /* * Remove any old PPD or script files... */ - snprintf(filename, sizeof(filename), "%s/interfaces/%s", ServerRoot, dest); + snprintf(filename, sizeof(filename), "%s/interfaces/%s", ServerRoot, + printer->name); + unlink(filename); + + snprintf(filename, sizeof(filename), "%s/ppd/%s.ppd", ServerRoot, + printer->name); unlink(filename); - snprintf(filename, sizeof(filename), "%s/ppd/%s.ppd", ServerRoot, dest); + snprintf(filename, sizeof(filename), "%s/%s.ipp", CacheDir, printer->name); unlink(filename); +#ifdef __APPLE__ + /* + * Unregister color profiles... + */ + + apple_unregister_profiles(printer); +#endif /* __APPLE__ */ + if (dtype & CUPS_PRINTER_CLASS) { - cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" deleted by \"%s\".", dest, - con->username); + cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" deleted by \"%s\".", + printer->name, get_username(con)); cupsdDeletePrinter(printer, 0); - cupsdSaveAllClasses(); + cupsdMarkDirty(CUPSD_DIRTY_CLASSES); } else { - cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" deleted by \"%s\".", dest, - con->username); + cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" deleted by \"%s\".", + printer->name, get_username(con)); cupsdDeletePrinter(printer, 0); - cupsdSaveAllPrinters(); + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); } - cupsdWritePrintcap(); + cupsdMarkDirty(CUPSD_DIRTY_PRINTCAP); /* * Return with no errors... @@ -4960,7 +6465,7 @@ get_default(cupsd_client_t *con) /* I - Client connection */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -4975,111 +6480,245 @@ get_default(cupsd_client_t *con) /* I - Client connection */ con->response->request.status.status_code = IPP_OK; } else - send_ipp_status(con, IPP_NOT_FOUND, _("No default printer")); + send_ipp_status(con, IPP_NOT_FOUND, _("No default printer")); +} + + +/* + * 'get_devices()' - Get the list of available devices on the local system. + */ + +static void +get_devices(cupsd_client_t *con) /* I - Client connection */ +{ + http_status_t status; /* Policy status */ + ipp_attribute_t *limit, /* limit attribute */ + *timeout, /* timeout attribute */ + *requested, /* requested-attributes attribute */ + *exclude, /* exclude-schemes attribute */ + *include; /* include-schemes attribute */ + char command[1024], /* cups-deviced command */ + options[2048], /* Options to pass to command */ + requested_str[256], + /* String for requested attributes */ + exclude_str[512], + /* String for excluded schemes */ + include_str[512]; + /* String for included schemes */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_devices(%p[%d])", con, con->http.fd); + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, NULL); + return; + } + + /* + * Run cups-deviced command with the given options... + */ + + limit = ippFindAttribute(con->request, "limit", IPP_TAG_INTEGER); + timeout = ippFindAttribute(con->request, "timeout", IPP_TAG_INTEGER); + requested = ippFindAttribute(con->request, "requested-attributes", + IPP_TAG_KEYWORD); + exclude = ippFindAttribute(con->request, "exclude-schemes", IPP_TAG_NAME); + include = ippFindAttribute(con->request, "include-schemes", IPP_TAG_NAME); + + if (requested) + url_encode_attr(requested, requested_str, sizeof(requested_str)); + else + strlcpy(requested_str, "requested-attributes=all", sizeof(requested_str)); + + if (exclude) + url_encode_attr(exclude, exclude_str, sizeof(exclude_str)); + else + exclude_str[0] = '\0'; + + if (include) + url_encode_attr(include, include_str, sizeof(include_str)); + else + include_str[0] = '\0'; + + snprintf(command, sizeof(command), "%s/daemon/cups-deviced", ServerBin); + snprintf(options, sizeof(options), + "%d+%d+%d+%d+%s%s%s%s%s", + con->request->request.op.request_id, + limit ? limit->values[0].integer : 0, + timeout ? timeout->values[0].integer : 10, + (int)User, + requested_str, + exclude_str[0] ? "%20" : "", exclude_str, + include_str[0] ? "%20" : "", include_str); + + if (cupsdSendCommand(con, command, options, 1)) + { + /* + * Command started successfully, don't send an IPP response here... + */ + + ippDelete(con->response); + con->response = NULL; + } + else + { + /* + * Command failed, return "internal error" so the user knows something + * went wrong... + */ + + send_ipp_status(con, IPP_INTERNAL_ERROR, + _("cups-deviced failed to execute.")); + } } /* - * 'get_devices()' - Get the list of available devices on the local system. + * 'get_document()' - Get a copy of a job file. */ static void -get_devices(cupsd_client_t *con) /* I - Client connection */ +get_document(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Job URI */ { - http_status_t status; /* Policy status */ - int i; /* Looping var */ - ipp_attribute_t *limit, /* Limit attribute */ - *requested; /* requested-attributes attribute */ - char command[1024], /* cups-deviced command */ - options[1024], /* Options to pass to command */ - attrs[1024], /* String for requested attributes */ - *aptr; /* Pointer into string */ - int alen; /* Length of attribute value */ + http_status_t status; /* Policy status */ + ipp_attribute_t *attr; /* Current attribute */ + int jobid; /* Job ID */ + int docnum; /* Document number */ + cupsd_job_t *job; /* Current job */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ + username[HTTP_MAX_URI], /* Username portion of URI */ + host[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ + int port; /* Port portion of URI */ + char filename[1024], /* Filename for document */ + format[1024]; /* Format for document */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_devices(%p[%d])", con, con->http.fd); + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_document(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); /* - * Check policy... + * See if we have a job URI or a printer URI... */ - if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + if (!strcmp(uri->name, "printer-uri")) { - send_http_error(con, status); - return; - } - - /* - * Run cups-deviced command with the given options... - */ + /* + * Got a printer URI; see if we also have a job-id attribute... + */ - limit = ippFindAttribute(con->request, "limit", IPP_TAG_INTEGER); - requested = ippFindAttribute(con->request, "requested-attributes", - IPP_TAG_KEYWORD); + if ((attr = ippFindAttribute(con->request, "job-id", + IPP_TAG_INTEGER)) == NULL) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Got a printer-uri attribute but no job-id!")); + return; + } - if (requested) + jobid = attr->values[0].integer; + } + else { - for (i = 0, aptr = attrs; i < requested->num_values; i ++) - { - /* - * Check that we have enough room... - */ + /* + * Got a job URI; parse it to get the job ID... + */ - alen = strlen(requested->values[i].string.text); - if (alen > (sizeof(attrs) - (aptr - attrs) - 2)) - break; + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); + if (strncmp(resource, "/jobs/", 6)) + { /* - * Put commas between values... + * Not a valid URI! */ - if (i) - *aptr++ = ','; + send_ipp_status(con, IPP_BAD_REQUEST, + _("Bad job-uri attribute \"%s\"!"), + uri->values[0].string.text); + return; + } - /* - * Add the value to the end of the string... - */ + jobid = atoi(resource + 6); + } - strcpy(aptr, requested->values[i].string.text); - aptr += alen; - } + /* + * See if the job exists... + */ + if ((job = cupsdFindJob(jobid)) == NULL) + { /* - * If we have more attribute names than will fit, default to "all"... + * Nope - return a "not found" error... */ - if (i < requested->num_values) - strcpy(attrs, "all"); + send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist!"), jobid); + return; } - else - strcpy(attrs, "all"); - snprintf(command, sizeof(command), "%s/daemon/cups-deviced", ServerBin); - snprintf(options, sizeof(options), - "cups-deviced %d+%d+requested-attributes=%s", - con->request->request.op.request_id, - limit ? limit->values[0].integer : 0, - attrs); + /* + * Check policy... + */ - if (cupsdSendCommand(con, command, options, 1)) + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - /* - * Command started successfully, don't send an IPP response here... - */ + send_http_error(con, status, NULL); + return; + } - ippDelete(con->response); - con->response = NULL; + /* + * Get the document number... + */ + + if ((attr = ippFindAttribute(con->request, "document-number", + IPP_TAG_INTEGER)) == NULL) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Missing document-number attribute!")); + return; } - else + + if ((docnum = attr->values[0].integer) < 1 || docnum > job->num_files || + attr->num_values > 1) { - /* - * Command failed, return "internal error" so the user knows something - * went wrong... - */ + send_ipp_status(con, IPP_NOT_FOUND, _("Document %d not found in job %d."), + docnum, jobid); + return; + } - send_ipp_status(con, IPP_INTERNAL_ERROR, - _("cups-deviced failed to execute.")); + snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, jobid, + docnum); + if ((con->file = open(filename, O_RDONLY)) == -1) + { + cupsdLogMessage(CUPSD_LOG_ERROR, + "Unable to open document %d in job %d - %s", docnum, jobid, + strerror(errno)); + send_ipp_status(con, IPP_NOT_FOUND, + _("Unable to open document %d in job %d!"), docnum, jobid); + return; } + + fcntl(con->file, F_SETFD, fcntl(con->file, F_GETFD) | FD_CLOEXEC); + + cupsdLoadJob(job); + + snprintf(format, sizeof(format), "%s/%s", job->filetypes[docnum - 1]->super, + job->filetypes[docnum - 1]->type); + + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_MIMETYPE, "document-format", + NULL, format); + ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "document-number", + docnum); + if ((attr = ippFindAttribute(job->attrs, "document-name", + IPP_TAG_NAME)) != NULL) + ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_NAME, "document-name", + NULL, attr->values[0].string.text); } @@ -5095,7 +6734,7 @@ get_job_attrs(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *attr; /* Current attribute */ int jobid; /* Job ID */ cupsd_job_t *job; /* Current job */ - char method[HTTP_MAX_URI], /* Method portion of URI */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ @@ -5132,8 +6771,8 @@ get_job_attrs(cupsd_client_t *con, /* I - Client connection */ * Got a job URI; parse it to get the job ID... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); if (strncmp(resource, "/jobs/", 6)) @@ -5171,7 +6810,7 @@ get_job_attrs(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -5179,6 +6818,8 @@ get_job_attrs(cupsd_client_t *con, /* I - Client connection */ * Copy attributes... */ + cupsdLoadJob(job); + ra = create_requested_array(con->request); copy_job_attrs(con, job, ra); cupsArrayDelete(ra); @@ -5198,9 +6839,9 @@ get_jobs(cupsd_client_t *con, /* I - Client connection */ http_status_t status; /* Policy status */ ipp_attribute_t *attr; /* Current attribute */ const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cups_ptype_t dmask; /* Destination type mask */ - char method[HTTP_MAX_URI], /* Method portion of URI */ + char scheme[HTTP_MAX_URI], /* Scheme portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ @@ -5222,12 +6863,17 @@ get_jobs(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + if (strcmp(uri->name, "printer-uri")) + { + send_ipp_status(con, IPP_BAD_REQUEST, _("No printer-uri in request!")); + return; + } + + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); - if (!strcmp(resource, "/") || - (!strncmp(resource, "/jobs", 5) && strlen(resource) <= 6)) + if (!strcmp(resource, "/") || !strcmp(resource, "/jobs")) { dest = NULL; dtype = (cups_ptype_t)0; @@ -5248,7 +6894,8 @@ get_jobs(cupsd_client_t *con, /* I - Client connection */ dmask = CUPS_PRINTER_CLASS; printer = NULL; } - else if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + else if ((dest = cupsdValidateDest(uri->values[0].string.text, &dtype, + &printer)) == NULL) { /* * Bad URI... @@ -5259,7 +6906,10 @@ get_jobs(cupsd_client_t *con, /* I - Client connection */ return; } else + { + dtype &= CUPS_PRINTER_CLASS; dmask = CUPS_PRINTER_CLASS; + } /* * Check policy... @@ -5267,130 +6917,418 @@ get_jobs(cupsd_client_t *con, /* I - Client connection */ if (printer) { - if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, + NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } } else if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); + return; + } + + /* + * See if the "which-jobs" attribute have been specified... + */ + + if ((attr = ippFindAttribute(con->request, "which-jobs", + IPP_TAG_KEYWORD)) != NULL && + !strcmp(attr->values[0].string.text, "completed")) + { + completed = 1; + list = Jobs; + } + else if (attr && !strcmp(attr->values[0].string.text, "all")) + { + completed = 0; + list = Jobs; + } + else if (attr && !strcmp(attr->values[0].string.text, "processing")) + { + completed = 0; + list = PrintingJobs; + } + else + { + completed = 0; + list = ActiveJobs; + } + + /* + * See if they want to limit the number of jobs reported... + */ + + if ((attr = ippFindAttribute(con->request, "limit", + IPP_TAG_INTEGER)) != NULL) + limit = attr->values[0].integer; + else + limit = 0; + + if ((attr = ippFindAttribute(con->request, "first-job-id", + IPP_TAG_INTEGER)) != NULL) + first_job_id = attr->values[0].integer; + else + first_job_id = 1; + + /* + * See if we only want to see jobs for a specific user... + */ + + if ((attr = ippFindAttribute(con->request, "my-jobs", + IPP_TAG_BOOLEAN)) != NULL && + attr->values[0].boolean) + strlcpy(username, get_username(con), sizeof(username)); + else + username[0] = '\0'; + + ra = create_requested_array(con->request); + + /* + * OK, build a list of jobs for this printer... + */ + + for (count = 0, job = (cupsd_job_t *)cupsArrayFirst(list); + (limit <= 0 || count < limit) && job; + job = (cupsd_job_t *)cupsArrayNext(list)) + { + /* + * Filter out jobs that don't match... + */ + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "get_jobs: job->id=%d, dest=\"%s\", username=\"%s\", " + "state_value=%d, attrs=%p", job->id, job->dest, + job->username, job->state_value, job->attrs); + + if (!job->dest || !job->username) + cupsdLoadJob(job); + + if (!job->dest || !job->username) + continue; + + if ((dest && strcmp(job->dest, dest)) && + (!job->printer || !dest || strcmp(job->printer->name, dest))) + continue; + if ((job->dtype & dmask) != dtype && + (!job->printer || (job->printer->type & dmask) != dtype)) + continue; + if (completed && job->state_value <= IPP_JOB_STOPPED) + continue; + + if (job->id < first_job_id) + continue; + + cupsdLoadJob(job); + + if (!job->attrs) + { + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: No attributes for job %d!", + job->id); + continue; + } + + if (username[0] && strcasecmp(username, job->username)) + continue; + + if (count > 0) + ippAddSeparator(con->response); + + count ++; + + copy_job_attrs(con, job, ra); + } + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count=%d", count); + + cupsArrayDelete(ra); + + con->response->request.status.status_code = IPP_OK; +} + + +/* + * 'get_notifications()' - Get events for a subscription. + */ + +static void +get_notifications(cupsd_client_t *con) /* I - Client connection */ +{ + int i, j; /* Looping vars */ + http_status_t status; /* Policy status */ + cupsd_subscription_t *sub; /* Subscription */ + ipp_attribute_t *ids, /* notify-subscription-ids */ + *sequences; /* notify-sequence-numbers */ + int min_seq; /* Minimum sequence number */ + int interval; /* Poll interval */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_notifications(con=%p[%d])", + con, con->http.fd); + + /* + * Get subscription attributes... + */ + + ids = ippFindAttribute(con->request, "notify-subscription-ids", + IPP_TAG_INTEGER); + sequences = ippFindAttribute(con->request, "notify-sequence-numbers", + IPP_TAG_INTEGER); + + if (!ids) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Missing notify-subscription-ids attribute!")); return; } /* - * See if the "which-jobs" attribute have been specified... + * Are the subscription IDs valid? */ - if ((attr = ippFindAttribute(con->request, "which-jobs", - IPP_TAG_KEYWORD)) != NULL && - !strcmp(attr->values[0].string.text, "completed")) - { - completed = 1; - list = Jobs; - } - else if (attr && !strcmp(attr->values[0].string.text, "all")) - { - completed = 0; - list = Jobs; - } - else + for (i = 0, interval = 60; i < ids->num_values; i ++) { - completed = 0; - list = ActiveJobs; - } + if ((sub = cupsdFindSubscription(ids->values[i].integer)) == NULL) + { + /* + * Bad subscription ID... + */ - /* - * See if they want to limit the number of jobs reported... - */ + send_ipp_status(con, IPP_NOT_FOUND, + _("notify-subscription-id %d no good!"), + ids->values[i].integer); + return; + } - if ((attr = ippFindAttribute(con->request, "limit", - IPP_TAG_INTEGER)) != NULL) - limit = attr->values[0].integer; - else - limit = 1000000; + /* + * Check policy... + */ - if ((attr = ippFindAttribute(con->request, "first-job-id", - IPP_TAG_INTEGER)) != NULL) - first_job_id = attr->values[0].integer; - else - first_job_id = 1; + if ((status = cupsdCheckPolicy(sub->dest ? sub->dest->op_policy_ptr : + DefaultPolicyPtr, + con, sub->owner)) != HTTP_OK) + { + send_http_error(con, status, sub->dest); + return; + } + + /* + * Check the subscription type and update the interval accordingly. + */ + + if (sub->job && sub->job->state_value == IPP_JOB_PROCESSING && + interval > 10) + interval = 10; + else if (sub->job && sub->job->state_value >= IPP_JOB_STOPPED) + interval = 0; + else if (sub->dest && sub->dest->state == IPP_PRINTER_PROCESSING && + interval > 30) + interval = 30; + } /* - * See if we only want to see jobs for a specific user... + * Tell the client to poll again in N seconds... */ - if ((attr = ippFindAttribute(con->request, "my-jobs", - IPP_TAG_BOOLEAN)) != NULL && - attr->values[0].boolean) - { - if (con->username[0]) - strlcpy(username, con->username, sizeof(username)); - else if ((attr = ippFindAttribute(con->request, "requesting-user-name", - IPP_TAG_NAME)) != NULL) - strlcpy(username, attr->values[0].string.text, sizeof(username)); - else - strcpy(username, "anonymous"); - } - else - username[0] = '\0'; + if (interval > 0) + ippAddInteger(con->response, IPP_TAG_OPERATION, IPP_TAG_INTEGER, + "notify-get-interval", interval); - ra = create_requested_array(con->request); + ippAddInteger(con->response, IPP_TAG_OPERATION, IPP_TAG_INTEGER, + "printer-up-time", time(NULL)); /* - * OK, build a list of jobs for this printer... + * Copy the subscription event attributes to the response. */ - for (count = 0, job = (cupsd_job_t *)cupsArrayFirst(list); - count < limit && job; - job = (cupsd_job_t *)cupsArrayNext(list)) + con->response->request.status.status_code = + interval ? IPP_OK : IPP_OK_EVENTS_COMPLETE; + + for (i = 0; i < ids->num_values; i ++) { /* - * Filter out jobs that don't match... + * Get the subscription and sequence number... */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: job->id = %d", job->id); + sub = cupsdFindSubscription(ids->values[i].integer); - if ((dest && strcmp(job->dest, dest)) && - (!job->printer || !dest || strcmp(job->printer->name, dest))) - continue; - if ((job->dtype & dmask) != dtype && - (!job->printer || (job->printer->type & dmask) != dtype)) - continue; - if (username[0] && strcasecmp(username, job->username)) - continue; + if (sequences && i < sequences->num_values) + min_seq = sequences->values[i].integer; + else + min_seq = 1; - if (completed && job->state->values[0].integer <= IPP_JOB_STOPPED) - continue; + /* + * If we don't have any new events, nothing to do here... + */ - if (job->id < first_job_id) + if (min_seq > (sub->first_event_id + sub->num_events)) continue; - if (count > 0) - ippAddSeparator(con->response); + /* + * Otherwise copy all of the new events... + */ - count ++; + if (sub->first_event_id > min_seq) + j = 0; + else + j = min_seq - sub->first_event_id; - cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_jobs: count = %d", count); + for (; j < sub->num_events; j ++) + { + ippAddSeparator(con->response); - copy_job_attrs(con, job, ra); + copy_attrs(con->response, sub->events[j]->attrs, NULL, + IPP_TAG_EVENT_NOTIFICATION, 0); + } } - - cupsArrayDelete(ra); - - con->response->request.status.status_code = IPP_OK; } /* - * 'get_notifications()' - Get events for a subscription. + * 'get_ppd()' - Get a named PPD from the local system. */ static void -get_notifications(cupsd_client_t *con, /* I - Client connection */ - int id) /* I - Subscription ID */ +get_ppd(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Printer URI or PPD name */ { + http_status_t status; /* Policy status */ + cupsd_printer_t *dest; /* Destination */ + cups_ptype_t dtype; /* Destination type */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_ppd(%p[%d], %p[%s=%s])", con, + con->http.fd, uri, uri->name, uri->values[0].string.text); + + if (!strcmp(uri->name, "ppd-name")) + { + /* + * Return a PPD file from cups-driverd... + */ + + char command[1024], /* cups-driverd command */ + options[1024], /* Options to pass to command */ + ppd_name[1024]; /* ppd-name */ + + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, NULL); + return; + } + + /* + * Run cups-driverd command with the given options... + */ + + snprintf(command, sizeof(command), "%s/daemon/cups-driverd", ServerBin); + url_encode_string(uri->values[0].string.text, ppd_name, sizeof(ppd_name)); + snprintf(options, sizeof(options), "get+%d+%s", + con->request->request.op.request_id, ppd_name); + + if (cupsdSendCommand(con, command, options, 0)) + { + /* + * Command started successfully, don't send an IPP response here... + */ + + ippDelete(con->response); + con->response = NULL; + } + else + { + /* + * Command failed, return "internal error" so the user knows something + * went wrong... + */ + + send_ipp_status(con, IPP_INTERNAL_ERROR, + _("cups-driverd failed to execute.")); + } + } + else if (!strcmp(uri->name, "printer-uri") && + cupsdValidateDest(uri->values[0].string.text, &dtype, &dest)) + { + int i; /* Looping var */ + char filename[1024]; /* PPD filename */ + + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(dest->op_policy_ptr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, dest); + return; + } + + /* + * See if we need the PPD for a class or remote printer... + */ + + snprintf(filename, sizeof(filename), "%s/ppd/%s.ppd", ServerRoot, + dest->name); + + if ((dtype & CUPS_PRINTER_REMOTE) && access(filename, 0)) + { + con->response->request.status.status_code = CUPS_SEE_OTHER; + ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, dest->uri); + return; + } + else if (dtype & (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT)) + { + for (i = 0; i < dest->num_printers; i ++) + if (!(dest->printers[i]->type & + (CUPS_PRINTER_CLASS | CUPS_PRINTER_IMPLICIT))) + { + snprintf(filename, sizeof(filename), "%s/ppd/%s.ppd", ServerRoot, + dest->printers[i]->name); + + if (!access(filename, 0)) + break; + } + + if (i < dest->num_printers) + dest = dest->printers[i]; + else + { + con->response->request.status.status_code = CUPS_SEE_OTHER; + ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_URI, + "printer-uri", NULL, dest->printers[0]->uri); + return; + } + } + + /* + * Found the printer with the PPD file, now see if there is one... + */ + + if ((con->file = open(filename, O_RDONLY)) < 0) + { + send_ipp_status(con, IPP_NOT_FOUND, + _("The PPD file \"%s\" could not be opened: %s"), + uri->values[0].string.text, strerror(errno)); + return; + } + + fcntl(con->file, F_SETFD, fcntl(con->file, F_GETFD) | FD_CLOEXEC); + + con->pipe_pid = 0; + + con->response->request.status.status_code = IPP_OK; + } + else + send_ipp_status(con, IPP_NOT_FOUND, + _("The PPD file \"%s\" could not be found."), + uri->values[0].string.text); } @@ -5402,15 +7340,38 @@ static void get_ppds(cupsd_client_t *con) /* I - Client connection */ { http_status_t status; /* Policy status */ - int i; /* Looping var */ ipp_attribute_t *limit, /* Limit attribute */ + *device, /* ppd-device-id attribute */ + *language, /* ppd-natural-language attribute */ *make, /* ppd-make attribute */ - *requested; /* requested-attributes attribute */ - char command[1024], /* cups-deviced command */ - options[1024], /* Options to pass to command */ - attrs[1024], /* String for requested attributes */ - *aptr; /* Pointer into string */ - int alen; /* Length of attribute value */ + *model, /* ppd-make-and-model attribute */ + *model_number, /* ppd-model-number attribute */ + *product, /* ppd-product attribute */ + *psversion, /* ppd-psverion attribute */ + *type, /* ppd-type attribute */ + *requested, /* requested-attributes attribute */ + *exclude, /* exclude-schemes attribute */ + *include; /* include-schemes attribute */ + char command[1024], /* cups-driverd command */ + options[4096], /* Options to pass to command */ + device_str[256],/* Escaped ppd-device-id string */ + language_str[256], + /* Escaped ppd-natural-language */ + make_str[256], /* Escaped ppd-make string */ + model_str[256], /* Escaped ppd-make-and-model string */ + model_number_str[256], + /* ppd-model-number string */ + product_str[256], + /* Escaped ppd-product string */ + psversion_str[256], + /* Escaped ppd-psversion string */ + type_str[256], /* Escaped ppd-type string */ + requested_str[256], + /* String for requested attributes */ + exclude_str[512], + /* String for excluded schemes */ + include_str[512]; + /* String for included schemes */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_ppds(%p[%d])", con, con->http.fd); @@ -5421,7 +7382,7 @@ get_ppds(cupsd_client_t *con) /* I - Client connection */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -5429,56 +7390,97 @@ get_ppds(cupsd_client_t *con) /* I - Client connection */ * Run cups-driverd command with the given options... */ - limit = ippFindAttribute(con->request, "limit", IPP_TAG_INTEGER); - make = ippFindAttribute(con->request, "ppd-make", IPP_TAG_TEXT); - requested = ippFindAttribute(con->request, "requested-attributes", - IPP_TAG_KEYWORD); + limit = ippFindAttribute(con->request, "limit", IPP_TAG_INTEGER); + device = ippFindAttribute(con->request, "ppd-device-id", IPP_TAG_TEXT); + language = ippFindAttribute(con->request, "ppd-natural-language", + IPP_TAG_LANGUAGE); + make = ippFindAttribute(con->request, "ppd-make", IPP_TAG_TEXT); + model = ippFindAttribute(con->request, "ppd-make-and-model", + IPP_TAG_TEXT); + model_number = ippFindAttribute(con->request, "ppd-model-number", + IPP_TAG_INTEGER); + product = ippFindAttribute(con->request, "ppd-product", IPP_TAG_TEXT); + psversion = ippFindAttribute(con->request, "ppd-psversion", IPP_TAG_TEXT); + type = ippFindAttribute(con->request, "ppd-type", IPP_TAG_KEYWORD); + requested = ippFindAttribute(con->request, "requested-attributes", + IPP_TAG_KEYWORD); + exclude = ippFindAttribute(con->request, "exclude-schemes", + IPP_TAG_NAME); + include = ippFindAttribute(con->request, "include-schemes", + IPP_TAG_NAME); if (requested) - { - for (i = 0, aptr = attrs; i < requested->num_values; i ++) - { - /* - * Check that we have enough room... - */ + url_encode_attr(requested, requested_str, sizeof(requested_str)); + else + strlcpy(requested_str, "requested-attributes=all", sizeof(requested_str)); - alen = strlen(requested->values[i].string.text); - if (alen > (sizeof(attrs) - (aptr - attrs) - 2)) - break; + if (device) + url_encode_attr(device, device_str, sizeof(device_str)); + else + device_str[0] = '\0'; - /* - * Put commas between values... - */ + if (language) + url_encode_attr(language, language_str, sizeof(language_str)); + else + language_str[0] = '\0'; - if (i) - *aptr++ = ','; + if (make) + url_encode_attr(make, make_str, sizeof(make_str)); + else + make_str[0] = '\0'; - /* - * Add the value to the end of the string... - */ + if (model) + url_encode_attr(model, model_str, sizeof(model_str)); + else + model_str[0] = '\0'; - strcpy(aptr, requested->values[i].string.text); - aptr += alen; - } + if (model_number) + snprintf(model_number_str, sizeof(model_number_str), "ppd-model-number=%d", + model_number->values[0].integer); + else + model_number_str[0] = '\0'; - /* - * If we have more attribute names than will fit, default to "all"... - */ + if (product) + url_encode_attr(product, product_str, sizeof(product_str)); + else + product_str[0] = '\0'; - if (i < requested->num_values) - strcpy(attrs, "all"); - } + if (psversion) + url_encode_attr(psversion, psversion_str, sizeof(psversion_str)); else - strcpy(attrs, "all"); + psversion_str[0] = '\0'; + + if (type) + url_encode_attr(type, type_str, sizeof(type_str)); + else + type_str[0] = '\0'; + + if (exclude) + url_encode_attr(exclude, exclude_str, sizeof(exclude_str)); + else + exclude_str[0] = '\0'; + + if (include) + url_encode_attr(include, include_str, sizeof(include_str)); + else + include_str[0] = '\0'; snprintf(command, sizeof(command), "%s/daemon/cups-driverd", ServerBin); snprintf(options, sizeof(options), - "cups-driverd list+%d+%d+requested-attributes=%s%s%s", + "list+%d+%d+%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", con->request->request.op.request_id, limit ? limit->values[0].integer : 0, - attrs, - make ? "%20ppd-make=" : "", - make ? make->values[0].string.text : ""); + requested_str, + device ? "%20" : "", device_str, + language ? "%20" : "", language_str, + make ? "%20" : "", make_str, + model ? "%20" : "", model_str, + model_number ? "%20" : "", model_number_str, + product ? "%20" : "", product_str, + psversion ? "%20" : "", psversion_str, + type ? "%20" : "", type_str, + exclude_str[0] ? "%20" : "", exclude_str, + include_str[0] ? "%20" : "", include_str); if (cupsdSendCommand(con, command, options, 0)) { @@ -5511,33 +7513,75 @@ get_printer_attrs(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer URI */ { http_status_t status; /* Policy status */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ - username[HTTP_MAX_URI], - /* Username portion of URI */ - host[HTTP_MAX_URI], - /* Host portion of URI */ - resource[HTTP_MAX_URI]; - /* Resource portion of URI */ - int port; /* Port portion of URI */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + cupsd_printer_t *printer; /* Printer/class */ + cups_array_t *ra; /* Requested attributes array */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_printer_attrs(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); + + /* + * Is the destination valid? + */ + + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) + { + /* + * Bad URI... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return; + } + + /* + * Send the attributes... + */ + + ra = create_requested_array(con->request); + + copy_printer_attrs(con, printer, ra); + + cupsArrayDelete(ra); + + con->response->request.status.status_code = IPP_OK; +} + + +/* + * 'get_printer_supported()' - Get printer supported values. + */ + +static void +get_printer_supported( + cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Printer URI */ +{ + http_status_t status; /* Policy status */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cupsd_printer_t *printer; /* Printer/class */ - cups_array_t *ra; /* Requested attributes array */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_printer_attrs(%p[%d], %s)", con, + cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_printer_supported(%p[%d], %s)", con, con->http.fd, uri->values[0].string.text); /* * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -5554,19 +7598,18 @@ get_printer_attrs(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } /* - * Send the attributes... + * Return a list of attributes that can be set via Set-Printer-Attributes. */ - ra = create_requested_array(con->request); - - copy_printer_attrs(con, printer, ra); - - cupsArrayDelete(ra); + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ADMINDEFINE, + "printer-info", 0); + ippAddInteger(con->response, IPP_TAG_PRINTER, IPP_TAG_ADMINDEFINE, + "printer-location", 0); con->response->request.status.status_code = IPP_OK; } @@ -5582,7 +7625,7 @@ get_printers(cupsd_client_t *con, /* I - Client connection */ { http_status_t status; /* Policy status */ ipp_attribute_t *attr; /* Current attribute */ - int limit; /* Maximum number of printers to return */ + int limit; /* Max number of printers to return */ int count; /* Number of printers that match */ cupsd_printer_t *printer; /* Current printer pointer */ int printer_type, /* printer-type attribute */ @@ -5591,6 +7634,7 @@ get_printers(cupsd_client_t *con, /* I - Client connection */ const char *username; /* Current user */ char *first_printer_name; /* first-printer-name attribute */ cups_array_t *ra; /* Requested attributes array */ + int local; /* Local connection? */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "get_printers(%p[%d], %x)", con, @@ -5602,7 +7646,7 @@ get_printers(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -5648,6 +7692,8 @@ get_printers(cupsd_client_t *con, /* I - Client connection */ else printer_mask = 0; + local = httpAddrLocalhost(&(con->clientaddr)); + if ((attr = ippFindAttribute(con->request, "printer-location", IPP_TAG_TEXT)) != NULL) location = attr->values[0].string.text; @@ -5680,10 +7726,13 @@ get_printers(cupsd_client_t *con, /* I - Client connection */ count < limit && printer; printer = (cupsd_printer_t *)cupsArrayNext(Printers)) { + if (!local && !printer->shared) + continue; + if ((!type || (printer->type & CUPS_PRINTER_CLASS) == type) && (printer->type & printer_mask) == printer_type && - (!location || !printer->location || - !strcasecmp(printer->location, location))) + (!location || + (printer->location && !strcasecmp(printer->location, location)))) { /* * If HideImplicitMembers is enabled, see if this printer or class @@ -5699,7 +7748,8 @@ get_printers(cupsd_client_t *con, /* I - Client connection */ * access... */ - if (printer->num_users && username && !user_allowed(printer, username)) + if (!(printer->type & CUPS_PRINTER_AUTHENTICATED) && + printer->num_users && username && !user_allowed(printer, username)) continue; /* @@ -5766,7 +7816,7 @@ get_subscription_attrs( DefaultPolicyPtr, con, sub->owner)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, sub->dest); return; } @@ -5799,9 +7849,9 @@ get_subscriptions(cupsd_client_t *con, /* I - Client connection */ cupsd_subscription_t *sub; /* Subscription */ cups_array_t *ra; /* Requested attributes array */ ipp_attribute_t *attr; /* Attribute */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + char scheme[HTTP_MAX_URI], + /* Scheme portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], @@ -5821,8 +7871,8 @@ get_subscriptions(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); if (!strcmp(resource, "/") || @@ -5845,7 +7895,7 @@ get_subscriptions(cupsd_client_t *con, /* I - Client connection */ return; } } - else if (!cupsdValidateDest(host, resource, &dtype, &printer)) + else if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -5878,7 +7928,7 @@ get_subscriptions(cupsd_client_t *con, /* I - Client connection */ DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } @@ -5902,15 +7952,7 @@ get_subscriptions(cupsd_client_t *con, /* I - Client connection */ if ((attr = ippFindAttribute(con->request, "my-subscriptions", IPP_TAG_BOOLEAN)) != NULL && attr->values[0].boolean) - { - if (con->username[0]) - strlcpy(username, con->username, sizeof(username)); - else if ((attr = ippFindAttribute(con->request, "requesting-user-name", - IPP_TAG_NAME)) != NULL) - strlcpy(username, attr->values[0].string.text, sizeof(username)); - else - strcpy(username, "anonymous"); - } + strlcpy(username, get_username(con), sizeof(username)); else username[0] = '\0'; @@ -5938,1219 +7980,783 @@ get_subscriptions(cupsd_client_t *con, /* I - Client connection */ /* - * 'hold_job()' - Hold a print job. - */ - -static void -hold_job(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Job or Printer URI */ -{ - ipp_attribute_t *attr, /* Current job-hold-until */ - *newattr; /* New job-hold-until */ - int jobid; /* Job ID */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - cupsd_job_t *job; /* Job information */ - - - cupsdLogMessage(CUPSD_LOG_DEBUG2, "hold_job(%p[%d], %s)", con, con->http.fd, - uri->values[0].string.text); - - /* - * See if we have a job URI or a printer URI... - */ - - if (!strcmp(uri->name, "printer-uri")) - { - /* - * Got a printer URI; see if we also have a job-id attribute... - */ - - if ((attr = ippFindAttribute(con->request, "job-id", - IPP_TAG_INTEGER)) == NULL) - { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Got a printer-uri attribute but no job-id!")); - return; - } - - jobid = attr->values[0].integer; - } - else - { - /* - * Got a job URI; parse it to get the job ID... - */ - - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if (strncmp(resource, "/jobs/", 6)) - { - /* - * Not a valid URI! - */ - - send_ipp_status(con, IPP_BAD_REQUEST, - _("Bad job-uri attribute \"%s\"!"), - uri->values[0].string.text); - return; - } - - jobid = atoi(resource + 6); - } - - /* - * See if the job exists... - */ - - if ((job = cupsdFindJob(jobid)) == NULL) - { - /* - * Nope - return a "not found" error... - */ - - send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist!"), jobid); - return; - } - - /* - * See if the job is owned by the requesting user... - */ - - if (!validate_user(job, con, job->username, username, sizeof(username))) - { - send_ipp_status(con, IPP_FORBIDDEN, - _("Not authorized to hold job #%d owned by \"%s\"!"), - jobid, job->username); - return; - } - - /* - * Hold the job and return... - */ - - cupsdHoldJob(job); - - if ((newattr = ippFindAttribute(con->request, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - newattr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME); - - if ((attr = ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); - - if (attr) - { - /* - * Free the old hold value and copy the new one over... - */ - - free(attr->values[0].string.text); - - if (newattr) - { - attr->value_tag = newattr->value_tag; - attr->values[0].string.text = strdup(newattr->values[0].string.text); - } - else - { - attr->value_tag = IPP_TAG_KEYWORD; - attr->values[0].string.text = strdup("indefinite"); - } - - /* - * Hold job until specified time... - */ - - cupsdSetJobHoldUntil(job, attr->values[0].string.text); - } - - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d was held by \"%s\".", jobid, username); - - con->response->request.status.status_code = IPP_OK; -} - - -/* - * 'move_job()' - Move a job to a new destination. + * 'get_username()' - Get the username associated with a request. */ -static void -move_job(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Job URI */ -{ - http_status_t status; /* Policy status */ - ipp_attribute_t *attr; /* Current attribute */ - int jobid; /* Job ID */ - cupsd_job_t *job; /* Current job */ - const char *src, /* Source printer/class */ - *dest; /* Destination */ - cups_ptype_t stype, /* Source type (printer or class) */ - dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - cupsd_printer_t *sprinter, /* Source printer */ - *dprinter; /* Destination printer */ - - - cupsdLogMessage(CUPSD_LOG_DEBUG2, "move_job(%p[%d], %s)", con, con->http.fd, - uri->values[0].string.text); - - /* - * Get the new printer or class... - */ - - if ((attr = ippFindAttribute(con->request, "job-printer-uri", - IPP_TAG_URI)) == NULL) - { - /* - * Need job-printer-uri... - */ - - send_ipp_status(con, IPP_BAD_REQUEST, - _("job-printer-uri attribute missing!")); - return; - } - - httpSeparateURI(HTTP_URI_CODING_ALL, attr->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &dprinter)) == NULL) - { - /* - * Bad URI... - */ - - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer or class was not found.")); - return; - } - - /* - * Check policy... - */ - - if ((status = cupsdCheckPolicy(dprinter->op_policy_ptr, con, NULL)) != HTTP_OK) - { - send_http_error(con, status); - return; - } - - /* - * See if we have a job URI or a printer URI... - */ - - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if (!strcmp(uri->name, "printer-uri")) - { - /* - * Got a printer URI; see if we also have a job-id attribute... - */ - - if ((attr = ippFindAttribute(con->request, "job-id", - IPP_TAG_INTEGER)) == NULL) - { - /* - * Move all jobs... - */ - - if ((src = cupsdValidateDest(host, resource, &stype, &sprinter)) == NULL) - { - /* - * Bad URI... - */ - - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer or class was not found.")); - return; - } - - job = NULL; - } - else - { - /* - * Otherwise, just move a single job... - */ - - if ((job = cupsdFindJob(attr->values[0].integer)) == NULL) - { - /* - * Nope - return a "not found" error... - */ - - send_ipp_status(con, IPP_NOT_FOUND, - _("Job #%d does not exist!"), attr->values[0].integer); - return; - } - else - { - /* - * Job found, initialize source pointers... - */ - - src = NULL; - sprinter = NULL; - } - } - } - else - { - /* - * Got a job URI; parse it to get the job ID... - */ +static const char * /* O - Username */ +get_username(cupsd_client_t *con) /* I - Connection */ +{ + ipp_attribute_t *attr; /* Attribute */ - if (strncmp(resource, "/jobs/", 6)) - { - /* - * Not a valid URI! - */ - send_ipp_status(con, IPP_BAD_REQUEST, - _("Bad job-uri attribute \"%s\"!"), - uri->values[0].string.text); - return; - } + if (con->username[0]) + return (con->username); + else if ((attr = ippFindAttribute(con->request, "requesting-user-name", + IPP_TAG_NAME)) != NULL) + return (attr->values[0].string.text); + else + return ("anonymous"); +} - /* - * See if the job exists... - */ - jobid = atoi(resource + 6); +/* + * 'hold_job()' - Hold a print job. + */ - if ((job = cupsdFindJob(jobid)) == NULL) - { - /* - * Nope - return a "not found" error... - */ +static void +hold_job(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Job or Printer URI */ +{ + ipp_attribute_t *attr; /* Current job-hold-until */ + const char *when; /* New value */ + int jobid; /* Job ID */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ + username[HTTP_MAX_URI], /* Username portion of URI */ + host[HTTP_MAX_URI], /* Host portion of URI */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ + int port; /* Port portion of URI */ + cupsd_job_t *job; /* Job information */ - send_ipp_status(con, IPP_NOT_FOUND, - _("Job #%d does not exist!"), jobid); - return; - } - else - { - /* - * Job found, initialize source pointers... - */ - src = NULL; - sprinter = NULL; - } - } + cupsdLogMessage(CUPSD_LOG_DEBUG2, "hold_job(%p[%d], %s)", con, con->http.fd, + uri->values[0].string.text); /* - * Now move the job or jobs... + * See if we have a job URI or a printer URI... */ - if (job) + if (!strcmp(uri->name, "printer-uri")) { /* - * See if the job has been completed... - */ - - if (job->state->values[0].integer > IPP_JOB_STOPPED) - { - /* - * Return a "not-possible" error... - */ - - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job #%d is finished and cannot be altered!"), - job->id); - return; - } - - /* - * See if the job is owned by the requesting user... + * Got a printer URI; see if we also have a job-id attribute... */ - if (!validate_user(job, con, job->username, username, sizeof(username))) + if ((attr = ippFindAttribute(con->request, "job-id", + IPP_TAG_INTEGER)) == NULL) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to move job #%d owned " - "by \"%s\"!"), - job->id, job->username); + send_ipp_status(con, IPP_BAD_REQUEST, + _("Got a printer-uri attribute but no job-id!")); return; } - /* - * Move the job to a different printer or class... - */ - - cupsdMoveJob(job, dest); + jobid = attr->values[0].integer; } else { /* - * Got the source printer, now look through the jobs... + * Got a job URI; parse it to get the job ID... */ - for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); - job; - job = (cupsd_job_t *)cupsArrayNext(Jobs)) - { - /* - * See if the job is pointing at the source printer or has not been - * completed... - */ - - if (strcasecmp(job->dest, src) || - job->state->values[0].integer > IPP_JOB_STOPPED) - continue; - - /* - * See if the job can be moved by the requesting user... - */ - - if (!validate_user(job, con, job->username, username, sizeof(username))) - continue; + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); + if (strncmp(resource, "/jobs/", 6)) + { /* - * Move the job to a different printer or class... + * Not a valid URI! */ - cupsdMoveJob(job, dest); + send_ipp_status(con, IPP_BAD_REQUEST, + _("Bad job-uri attribute \"%s\"!"), + uri->values[0].string.text); + return; } - } - - /* - * Start jobs if possible... - */ - cupsdCheckJobs(); + jobid = atoi(resource + 6); + } /* - * Return with "everything is OK" status... + * See if the job exists... */ - con->response->request.status.status_code = IPP_OK; -} - - -/* - * 'ppd_add_default()' - Add a PPD default choice. - */ - -static int /* O - Number of defaults */ -ppd_add_default( - const char *option, /* I - Option name */ - const char *choice, /* I - Choice name */ - int num_defaults, /* I - Number of defaults */ - ppd_default_t **defaults) /* IO - Defaults */ -{ - int i; /* Looping var */ - ppd_default_t *temp; /* Temporary defaults array */ + if ((job = cupsdFindJob(jobid)) == NULL) + { + /* + * Nope - return a "not found" error... + */ + send_ipp_status(con, IPP_NOT_FOUND, _("Job #%d does not exist!"), jobid); + return; + } /* - * First check if the option already has a default value; the PPD spec - * says that the first one is used... + * See if the job is owned by the requesting user... */ - for (i = 0, temp = *defaults; i < num_defaults; i ++) - if (!strcmp(option, temp[i].option)) - return (num_defaults); + if (!validate_user(job, con, job->username, username, sizeof(username))) + { + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); + return; + } /* - * Now add the option... + * Hold the job and return... */ - if (num_defaults == 0) - temp = malloc(sizeof(ppd_default_t)); - else - temp = realloc(*defaults, (num_defaults + 1) * sizeof(ppd_default_t)); + if ((attr = ippFindAttribute(con->request, "job-hold-until", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME); - if (!temp) + if (attr) { - cupsdLogMessage(CUPSD_LOG_ERROR, "ppd_add_default: Unable to add default value for \"%s\" - %s", - option, strerror(errno)); - return (num_defaults); - } + when = attr->values[0].string.text; - *defaults = temp; - temp += num_defaults; + cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, cupsdFindDest(job->dest), job, + "Job job-hold-until value changed by user."); + } + else + when = "indefinite"; - strlcpy(temp->option, option, sizeof(temp->option)); - strlcpy(temp->choice, choice, sizeof(temp->choice)); + cupsdSetJobHoldUntil(job, when, 1); + cupsdSetJobState(job, IPP_JOB_HELD, CUPSD_JOB_DEFAULT, "Job held by \"%s\".", + username); - return (num_defaults + 1); + con->response->request.status.status_code = IPP_OK; } /* - * 'ppd_parse_line()' - Parse a PPD default line. + * 'hold_new_jobs()' - Hold pending/new jobs on a printer or class. */ -static int /* O - 0 on success, -1 on failure */ -ppd_parse_line(const char *line, /* I - Line */ - char *option, /* O - Option name */ - int olen, /* I - Size of option name */ - char *choice, /* O - Choice name */ - int clen) /* I - Size of choice name */ +static void +hold_new_jobs(cupsd_client_t *con, /* I - Connection */ + ipp_attribute_t *uri) /* I - Printer URI */ { - /* - * Verify this is a default option line... - */ + http_status_t status; /* Policy status */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + cupsd_printer_t *printer; /* Printer data */ - if (strncmp(line, "*Default", 8)) - return (-1); + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "hold_new_jobs(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); /* - * Read the option name... + * Is the destination valid? */ - for (line += 8, olen --; isalnum(*line & 255); line ++) - if (olen > 0) - { - *option++ = *line; - olen --; - } + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) + { + /* + * Bad URI... + */ - *option = '\0'; + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } /* - * Skip everything else up to the colon (:)... + * Check policy... */ - while (*line && *line != ':') - line ++; - - if (!*line) - return (-1); - - line ++; + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return; + } /* - * Now grab the option choice, skipping leading whitespace... + * Hold pending/new jobs sent to the printer... */ - while (isspace(*line & 255)) - line ++; + printer->holding_new_jobs = 1; - for (clen --; isalnum(*line & 255); line ++) - if (clen > 0) - { - *choice++ = *line; - clen --; - } + cupsdSetPrinterReasons(printer, "+hold-new-jobs"); + cupsdAddPrinterHistory(printer); - *choice = '\0'; + if (dtype & CUPS_PRINTER_CLASS) + cupsdLogMessage(CUPSD_LOG_INFO, + "Class \"%s\" now holding pending/new jobs (\"%s\").", + printer->name, get_username(con)); + else + cupsdLogMessage(CUPSD_LOG_INFO, + "Printer \"%s\" now holding pending/new jobs (\"%s\").", + printer->name, get_username(con)); /* - * Return with no errors... + * Everything was ok, so return OK status... */ - return (0); + con->response->request.status.status_code = IPP_OK; } /* - * 'print_job()' - Print a file to a printer or class. + * 'move_job()' - Move a job to a new destination. */ static void -print_job(cupsd_client_t *con, /* I - Client connection */ - ipp_attribute_t *uri) /* I - Printer URI */ +move_job(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Job URI */ { http_status_t status; /* Policy status */ ipp_attribute_t *attr; /* Current attribute */ - ipp_attribute_t *format; /* Document-format attribute */ - const char *dest; /* Destination */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - int priority; /* Job priority */ - char *title; /* Job name/title */ + int jobid; /* Job ID */ cupsd_job_t *job; /* Current job */ - char job_uri[HTTP_MAX_URI], /* Job URI */ - method[HTTP_MAX_URI], /* Method portion of URI */ + const char *src; /* Source printer/class */ + cups_ptype_t stype, /* Source type (printer or class) */ + dtype; /* Destination type (printer/class) */ + char scheme[HTTP_MAX_URI], /* Scheme portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI], /* Resource portion of URI */ - filename[1024]; /* Job filename */ + resource[HTTP_MAX_URI]; /* Resource portion of URI */ int port; /* Port portion of URI */ - mime_type_t *filetype; /* Type of file */ - char super[MIME_MAX_SUPER], /* Supertype of file */ - type[MIME_MAX_TYPE], /* Subtype of file */ - mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2]; - /* Textual name of mime type */ - cupsd_printer_t *printer; /* Printer data */ - struct stat fileinfo; /* File information */ - int kbytes; /* Size of file */ - int i; /* Looping var */ - int lowerpagerange; /* Page range bound */ - int compression; /* Document compression */ + cupsd_printer_t *sprinter, /* Source printer */ + *dprinter; /* Destination printer */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "move_job(%p[%d], %s)", con, con->http.fd, + uri->values[0].string.text); + + /* + * Get the new printer or class... + */ + + if ((attr = ippFindAttribute(con->request, "job-printer-uri", + IPP_TAG_URI)) == NULL) + { + /* + * Need job-printer-uri... + */ + + send_ipp_status(con, IPP_BAD_REQUEST, + _("job-printer-uri attribute missing!")); + return; + } + if (!cupsdValidateDest(attr->values[0].string.text, &dtype, &dprinter)) + { + /* + * Bad URI... + */ - cupsdLogMessage(CUPSD_LOG_DEBUG2, "print_job(%p[%d], %s)", con, con->http.fd, - uri->values[0].string.text); + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } /* - * Validate job template attributes; for now just copies and page-ranges... + * See if we have a job URI or a printer URI... */ - if ((attr = ippFindAttribute(con->request, "copies", - IPP_TAG_INTEGER)) != NULL) + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, + sizeof(host), &port, resource, sizeof(resource)); + + if (!strcmp(uri->name, "printer-uri")) { - if (attr->values[0].integer < 1 || attr->values[0].integer > MaxCopies) + /* + * Got a printer URI; see if we also have a job-id attribute... + */ + + if ((attr = ippFindAttribute(con->request, "job-id", + IPP_TAG_INTEGER)) == NULL) { - send_ipp_status(con, IPP_ATTRIBUTES, _("Bad copies value %d."), - attr->values[0].integer); - ippAddInteger(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_INTEGER, - "copies", attr->values[0].integer); - return; - } - } + /* + * Move all jobs... + */ - if ((attr = ippFindAttribute(con->request, "page-ranges", - IPP_TAG_RANGE)) != NULL) - { - for (i = 0, lowerpagerange = 1; i < attr->num_values; i ++) + if ((src = cupsdValidateDest(uri->values[0].string.text, &stype, + &sprinter)) == NULL) + { + /* + * Bad URI... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } + + job = NULL; + } + else { - if (attr->values[i].range.lower < lowerpagerange || - attr->values[i].range.lower > attr->values[i].range.upper) + /* + * Otherwise, just move a single job... + */ + + if ((job = cupsdFindJob(attr->values[0].integer)) == NULL) { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Bad page-ranges values %d-%d."), - attr->values[i].range.lower, - attr->values[i].range.upper); + /* + * Nope - return a "not found" error... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("Job #%d does not exist!"), attr->values[0].integer); return; } + else + { + /* + * Job found, initialize source pointers... + */ - lowerpagerange = attr->values[i].range.upper + 1; + src = NULL; + sprinter = NULL; + } } } + else + { + /* + * Got a job URI; parse it to get the job ID... + */ - /* - * OK, see if the client is sending the document compressed - CUPS - * only supports "none" and "gzip". - */ + if (strncmp(resource, "/jobs/", 6)) + { + /* + * Not a valid URI! + */ - compression = CUPS_FILE_NONE; + send_ipp_status(con, IPP_BAD_REQUEST, + _("Bad job-uri attribute \"%s\"!"), + uri->values[0].string.text); + return; + } - if ((attr = ippFindAttribute(con->request, "compression", - IPP_TAG_KEYWORD)) != NULL) - { - if (strcmp(attr->values[0].string.text, "none") -#ifdef HAVE_LIBZ - && strcmp(attr->values[0].string.text, "gzip") -#endif /* HAVE_LIBZ */ - ) + /* + * See if the job exists... + */ + + jobid = atoi(resource + 6); + + if ((job = cupsdFindJob(jobid)) == NULL) { - send_ipp_status(con, IPP_ATTRIBUTES, - _("Unsupported compression \"%s\"!"), - attr->values[0].string.text); - ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, - "compression", NULL, attr->values[0].string.text); + /* + * Nope - return a "not found" error... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("Job #%d does not exist!"), jobid); return; } + else + { + /* + * Job found, initialize source pointers... + */ -#ifdef HAVE_LIBZ - if (!strcmp(attr->values[0].string.text, "gzip")) - compression = CUPS_FILE_GZIP; -#endif /* HAVE_LIBZ */ + src = NULL; + sprinter = NULL; + } } /* - * Do we have a file to print? + * Check the policy of the destination printer... */ - if (!con->filename) + if ((status = cupsdCheckPolicy(dprinter->op_policy_ptr, con, + job ? job->username : NULL)) != HTTP_OK) { - send_ipp_status(con, IPP_BAD_REQUEST, _("No file!?!")); + send_http_error(con, status, dprinter); return; } /* - * Is it a format we support? + * Now move the job or jobs... */ - if ((format = ippFindAttribute(con->request, "document-format", - IPP_TAG_MIMETYPE)) != NULL) + if (job) { /* - * Grab format from client... + * See if the job has been completed... */ - if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2) + if (job->state_value > IPP_JOB_STOPPED) { - send_ipp_status(con, IPP_BAD_REQUEST, - _("Could not scan type \"%s\"!"), - format->values[0].string.text); + /* + * Return a "not-possible" error... + */ + + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job #%d is finished and cannot be altered!"), + job->id); return; } - } - else - { + /* - * No document format attribute? Auto-type it! + * See if the job is owned by the requesting user... */ - strcpy(super, "application"); - strcpy(type, "octet-stream"); - } + if (!validate_user(job, con, job->username, username, sizeof(username))) + { + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); + return; + } - if (!strcmp(super, "application") && !strcmp(type, "octet-stream")) - { /* - * Auto-type the file... + * Move the job to a different printer or class... */ - cupsdLogMessage(CUPSD_LOG_DEBUG, "print_job: auto-typing file..."); - - filetype = mimeFileType(MimeDatabase, con->filename, &compression); + cupsdMoveJob(job, dprinter); + } + else + { + /* + * Got the source printer, now look through the jobs... + */ - if (filetype) + for (job = (cupsd_job_t *)cupsArrayFirst(Jobs); + job; + job = (cupsd_job_t *)cupsArrayNext(Jobs)) { /* - * Replace the document-format attribute value with the auto-typed one. + * See if the job is pointing at the source printer or has not been + * completed... */ - snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super, - filetype->type); + if (strcasecmp(job->dest, src) || + job->state_value > IPP_JOB_STOPPED) + continue; - if (format) - { - free(format->values[0].string.text); - format->values[0].string.text = strdup(mimetype); - } - else - ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, - "document-format", NULL, mimetype); - } - else - filetype = mimeType(MimeDatabase, super, type); - } - else - filetype = mimeType(MimeDatabase, super, type); + /* + * See if the job can be moved by the requesting user... + */ - if (!filetype) - { - send_ipp_status(con, IPP_DOCUMENT_FORMAT, - _("Unsupported format \'%s/%s\'!"), super, type); - cupsdLogMessage(CUPSD_LOG_INFO, - "Hint: Do you have the raw file printing rules enabled?"); + if (!validate_user(job, con, job->username, username, sizeof(username))) + continue; - if (format) - ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE, - "document-format", NULL, format->values[0].string.text); + /* + * Move the job to a different printer or class... + */ - return; + cupsdMoveJob(job, dprinter); + } } - cupsdLogMessage(CUPSD_LOG_DEBUG, "print_job: request file type is %s/%s.", - filetype->super, filetype->type); - /* - * Read any embedded job ticket info from PS files... + * Start jobs if possible... */ - if (!strcasecmp(filetype->super, "application") && - !strcasecmp(filetype->type, "postscript")) - read_ps_job_ticket(con); + cupsdCheckJobs(); /* - * Is the destination valid? + * Return with "everything is OK" status... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((dest = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) - { - /* - * Bad URI... - */ - - send_ipp_status(con, IPP_NOT_FOUND, - _("The printer or class was not found.")); - return; - } + con->response->request.status.status_code = IPP_OK; +} - /* - * Check remote printing to non-shared printer... - */ - if (!printer->shared && - strcasecmp(con->http.hostname, "localhost") && - strcasecmp(con->http.hostname, ServerName)) - { - send_ipp_status(con, IPP_NOT_AUTHORIZED, _("Printer not shared!")); - return; - } +/* + * 'ppd_parse_line()' - Parse a PPD default line. + */ +static int /* O - 0 on success, -1 on failure */ +ppd_parse_line(const char *line, /* I - Line */ + char *option, /* O - Option name */ + int olen, /* I - Size of option name */ + char *choice, /* O - Choice name */ + int clen) /* I - Size of choice name */ +{ /* - * Check policy... + * Verify this is a default option line... */ - if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) - { - send_http_error(con, status); - return; - } - else if ((printer->type & CUPS_PRINTER_AUTHENTICATED) && !con->username[0]) - { - send_http_error(con, status); - return; - } + if (strncmp(line, "*Default", 8)) + return (-1); /* - * See if the printer is accepting jobs... + * Read the option name... */ - if (!printer->accepting) - { - send_ipp_status(con, IPP_NOT_ACCEPTING, - _("Destination \"%s\" is not accepting jobs."), dest); - return; - } + for (line += 8, olen --; isalnum(*line & 255); line ++) + if (olen > 0) + { + *option++ = *line; + olen --; + } + + *option = '\0'; /* - * Make sure we aren't over our limit... + * Skip everything else up to the colon (:)... */ - if (cupsArrayCount(Jobs) >= MaxJobs && MaxJobs) - cupsdCleanJobs(); + while (*line && *line != ':') + line ++; - if (cupsArrayCount(Jobs) >= MaxJobs && MaxJobs) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Too many jobs - %d jobs, max jobs is %d."), - cupsArrayCount(Jobs), MaxJobs); - return; - } + if (!*line) + return (-1); - if (!check_quotas(con, printer)) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, _("Quota limit reached.")); - return; - } + line ++; /* - * Create the job and set things up... + * Now grab the option choice, skipping leading whitespace... */ - if ((attr = ippFindAttribute(con->request, "job-priority", - IPP_TAG_INTEGER)) != NULL) - priority = attr->values[0].integer; - else - ippAddInteger(con->request, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-priority", - priority = 50); - - if ((attr = ippFindAttribute(con->request, "job-name", - IPP_TAG_NAME)) != NULL) - title = attr->values[0].string.text; - else - ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, - title = "Untitled"); + while (isspace(*line & 255)) + line ++; - if ((job = cupsdAddJob(priority, printer->name)) == NULL) - { - send_ipp_status(con, IPP_INTERNAL_ERROR, - _("Unable to add job for destination \"%s\"!"), dest); - return; - } + for (clen --; isalnum(*line & 255); line ++) + if (clen > 0) + { + *choice++ = *line; + clen --; + } - job->dtype = dtype; - job->attrs = con->request; - con->request = NULL; + *choice = '\0'; /* - * Copy the rest of the job info... + * Return with no errors... */ - attr = ippFindAttribute(job->attrs, "requesting-user-name", IPP_TAG_NAME); + return (0); +} - if (con->username[0]) - { - cupsdSetString(&job->username, con->username); - if (attr) - cupsdSetString(&attr->values[0].string.text, con->username); +/* + * 'print_job()' - Print a file to a printer or class. + */ - save_auth_info(con, job); - } - else if (attr) - { - cupsdLogMessage(CUPSD_LOG_DEBUG, "print_job: requesting-user-name = \"%s\"", - attr->values[0].string.text); +static void +print_job(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Printer URI */ +{ + ipp_attribute_t *attr; /* Current attribute */ + ipp_attribute_t *format; /* Document-format attribute */ + const char *default_format; /* document-format-default value */ + cupsd_job_t *job; /* New job */ + char filename[1024]; /* Job filename */ + mime_type_t *filetype; /* Type of file */ + char super[MIME_MAX_SUPER], /* Supertype of file */ + type[MIME_MAX_TYPE], /* Subtype of file */ + mimetype[MIME_MAX_SUPER + MIME_MAX_TYPE + 2]; + /* Textual name of mime type */ + cupsd_printer_t *printer; /* Printer data */ + struct stat fileinfo; /* File information */ + int kbytes; /* Size of file */ + int compression; /* Document compression */ - cupsdSetString(&job->username, attr->values[0].string.text); - } - else - cupsdSetString(&job->username, "anonymous"); - if (!attr) - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, - "job-originating-user-name", NULL, job->username); - else - { - attr->group_tag = IPP_TAG_JOB; - cupsdSetString(&attr->name, "job-originating-user-name"); - } + cupsdLogMessage(CUPSD_LOG_DEBUG2, "print_job(%p[%d], %s)", con, con->http.fd, + uri->values[0].string.text); /* - * Add remaining job attributes... + * Validate print file attributes, for now just document-format and + * compression (CUPS only supports "none" and "gzip")... */ - if ((attr = ippFindAttribute(job->attrs, "job-originating-host-name", - IPP_TAG_ZERO)) != NULL) - { - /* - * Request contains a job-originating-host-name attribute; validate it... - */ + compression = CUPS_FILE_NONE; - if (attr->value_tag != IPP_TAG_NAME || - attr->num_values != 1 || - strcmp(con->http.hostname, "localhost")) + if ((attr = ippFindAttribute(con->request, "compression", + IPP_TAG_KEYWORD)) != NULL) + { + if (strcmp(attr->values[0].string.text, "none") +#ifdef HAVE_LIBZ + && strcmp(attr->values[0].string.text, "gzip") +#endif /* HAVE_LIBZ */ + ) { - /* - * Can't override the value if we aren't connected via localhost. - * Also, we can only have 1 value and it must be a name value. - */ - - switch (attr->value_tag) - { - case IPP_TAG_STRING : - case IPP_TAG_TEXTLANG : - case IPP_TAG_NAMELANG : - case IPP_TAG_TEXT : - case IPP_TAG_NAME : - case IPP_TAG_KEYWORD : - case IPP_TAG_URI : - case IPP_TAG_URISCHEME : - case IPP_TAG_CHARSET : - case IPP_TAG_LANGUAGE : - case IPP_TAG_MIMETYPE : - /* - * Free old strings... - */ - - for (i = 0; i < attr->num_values; i ++) - { - free(attr->values[i].string.text); - attr->values[i].string.text = NULL; - if (attr->values[i].string.charset) - { - free(attr->values[i].string.charset); - attr->values[i].string.charset = NULL; - } - } - - default : - break; - } - - /* - * Use the default connection hostname instead... - */ - - attr->value_tag = IPP_TAG_NAME; - attr->num_values = 1; - attr->values[0].string.text = strdup(con->http.hostname); + send_ipp_status(con, IPP_ATTRIBUTES, + _("Unsupported compression \"%s\"!"), + attr->values[0].string.text); + ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_KEYWORD, + "compression", NULL, attr->values[0].string.text); + return; } - attr->group_tag = IPP_TAG_JOB; - } - else - { - /* - * No job-originating-host-name attribute, so use the hostname from - * the connection... - */ - - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, - "job-originating-host-name", NULL, con->http.hostname); +#ifdef HAVE_LIBZ + if (!strcmp(attr->values[0].string.text, "gzip")) + compression = CUPS_FILE_GZIP; +#endif /* HAVE_LIBZ */ } - ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); - job->state = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_ENUM, - "job-state", IPP_JOB_PENDING); - job->sheets = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "job-media-sheets-completed", 0); - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_URI, "job-printer-uri", NULL, - printer->uri); - ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-name", NULL, - title); - - if ((attr = ippFindAttribute(job->attrs, "job-k-octets", - IPP_TAG_INTEGER)) == NULL) - attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "job-k-octets", 0); - - if (stat(con->filename, &fileinfo)) - kbytes = 0; - else - kbytes = (fileinfo.st_size + 1023) / 1024; - - cupsdUpdateQuota(printer, job->username, 0, kbytes); - attr->values[0].integer += kbytes; + /* + * Do we have a file to print? + */ - ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, "time-at-creation", - time(NULL)); - attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "time-at-processing", 0); - attr->value_tag = IPP_TAG_NOVALUE; - attr = ippAddInteger(job->attrs, IPP_TAG_JOB, IPP_TAG_INTEGER, - "time-at-completed", 0); - attr->value_tag = IPP_TAG_NOVALUE; + if (!con->filename) + { + send_ipp_status(con, IPP_BAD_REQUEST, _("No file!?!")); + return; + } - if ((attr = ippFindAttribute(job->attrs, "job-hold-until", - IPP_TAG_KEYWORD)) == NULL) - attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); - if (!attr) - attr = ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_KEYWORD, - "job-hold-until", NULL, "no-hold"); + /* + * Is the destination valid? + */ - if (attr && strcmp(attr->values[0].string.text, "no-hold") && - !(printer->type & CUPS_PRINTER_REMOTE)) + if (!cupsdValidateDest(uri->values[0].string.text, NULL, &printer)) { /* - * Hold job until specified time... + * Bad URI... */ - job->state->values[0].integer = IPP_JOB_HELD; - cupsdSetJobHoldUntil(job, attr->values[0].string.text); + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; } - if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) || - Classification) + /* + * Is it a format we support? + */ + + if ((format = ippFindAttribute(con->request, "document-format", + IPP_TAG_MIMETYPE)) != NULL) { /* - * Add job sheets options... + * Grab format from client... */ - if ((attr = ippFindAttribute(job->attrs, "job-sheets", - IPP_TAG_ZERO)) == NULL) + if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, + type) != 2) { - cupsdLogMessage(CUPSD_LOG_DEBUG, - "Adding default job-sheets values \"%s,%s\"...", - printer->job_sheets[0], printer->job_sheets[1]); - - attr = ippAddStrings(job->attrs, IPP_TAG_JOB, IPP_TAG_NAME, "job-sheets", - 2, NULL, NULL); - attr->values[0].string.text = strdup(printer->job_sheets[0]); - attr->values[1].string.text = strdup(printer->job_sheets[1]); + send_ipp_status(con, IPP_BAD_REQUEST, + _("Could not scan type \"%s\"!"), + format->values[0].string.text); + return; } - - job->job_sheets = attr; - + } + else if ((default_format = cupsGetOption("document-format", + printer->num_options, + printer->options)) != NULL) + { /* - * Enforce classification level if set... + * Use default document format... */ - if (Classification) + if (sscanf(default_format, "%15[^/]/%31[^;]", super, type) != 2) { - cupsdLogMessage(CUPSD_LOG_INFO, - "Classification=\"%s\", ClassifyOverride=%d", - Classification ? Classification : "(null)", - ClassifyOverride); + send_ipp_status(con, IPP_BAD_REQUEST, + _("Could not scan type \"%s\"!"), + default_format); + return; + } + } + else + { + /* + * Auto-type it! + */ - if (ClassifyOverride) - { - if (!strcmp(attr->values[0].string.text, "none") && - (attr->num_values == 1 || - !strcmp(attr->values[1].string.text, "none"))) - { - /* - * Force the leading banner to have the classification on it... - */ + strcpy(super, "application"); + strcpy(type, "octet-stream"); + } - cupsdSetString(&attr->values[0].string.text, Classification); + if (!strcmp(super, "application") && !strcmp(type, "octet-stream")) + { + /* + * Auto-type the file... + */ - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s,none\", " - "job-originating-user-name=\"%s\"", - job->id, Classification, job->username); - } - else if (attr->num_values == 2 && - strcmp(attr->values[0].string.text, - attr->values[1].string.text) && - strcmp(attr->values[0].string.text, "none") && - strcmp(attr->values[1].string.text, "none")) - { - /* - * Can't put two different security markings on the same document! - */ + ipp_attribute_t *doc_name; /* document-name attribute */ - cupsdSetString(&attr->values[1].string.text, attr->values[0].string.text); - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s,%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - attr->values[1].string.text, job->username); - } - else if (strcmp(attr->values[0].string.text, Classification) && - strcmp(attr->values[0].string.text, "none") && - (attr->num_values == 1 || - (strcmp(attr->values[1].string.text, Classification) && - strcmp(attr->values[1].string.text, "none")))) - { - if (attr->num_values == 1) - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION OVERRIDDEN " - "job-sheets=\"%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - job->username); - else - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION OVERRIDDEN " - "job-sheets=\"%s,%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - attr->values[1].string.text, job->username); - } - } - else if (strcmp(attr->values[0].string.text, Classification) && - (attr->num_values == 1 || - strcmp(attr->values[1].string.text, Classification))) - { - /* - * Force the banner to have the classification on it... - */ + cupsdLogMessage(CUPSD_LOG_DEBUG, "[Job ???] Auto-typing file..."); - if (attr->num_values > 1 && - !strcmp(attr->values[0].string.text, attr->values[1].string.text)) - { - cupsdSetString(&(attr->values[0].string.text), Classification); - cupsdSetString(&(attr->values[1].string.text), Classification); - } - else - { - if (attr->num_values == 1 || - strcmp(attr->values[0].string.text, "none")) - cupsdSetString(&(attr->values[0].string.text), Classification); + doc_name = ippFindAttribute(con->request, "document-name", IPP_TAG_NAME); + filetype = mimeFileType(MimeDatabase, con->filename, + doc_name ? doc_name->values[0].string.text : NULL, + &compression); - if (attr->num_values > 1 && - strcmp(attr->values[1].string.text, "none")) - cupsdSetString(&(attr->values[1].string.text), Classification); - } + if (!filetype) + filetype = mimeType(MimeDatabase, super, type); - if (attr->num_values > 1) - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s,%s\", " - "job-originating-user-name=\"%s\"", - job->id, attr->values[0].string.text, - attr->values[1].string.text, job->username); - else - cupsdLogMessage(CUPSD_LOG_NOTICE, - "[Job %d] CLASSIFICATION FORCED " - "job-sheets=\"%s\", " - "job-originating-user-name=\"%s\"", - job->id, Classification, job->username); - } - } + cupsdLogMessage(CUPSD_LOG_INFO, "[Job ???] Request file type is %s/%s.", + filetype->super, filetype->type); + } + else + filetype = mimeType(MimeDatabase, super, type); + if (filetype && + (!format || + (!strcmp(super, "application") && !strcmp(type, "octet-stream")))) + { /* - * Add the starting sheet... + * Replace the document-format attribute value with the auto-typed or + * default one. */ - if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT))) - { - cupsdLogMessage(CUPSD_LOG_INFO, - "Adding start banner page \"%s\" to job %d.", - attr->values[0].string.text, job->id); + snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super, + filetype->type); - kbytes = copy_banner(con, job, attr->values[0].string.text); + if (format) + { + _cupsStrFree(format->values[0].string.text); - cupsdUpdateQuota(printer, job->username, 0, kbytes); + format->values[0].string.text = _cupsStrAlloc(mimetype); } + else + ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, + "document-format", NULL, mimetype); } - else if ((attr = ippFindAttribute(job->attrs, "job-sheets", - IPP_TAG_ZERO)) != NULL) - job->sheets = attr; - - /* - * Add the job file... - */ + else if (!filetype) + { + send_ipp_status(con, IPP_DOCUMENT_FORMAT, + _("Unsupported format \'%s/%s\'!"), super, type); + cupsdLogMessage(CUPSD_LOG_INFO, + "Hint: Do you have the raw file printing rules enabled?"); - if (add_file(con, job, filetype, compression)) - return; + if (format) + ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE, + "document-format", NULL, format->values[0].string.text); - snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, - job->num_files); - rename(con->filename, filename); - cupsdClearString(&con->filename); + return; + } /* - * See if we need to add the ending sheet... + * Read any embedded job ticket info from PS files... */ - if (!(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) && - attr->num_values > 1) - { - /* - * Yes... - */ - - cupsdLogMessage(CUPSD_LOG_INFO, "Adding end banner page \"%s\" to job %d.", - attr->values[1].string.text, job->id); - - kbytes = copy_banner(con, job, attr->values[1].string.text); - - cupsdUpdateQuota(printer, job->username, 0, kbytes); - } + if (!strcasecmp(filetype->super, "application") && + (!strcasecmp(filetype->type, "postscript") || + !strcasecmp(filetype->type, "pdf"))) + read_job_ticket(con); /* - * Fill in the response info... + * Create the job object... */ - snprintf(job_uri, sizeof(job_uri), "http://%s:%d/jobs/%d", ServerName, - LocalPort, job->id); + if ((job = add_job(con, printer, filetype)) == NULL) + return; - ippAddString(con->response, IPP_TAG_JOB, IPP_TAG_URI, "job-uri", NULL, - job_uri); + /* + * Update quota data... + */ - ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", job->id); + if (stat(con->filename, &fileinfo)) + kbytes = 0; + else + kbytes = (fileinfo.st_size + 1023) / 1024; - ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", - job->state->values[0].integer); - add_job_state_reasons(con, job); + cupsdUpdateQuota(printer, job->username, 0, kbytes); - con->response->request.status.status_code = IPP_OK; + if ((attr = ippFindAttribute(job->attrs, "job-k-octets", + IPP_TAG_INTEGER)) != NULL) + attr->values[0].integer += kbytes; /* - * Add any job subscriptions... + * Add the job file... */ - add_job_subscriptions(con, job); + if (add_file(con, job, filetype, compression)) + return; + + snprintf(filename, sizeof(filename), "%s/d%05d-%03d", RequestRoot, job->id, + job->num_files); + rename(con->filename, filename); + cupsdClearString(&con->filename); /* - * Set all but the first two attributes to the job attributes group... + * See if we need to add the ending sheet... */ - for (attr = job->attrs->attrs->next->next; attr; attr = attr->next) - attr->group_tag = IPP_TAG_JOB; + if (cupsdTimeoutJob(job)) + return; /* * Log and save the job... */ - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d queued on \"%s\" by \"%s\".", job->id, - job->dest, job->username); - cupsdLogMessage(CUPSD_LOG_DEBUG, "Job %d hold_until = %d", job->id, - (int)job->hold_until); - - cupsdSaveJob(job); - - cupsdAddEvent(CUPSD_EVENT_JOB_CREATED, printer, job, "Job created."); + cupsdLogJob(job, CUPSD_LOG_INFO, + "File of type %s/%s queued by \"%s\".", + filetype->super, filetype->type, job->username); + cupsdLogJob(job, CUPSD_LOG_DEBUG, "hold_until=%d", (int)job->hold_until); + cupsdLogJob(job, CUPSD_LOG_INFO, "Queued on \"%s\" by \"%s\".", + job->dest, job->username); /* * Start the job if possible... @@ -7161,16 +8767,16 @@ print_job(cupsd_client_t *con, /* I - Client connection */ /* - * 'read_ps_job_ticket()' - Reads a job ticket embedded in a PS file. + * 'read_job_ticket()' - Read a job ticket embedded in a print file. * - * This function only gets called when printing a single PostScript + * This function only gets called when printing a single PDF or PostScript * file using the Print-Job operation. It doesn't work for Create-Job + * Send-File, since the job attributes need to be set at job creation - * time for banners to work. The embedded PS job ticket stuff is here - * only to allow the Windows printer driver for CUPS to pass in JCL + * time for banners to work. The embedded job ticket stuff is here + * primarily to allow the Windows printer driver for CUPS to pass in JCL * options and IPP attributes which otherwise would be lost. * - * The format of a PS job ticket is simple: + * The format of a job ticket is simple: * * %cupsJobTicket: attr1=value1 attr2=value2 ... attrN=valueN * @@ -7180,8 +8786,8 @@ print_job(cupsd_client_t *con, /* I - Client connection */ * %cupsJobTicket: attrN=valueN * * Job ticket lines must appear immediately after the first line that - * specifies PostScript format (%!PS-Adobe-3.0), and CUPS will stop - * looking for job ticket info when it finds a line that does not begin + * specifies PostScript (%!PS-Adobe-3.0) or PDF (%PDF) format, and CUPS + * stops looking for job ticket info when it finds a line that does not begin * with "%cupsJobTicket:". * * The maximum length of a job ticket line, including the prefix, is @@ -7194,7 +8800,7 @@ print_job(cupsd_client_t *con, /* I - Client connection */ */ static void -read_ps_job_ticket(cupsd_client_t *con) /* I - Client connection */ +read_job_ticket(cupsd_client_t *con) /* I - Client connection */ { cups_file_t *fp; /* File to read from */ char line[256]; /* Line data */ @@ -7213,8 +8819,7 @@ read_ps_job_ticket(cupsd_client_t *con) /* I - Client connection */ if ((fp = cupsFileOpen(con->filename, "rb")) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, - "read_ps_job_ticket: Unable to open PostScript print file " - "- %s", + "Unable to open print file for job ticket - %s", strerror(errno)); return; } @@ -7226,14 +8831,13 @@ read_ps_job_ticket(cupsd_client_t *con) /* I - Client connection */ if (cupsFileGets(fp, line, sizeof(line)) == NULL) { cupsdLogMessage(CUPSD_LOG_ERROR, - "read_ps_job_ticket: Unable to read from PostScript print " - "file - %s", + "Unable to read from print file for job ticket - %s", strerror(errno)); cupsFileClose(fp); return; } - if (strncmp(line, "%!PS-Adobe-", 11)) + if (strncmp(line, "%!PS-Adobe-", 11) && strncmp(line, "%PDF-", 5)) { /* * Not a DSC-compliant file, so no job ticket info will be available... @@ -7326,7 +8930,7 @@ read_ps_job_ticket(cupsd_client_t *con) /* I - Client connection */ if (con->request->last == attr2) con->request->last = prev2; - _ipp_free_attr(attr2); + _ippFreeAttr(attr2); } /* @@ -7354,13 +8958,7 @@ reject_jobs(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer or class URI */ { http_status_t status; /* Policy status */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], /* Method portion of URI */ - username[HTTP_MAX_URI], /* Username portion of URI */ - host[HTTP_MAX_URI], /* Host portion of URI */ - resource[HTTP_MAX_URI]; /* Resource portion of URI */ - int port; /* Port portion of URI */ - const char *name; /* Printer name */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cupsd_printer_t *printer; /* Printer data */ ipp_attribute_t *attr; /* printer-state-message text */ @@ -7372,11 +8970,7 @@ reject_jobs(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((name = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -7393,7 +8987,7 @@ reject_jobs(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } @@ -7403,29 +8997,97 @@ reject_jobs(cupsd_client_t *con, /* I - Client connection */ printer->accepting = 0; - if ((attr = ippFindAttribute(con->request, "printer-state-message", - IPP_TAG_TEXT)) == NULL) - strcpy(printer->state_message, "Rejecting Jobs"); - else - strlcpy(printer->state_message, attr->values[0].string.text, - sizeof(printer->state_message)); + if ((attr = ippFindAttribute(con->request, "printer-state-message", + IPP_TAG_TEXT)) == NULL) + strcpy(printer->state_message, "Rejecting Jobs"); + else + strlcpy(printer->state_message, attr->values[0].string.text, + sizeof(printer->state_message)); + + cupsdAddPrinterHistory(printer); + + if (dtype & CUPS_PRINTER_CLASS) + { + cupsdMarkDirty(CUPSD_DIRTY_CLASSES); + + cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" rejecting jobs (\"%s\").", + printer->name, get_username(con)); + } + else + { + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); + + cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" rejecting jobs (\"%s\").", + printer->name, get_username(con)); + } + + /* + * Everything was ok, so return OK status... + */ + + con->response->request.status.status_code = IPP_OK; +} + + +/* + * 'release_held_new_jobs()' - Release pending/new jobs on a printer or class. + */ + +static void +release_held_new_jobs( + cupsd_client_t *con, /* I - Connection */ + ipp_attribute_t *uri) /* I - Printer URI */ +{ + http_status_t status; /* Policy status */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + cupsd_printer_t *printer; /* Printer data */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "release_held_new_jobs(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); + + /* + * Is the destination valid? + */ + + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) + { + /* + * Bad URI... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return; + } + + /* + * Hold pending/new jobs sent to the printer... + */ + + printer->holding_new_jobs = 0; + cupsdSetPrinterReasons(printer, "-hold-new-jobs"); cupsdAddPrinterHistory(printer); if (dtype & CUPS_PRINTER_CLASS) - { - cupsdSaveAllClasses(); - - cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" rejecting jobs (\"%s\").", - name, con->username); - } + cupsdLogMessage(CUPSD_LOG_INFO, + "Class \"%s\" now printing pending/new jobs (\"%s\").", + printer->name, get_username(con)); else - { - cupsdSaveAllPrinters(); - - cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" rejecting jobs (\"%s\").", - name, con->username); - } + cupsdLogMessage(CUPSD_LOG_INFO, + "Printer \"%s\" now printing pending/new jobs (\"%s\").", + printer->name, get_username(con)); /* * Everything was ok, so return OK status... @@ -7445,7 +9107,7 @@ release_job(cupsd_client_t *con, /* I - Client connection */ { ipp_attribute_t *attr; /* Current attribute */ int jobid; /* Job ID */ - char method[HTTP_MAX_URI], /* Method portion of URI */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ @@ -7482,8 +9144,8 @@ release_job(cupsd_client_t *con, /* I - Client connection */ * Got a job URI; parse it to get the job ID... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); if (strncmp(resource, "/jobs/", 6)) @@ -7519,7 +9181,7 @@ release_job(cupsd_client_t *con, /* I - Client connection */ * See if job is "held"... */ - if (job->state->values[0].integer != IPP_JOB_HELD) + if (job->state_value != IPP_JOB_HELD) { /* * Nope - return a "not possible" error... @@ -7535,10 +9197,7 @@ release_job(cupsd_client_t *con, /* I - Client connection */ if (!validate_user(job, con, job->username, username, sizeof(username))) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to release job id " - "%d owned by \"%s\"!"), - jobid, job->username); + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); return; } @@ -7552,9 +9211,13 @@ release_job(cupsd_client_t *con, /* I - Client connection */ if (attr) { - free(attr->values[0].string.text); + _cupsStrFree(attr->values[0].string.text); + attr->value_tag = IPP_TAG_KEYWORD; - attr->values[0].string.text = strdup("no-hold"); + attr->values[0].string.text = _cupsStrAlloc("no-hold"); + + cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, cupsdFindDest(job->dest), job, + "Job job-hold-until value changed by user."); } /* @@ -7563,10 +9226,14 @@ release_job(cupsd_client_t *con, /* I - Client connection */ cupsdReleaseJob(job); - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d was released by \"%s\".", jobid, - username); + cupsdAddEvent(CUPSD_EVENT_JOB_STATE, cupsdFindDest(job->dest), job, + "Job released by user."); + + cupsdLogJob(job, CUPSD_LOG_INFO, "Released by \"%s\".", username); con->response->request.status.status_code = IPP_OK; + + cupsdCheckJobs(); } @@ -7579,6 +9246,79 @@ renew_subscription( cupsd_client_t *con, /* I - Client connection */ int sub_id) /* I - Subscription ID */ { + http_status_t status; /* Policy status */ + cupsd_subscription_t *sub; /* Subscription */ + ipp_attribute_t *lease; /* notify-lease-duration */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, + "renew_subscription(con=%p[%d], sub_id=%d)", + con, con->http.fd, sub_id); + + /* + * Is the subscription ID valid? + */ + + if ((sub = cupsdFindSubscription(sub_id)) == NULL) + { + /* + * Bad subscription ID... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("notify-subscription-id %d no good!"), sub_id); + return; + } + + if (sub->job) + { + /* + * Job subscriptions cannot be renewed... + */ + + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job subscriptions cannot be renewed!")); + return; + } + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(sub->dest ? sub->dest->op_policy_ptr : + DefaultPolicyPtr, + con, sub->owner)) != HTTP_OK) + { + send_http_error(con, status, sub->dest); + return; + } + + /* + * Renew the subscription... + */ + + lease = ippFindAttribute(con->request, "notify-lease-duration", + IPP_TAG_INTEGER); + + sub->lease = lease ? lease->values[0].integer : DefaultLeaseDuration; + + if (MaxLeaseDuration && (sub->lease == 0 || sub->lease > MaxLeaseDuration)) + { + cupsdLogMessage(CUPSD_LOG_INFO, + "renew_subscription: Limiting notify-lease-duration to " + "%d seconds.", + MaxLeaseDuration); + sub->lease = MaxLeaseDuration; + } + + sub->expire = sub->lease ? time(NULL) + sub->lease : 0; + + cupsdMarkDirty(CUPSD_DIRTY_SUBSCRIPTIONS); + + con->response->request.status.status_code = IPP_OK; + + ippAddInteger(con->response, IPP_TAG_SUBSCRIPTION, IPP_TAG_INTEGER, + "notify-lease-duration", sub->lease); } @@ -7592,12 +9332,12 @@ restart_job(cupsd_client_t *con, /* I - Client connection */ { ipp_attribute_t *attr; /* Current attribute */ int jobid; /* Job ID */ - char method[HTTP_MAX_URI], /* Method portion of URI */ + cupsd_job_t *job; /* Job information */ + char scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ host[HTTP_MAX_URI], /* Host portion of URI */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ int port; /* Port portion of URI */ - cupsd_job_t *job; /* Job information */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "restart_job(%p[%d], %s)", con, @@ -7629,8 +9369,8 @@ restart_job(cupsd_client_t *con, /* I - Client connection */ * Got a job URI; parse it to get the job ID... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); if (strncmp(resource, "/jobs/", 6)) @@ -7666,7 +9406,7 @@ restart_job(cupsd_client_t *con, /* I - Client connection */ * See if job is in any of the "completed" states... */ - if (job->state->values[0].integer <= IPP_JOB_PROCESSING) + if (job->state_value <= IPP_JOB_PROCESSING) { /* * Nope - return a "not possible" error... @@ -7681,7 +9421,9 @@ restart_job(cupsd_client_t *con, /* I - Client connection */ * See if we have retained the job files... */ - if (!JobFiles && job->state->values[0].integer > IPP_JOB_STOPPED) + cupsdLoadJob(job); + + if (!job->attrs || job->num_files == 0) { /* * Nope - return a "not possible" error... @@ -7698,21 +9440,44 @@ restart_job(cupsd_client_t *con, /* I - Client connection */ if (!validate_user(job, con, job->username, username, sizeof(username))) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to restart job id " - "%d owned by \"%s\"!"), - jobid, job->username); + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); return; } /* - * Restart the job and return... + * See if the job-hold-until attribute is specified... */ - cupsdRestartJob(job); + if ((attr = ippFindAttribute(con->request, "job-hold-until", + IPP_TAG_KEYWORD)) == NULL) + attr = ippFindAttribute(con->request, "job-hold-until", IPP_TAG_NAME); + + if (attr && strcmp(attr->values[0].string.text, "no-hold")) + { + /* + * Return the job to a held state... + */ + + cupsdLogJob(job, CUPSD_LOG_DEBUG, + "Restarted by \"%s\" with job-hold-until=%s.", + username, attr->values[0].string.text); + cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0); + + cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE, + NULL, job, "Job restarted by user with job-hold-until=%s", + attr->values[0].string.text); + } + else + { + /* + * Restart the job... + */ + + cupsdRestartJob(job); + cupsdCheckJobs(); + } - cupsdLogMessage(CUPSD_LOG_INFO, "Job %d was restarted by \"%s\".", jobid, - username); + cupsdLogJob(job, CUPSD_LOG_INFO, "Restarted by \"%s\".", username); con->response->request.status.status_code = IPP_OK; } @@ -7723,22 +9488,25 @@ restart_job(cupsd_client_t *con, /* I - Client connection */ */ static void -save_auth_info(cupsd_client_t *con, /* I - Client connection */ - cupsd_job_t *job) /* I - Job */ +save_auth_info( + cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job, /* I - Job */ + ipp_attribute_t *auth_info) /* I - auth-info attribute, if any */ { - int i; /* Looping var */ - char filename[1024]; /* Job authentication filename */ - cups_file_t *fp; /* Job authentication file */ - char line[1024]; /* Line for file */ + int i; /* Looping var */ + char filename[1024]; /* Job authentication filename */ + cups_file_t *fp; /* Job authentication file */ + char line[2048]; /* Line for file */ + cupsd_printer_t *dest; /* Destination printer/class */ /* * This function saves the in-memory authentication information for * a job so that it can be used to authenticate with a remote host. * The information is stored in a file that is readable only by the - * root user. The username and password are Base-64 encoded, each - * on a separate line, followed by random number (up to 1024) of - * newlines to limit the amount of information that is exposed. + * root user. The fields are Base-64 encoded, each on a separate line, + * followed by random number (up to 1024) of newlines to limit the + * amount of information that is exposed. * * Because of the potential for exposing of authentication information, * this functionality is only enabled when running cupsd as root. @@ -7758,6 +9526,9 @@ save_auth_info(cupsd_client_t *con, /* I - Client connection */ if (RunUser) return; + if ((dest = cupsdFindDest(job->dest)) == NULL) + return; + /* * Create the authentication file and change permissions... */ @@ -7774,19 +9545,54 @@ save_auth_info(cupsd_client_t *con, /* I - Client connection */ fchown(cupsFileNumber(fp), 0, 0); fchmod(cupsFileNumber(fp), 0400); - /* - * Write the authenticated username... - */ + if (auth_info && auth_info->num_values == dest->num_auth_info_required) + { + /* + * Write 1 to 3 auth values... + */ - httpEncode64_2(line, sizeof(line), con->username, strlen(con->username)); - cupsFilePrintf(fp, "%s\n", line); + cupsdClearString(&job->auth_username); + cupsdClearString(&job->auth_domain); + cupsdClearString(&job->auth_password); - /* - * Write the authenticated password... - */ + for (i = 0; i < auth_info->num_values; i ++) + { + httpEncode64_2(line, sizeof(line), auth_info->values[i].string.text, + strlen(auth_info->values[i].string.text)); + cupsFilePrintf(fp, "%s\n", line); + + if (!strcmp(dest->auth_info_required[i], "username")) + cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", + auth_info->values[i].string.text); + else if (!strcmp(dest->auth_info_required[i], "domain")) + cupsdSetStringf(&job->auth_domain, "AUTH_DOMAIN=%s", + auth_info->values[i].string.text); + else if (!strcmp(dest->auth_info_required[i], "password")) + cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", + auth_info->values[i].string.text); + } + } + else if (con->username[0]) + { + /* + * Write the authenticated username... + */ - httpEncode64_2(line, sizeof(line), con->password, strlen(con->password)); - cupsFilePrintf(fp, "%s\n", line); + httpEncode64_2(line, sizeof(line), con->username, strlen(con->username)); + cupsFilePrintf(fp, "%s\n", line); + + cupsdSetStringf(&job->auth_username, "AUTH_USERNAME=%s", con->username); + cupsdClearString(&job->auth_domain); + + /* + * Write the authenticated password... + */ + + httpEncode64_2(line, sizeof(line), con->password, strlen(con->password)); + cupsFilePrintf(fp, "%s\n", line); + + cupsdSetStringf(&job->auth_password, "AUTH_PASSWORD=%s", con->password); + } /* * Write a random number of newlines to the end of the file... @@ -7800,7 +9606,47 @@ save_auth_info(cupsd_client_t *con, /* I - Client connection */ */ cupsFileClose(fp); + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5_H) + if (con->gss_creds) + save_krb5_creds(con, job); + else if (job->ccname) + cupsdClearString(&(job->ccname)); +#endif /* HAVE_GSSAPI && HAVE_KRB5_H */ +} + + +#if defined(HAVE_GSSAPI) && defined(HAVE_KRB5_H) +/* + * 'save_krb5_creds()' - Save Kerberos credentials for the job. + */ + +static void +save_krb5_creds(cupsd_client_t *con, /* I - Client connection */ + cupsd_job_t *job) /* I - Job */ +{ + /* + * Get the credentials... + */ + + job->ccache = cupsdCopyKrb5Creds(con); + + /* + * Add the KRB5CCNAME environment variable to the job so that the + * backend can use the credentials when printing. + */ + + if (job->ccache) + { + cupsdSetStringf(&(job->ccname), "KRB5CCNAME=FILE:%s", + krb5_cc_get_name(KerberosContext, job->ccache)); + + cupsdLogJob(job, CUPSD_LOG_DEBUG2, "save_krb5_creds: %s", job->ccname); + } + else + cupsdClearString(&(job->ccname)); } +#endif /* HAVE_GSSAPI && HAVE_KRB5_H */ /* @@ -7812,12 +9658,14 @@ send_document(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer URI */ { ipp_attribute_t *attr; /* Current attribute */ - ipp_attribute_t *format; /* Document-format attribute */ + ipp_attribute_t *format; /* Request's document-format attribute */ + ipp_attribute_t *jformat; /* Job's document-format attribute */ + const char *default_format;/* document-format-default value */ int jobid; /* Job ID number */ cupsd_job_t *job; /* Current job */ char job_uri[HTTP_MAX_URI], /* Job URI */ - method[HTTP_MAX_URI], + scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ @@ -7838,6 +9686,7 @@ send_document(cupsd_client_t *con, /* I - Client connection */ struct stat fileinfo; /* File information */ int kbytes; /* Size of file */ int compression; /* Type of compression */ + int start_job; /* Start the job? */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "send_document(%p[%d], %s)", con, @@ -7869,8 +9718,8 @@ send_document(cupsd_client_t *con, /* I - Client connection */ * Got a job URI; parse it to get the job ID... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); if (strncmp(resource, "/jobs/", 6)) @@ -7902,16 +9751,15 @@ send_document(cupsd_client_t *con, /* I - Client connection */ return; } + printer = cupsdFindDest(job->dest); + /* * See if the job is owned by the requesting user... */ if (!validate_user(job, con, job->username, username, sizeof(username))) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to send document " - "for job #%d owned by \"%s\"!"), - jobid, job->username); + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); return; } @@ -7965,13 +9813,30 @@ send_document(cupsd_client_t *con, /* I - Client connection */ * Grab format from client... */ - if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2) + if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", + super, type) != 2) { send_ipp_status(con, IPP_BAD_REQUEST, _("Bad document-format \"%s\"!"), format->values[0].string.text); return; } } + else if ((default_format = cupsGetOption("document-format", + printer->num_options, + printer->options)) != NULL) + { + /* + * Use default document format... + */ + + if (sscanf(default_format, "%15[^/]/%31[^;]", super, type) != 2) + { + send_ipp_status(con, IPP_BAD_REQUEST, + _("Could not scan type \"%s\"!"), + default_format); + return; + } + } else { /* @@ -7988,35 +9853,47 @@ send_document(cupsd_client_t *con, /* I - Client connection */ * Auto-type the file... */ - cupsdLogMessage(CUPSD_LOG_DEBUG, "send_document: auto-typing file..."); + ipp_attribute_t *doc_name; /* document-name attribute */ - filetype = mimeFileType(MimeDatabase, con->filename, &compression); - if (filetype) - { - /* - * Replace the document-format attribute value with the auto-typed one. - */ + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Auto-typing file..."); - snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super, - filetype->type); + doc_name = ippFindAttribute(con->request, "document-name", IPP_TAG_NAME); + filetype = mimeFileType(MimeDatabase, con->filename, + doc_name ? doc_name->values[0].string.text : NULL, + &compression); - if (format) - { - free(format->values[0].string.text); - format->values[0].string.text = strdup(mimetype); - } - else - ippAddString(con->request, IPP_TAG_JOB, IPP_TAG_MIMETYPE, - "document-format", NULL, mimetype); - } - else + if (!filetype) filetype = mimeType(MimeDatabase, super, type); + + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Request file type is %s/%s.", + filetype->super, filetype->type); } else filetype = mimeType(MimeDatabase, super, type); - if (!filetype) + if (filetype) + { + /* + * Replace the document-format attribute value with the auto-typed or + * default one. + */ + + snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super, + filetype->type); + + if ((jformat = ippFindAttribute(job->attrs, "document-format", + IPP_TAG_MIMETYPE)) != NULL) + { + _cupsStrFree(jformat->values[0].string.text); + + jformat->values[0].string.text = _cupsStrAlloc(mimetype); + } + else + ippAddString(job->attrs, IPP_TAG_JOB, IPP_TAG_MIMETYPE, + "document-format", NULL, mimetype); + } + else if (!filetype) { send_ipp_status(con, IPP_DOCUMENT_FORMAT, _("Unsupported format \'%s/%s\'!"), super, type); @@ -8030,22 +9907,29 @@ send_document(cupsd_client_t *con, /* I - Client connection */ return; } - cupsdLogMessage(CUPSD_LOG_DEBUG, - "send_document: request file type is %s/%s.", - filetype->super, filetype->type); + if (printer->filetypes && !cupsArrayFind(printer->filetypes, filetype)) + { + snprintf(mimetype, sizeof(mimetype), "%s/%s", filetype->super, + filetype->type); + + send_ipp_status(con, IPP_DOCUMENT_FORMAT, + _("Unsupported format \'%s\'!"), mimetype); + + ippAddString(con->response, IPP_TAG_UNSUPPORTED_GROUP, IPP_TAG_MIMETYPE, + "document-format", NULL, mimetype); + + return; + } /* * Add the file to the job... */ + cupsdLoadJob(job); + if (add_file(con, job, filetype, compression)) return; - if (job->dtype & CUPS_PRINTER_CLASS) - printer = cupsdFindClass(job->dest); - else - printer = cupsdFindPrinter(job->dest); - if (stat(con->filename, &fileinfo)) kbytes = 0; else @@ -8063,9 +9947,8 @@ send_document(cupsd_client_t *con, /* I - Client connection */ cupsdClearString(&con->filename); - cupsdLogMessage(CUPSD_LOG_INFO, - "File of type %s/%s queued in job #%d by \"%s\".", - filetype->super, filetype->type, job->id, job->username); + cupsdLogJob(job, CUPSD_LOG_INFO, "File of type %s/%s queued by \"%s\".", + filetype->super, filetype->type, job->username); /* * Start the job if this is the last document... @@ -8079,49 +9962,31 @@ send_document(cupsd_client_t *con, /* I - Client connection */ * See if we need to add the ending sheet... */ - if (printer && - !(printer->type & (CUPS_PRINTER_REMOTE | CUPS_PRINTER_IMPLICIT)) && - (attr = ippFindAttribute(job->attrs, "job-sheets", - IPP_TAG_ZERO)) != NULL && - attr->num_values > 1) - { - /* - * Yes... - */ - - cupsdLogMessage(CUPSD_LOG_INFO, - "Adding end banner page \"%s\" to job %d.", - attr->values[1].string.text, job->id); - - kbytes = copy_banner(con, job, attr->values[1].string.text); - - cupsdUpdateQuota(printer, job->username, 0, kbytes); - } + if (cupsdTimeoutJob(job)) + return; - if (job->state->values[0].integer == IPP_JOB_STOPPED) + if (job->state_value == IPP_JOB_STOPPED) + { job->state->values[0].integer = IPP_JOB_PENDING; - else if (job->state->values[0].integer == IPP_JOB_HELD) + job->state_value = IPP_JOB_PENDING; + } + else if (job->state_value == IPP_JOB_HELD) { if ((attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_KEYWORD)) == NULL) attr = ippFindAttribute(job->attrs, "job-hold-until", IPP_TAG_NAME); if (!attr || !strcmp(attr->values[0].string.text, "no-hold")) + { job->state->values[0].integer = IPP_JOB_PENDING; + job->state_value = IPP_JOB_PENDING; + } } - cupsdSaveJob(job); - - /* - * Start the job if possible... Since cupsdCheckJobs() can cancel a - * job if it doesn't print, we need to re-find the job afterwards... - */ - - jobid = job->id; + job->dirty = 1; + cupsdMarkDirty(CUPSD_DIRTY_JOBS); - cupsdCheckJobs(); - - job = cupsdFindJob(jobid); + start_job = 1; } else { @@ -8132,9 +9997,14 @@ send_document(cupsd_client_t *con, /* I - Client connection */ if (!attr || !strcmp(attr->values[0].string.text, "no-hold")) { job->state->values[0].integer = IPP_JOB_HELD; - job->hold_until = time(NULL) + 60; - cupsdSaveJob(job); + job->state_value = IPP_JOB_HELD; + job->hold_until = time(NULL) + MultipleOperationTimeout; + job->dirty = 1; + + cupsdMarkDirty(CUPSD_DIRTY_JOBS); } + + start_job = 0; } /* @@ -8150,10 +10020,17 @@ send_document(cupsd_client_t *con, /* I - Client connection */ ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_INTEGER, "job-id", jobid); ippAddInteger(con->response, IPP_TAG_JOB, IPP_TAG_ENUM, "job-state", - job ? job->state->values[0].integer : IPP_JOB_CANCELLED); + job->state_value); add_job_state_reasons(con, job); con->response->request.status.status_code = IPP_OK; + + /* + * Start the job if necessary... + */ + + if (start_job) + cupsdCheckJobs(); } @@ -8162,14 +10039,81 @@ send_document(cupsd_client_t *con, /* I - Client connection */ */ static void -send_http_error(cupsd_client_t *con, /* I - Client connection */ - http_status_t status) /* I - HTTP status code */ +send_http_error( + cupsd_client_t *con, /* I - Client connection */ + http_status_t status, /* I - HTTP status code */ + cupsd_printer_t *printer) /* I - Printer, if any */ { - cupsdLogMessage(CUPSD_LOG_ERROR, "%s: %s", - ippOpString(con->request->request.op.operation_id), - httpStatus(status)); + ipp_attribute_t *uri; /* Request URI, if any */ + + + if ((uri = ippFindAttribute(con->request, "printer-uri", + IPP_TAG_URI)) == NULL) + uri = ippFindAttribute(con->request, "job-uri", IPP_TAG_URI); + + cupsdLogMessage(status == HTTP_FORBIDDEN ? CUPSD_LOG_ERROR : CUPSD_LOG_DEBUG, + "Returning HTTP %s for %s (%s) from %s", + httpStatus(status), + ippOpString(con->request->request.op.operation_id), + uri ? uri->values[0].string.text : "no URI", + con->http.hostname); + + if (printer) + { + int auth_type; /* Type of authentication required */ + + + auth_type = CUPSD_AUTH_NONE; + + if (status == HTTP_UNAUTHORIZED && + printer->num_auth_info_required > 0 && + !strcmp(printer->auth_info_required[0], "negotiate") && + con->request && + (con->request->request.op.operation_id == IPP_PRINT_JOB || + con->request->request.op.operation_id == IPP_CREATE_JOB || + con->request->request.op.operation_id == CUPS_AUTHENTICATE_JOB)) + { + /* + * Creating and authenticating jobs requires Kerberos... + */ + + auth_type = CUPSD_AUTH_NEGOTIATE; + } + else + { + /* + * Use policy/location-defined authentication requirements... + */ + + char resource[HTTP_MAX_URI]; /* Resource portion of URI */ + cupsd_location_t *auth; /* Pointer to authentication element */ + + + if (printer->type & CUPS_PRINTER_CLASS) + snprintf(resource, sizeof(resource), "/classes/%s", printer->name); + else + snprintf(resource, sizeof(resource), "/printers/%s", printer->name); - cupsdSendError(con, status); + if ((auth = cupsdFindBest(resource, HTTP_POST)) == NULL || + auth->type == CUPSD_AUTH_NONE) + auth = cupsdFindPolicyOp(printer->op_policy_ptr, + con->request ? + con->request->request.op.operation_id : + IPP_PRINT_JOB); + + if (auth) + { + if (auth->type == CUPSD_AUTH_DEFAULT) + auth_type = DefaultAuthType; + else + auth_type = auth->type; + } + } + + cupsdSendError(con, status, auth_type); + } + else + cupsdSendError(con, status, CUPSD_AUTH_NONE); ippDelete(con->response); con->response = NULL; @@ -8184,31 +10128,22 @@ send_http_error(cupsd_client_t *con, /* I - Client connection */ static void send_ipp_status(cupsd_client_t *con, /* I - Client connection */ - ipp_status_t status, /* I - IPP status code */ - const char *message, /* I - Status message */ - ...) /* I - Additional args as needed */ + ipp_status_t status, /* I - IPP status code */ + const char *message,/* I - Status message */ + ...) /* I - Additional args as needed */ { va_list ap; /* Pointer to additional args */ char formatted[1024]; /* Formatted errror message */ - if (message) - { - va_start(ap, message); - vsnprintf(formatted, sizeof(formatted), - _cupsLangString(con->language, message), ap); - va_end(ap); + va_start(ap, message); + vsnprintf(formatted, sizeof(formatted), + _cupsLangString(con->language, message), ap); + va_end(ap); - cupsdLogMessage(status >= IPP_BAD_REQUEST ? CUPSD_LOG_ERROR : CUPSD_LOG_INFO, - "%s %s: %s", - ippOpString(con->request->request.op.operation_id), - ippErrorString(status), formatted); - } - else - cupsdLogMessage(status >= IPP_BAD_REQUEST ? CUPSD_LOG_ERROR : CUPSD_LOG_INFO, - "%s %s", - ippOpString(con->request->request.op.operation_id), - ippErrorString(status)); + cupsdLogMessage(CUPSD_LOG_DEBUG, "%s %s: %s", + ippOpString(con->request->request.op.operation_id), + ippErrorString(status), formatted); con->response->request.status.status_code = status; @@ -8222,9 +10157,8 @@ send_ipp_status(cupsd_client_t *con, /* I - Client connection */ ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE, "attributes-natural-language", NULL, DefaultLanguage); - if (message) - ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, - "status-message", NULL, formatted); + ippAddString(con->response, IPP_TAG_OPERATION, IPP_TAG_TEXT, + "status-message", NULL, formatted); } @@ -8237,18 +10171,9 @@ set_default(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer URI */ { http_status_t status; /* Policy status */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ - username[HTTP_MAX_URI], - /* Username portion of URI */ - host[HTTP_MAX_URI], - /* Host portion of URI */ - resource[HTTP_MAX_URI]; - /* Resource portion of URI */ - int port; /* Port portion of URI */ - const char *name; /* Printer name */ - cupsd_printer_t *printer; /* Printer */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + cupsd_printer_t *printer, /* Printer */ + *oldprinter; /* Old default printer */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_default(%p[%d], %s)", con, @@ -8258,11 +10183,7 @@ set_default(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((name = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -8279,7 +10200,7 @@ set_default(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(DefaultPolicyPtr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, NULL); return; } @@ -8287,16 +10208,22 @@ set_default(cupsd_client_t *con, /* I - Client connection */ * Set it as the default... */ + oldprinter = DefaultPrinter; DefaultPrinter = printer; - cupsdSaveAllPrinters(); - cupsdSaveAllClasses(); + if (oldprinter) + cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, oldprinter, NULL, + "%s is no longer the default printer.", oldprinter->name); - cupsdWritePrintcap(); + cupsdAddEvent(CUPSD_EVENT_PRINTER_STATE, printer, NULL, + "%s is now the default printer.", printer->name); + + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS | CUPSD_DIRTY_CLASSES | + CUPSD_DIRTY_REMOTE | CUPSD_DIRTY_PRINTCAP); cupsdLogMessage(CUPSD_LOG_INFO, - "Default destination set to \"%s\" by \"%s\".", name, - con->username); + "Default destination set to \"%s\" by \"%s\".", + printer->name, get_username(con)); /* * Everything was ok, so return OK status... @@ -8318,7 +10245,7 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ *attr2; /* Job attribute */ int jobid; /* Job ID */ cupsd_job_t *job; /* Current job */ - char method[HTTP_MAX_URI], + char scheme[HTTP_MAX_URI], /* Method portion of URI */ username[HTTP_MAX_URI], /* Username portion of URI */ @@ -8327,6 +10254,8 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ resource[HTTP_MAX_URI]; /* Resource portion of URI */ int port; /* Port portion of URI */ + int event; /* Events? */ + int check_jobs; /* Check jobs? */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_job_attrs(%p[%d], %s)", con, @@ -8364,8 +10293,8 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ * Got a job URI; parse it to get the job ID... */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, + httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, scheme, + sizeof(scheme), username, sizeof(username), host, sizeof(host), &port, resource, sizeof(resource)); if (strncmp(resource, "/jobs/", 6)) @@ -8401,7 +10330,7 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ * See if the job has been completed... */ - if (job->state->values[0].integer > IPP_JOB_STOPPED) + if (job->state_value > IPP_JOB_STOPPED) { /* * Return a "not-possible" error... @@ -8418,10 +10347,7 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ if (!validate_user(job, con, job->username, username, sizeof(username))) { - send_ipp_status(con, IPP_FORBIDDEN, - _("You are not authorized to alter job id " - "%d owned by \"%s\"!"), - jobid, job->username); + send_http_error(con, HTTP_UNAUTHORIZED, cupsdFindDest(job->dest)); return; } @@ -8429,6 +10355,11 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ * See what the user wants to change. */ + cupsdLoadJob(job); + + check_jobs = 0; + event = 0; + for (attr = con->request->attrs; attr; attr = attr->next) { if (attr->group_tag != IPP_TAG_JOB || !attr->name) @@ -8441,6 +10372,7 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ !strcmp(attr->name, "job-detailed-status-messages") || !strcmp(attr->name, "job-document-access-errors") || !strcmp(attr->name, "job-id") || + !strcmp(attr->name, "job-impressions-completed") || !strcmp(attr->name, "job-k-octets") || !strcmp(attr->name, "job-originating-host-name") || !strcmp(attr->name, "job-originating-user-name") || @@ -8454,7 +10386,6 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ !strcmp(attr->name, "number-of-intervening-jobs") || !strcmp(attr->name, "output-device-assigned") || !strncmp(attr->name, "date-time-at-", 13) || - !strncmp(attr->name, "job-impressions", 15) || !strncmp(attr->name, "job-k-octets", 12) || !strncmp(attr->name, "job-media-sheets", 16) || !strncmp(attr->name, "time-at-", 8)) @@ -8482,160 +10413,537 @@ set_job_attrs(cupsd_client_t *con, /* I - Client connection */ { send_ipp_status(con, IPP_REQUEST_VALUE, _("Bad job-priority value!")); - if ((attr2 = copy_attribute(con->response, attr, 0)) != NULL) - attr2->group_tag = IPP_TAG_UNSUPPORTED_GROUP; - } - else if (job->state->values[0].integer >= IPP_JOB_PROCESSING) + if ((attr2 = copy_attribute(con->response, attr, 0)) != NULL) + attr2->group_tag = IPP_TAG_UNSUPPORTED_GROUP; + } + else if (job->state_value >= IPP_JOB_PROCESSING) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job is completed and cannot be changed.")); + return; + } + else if (con->response->request.status.status_code == IPP_OK) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-priority to %d", + attr->values[0].integer); + cupsdSetJobPriority(job, attr->values[0].integer); + + check_jobs = 1; + event |= CUPSD_EVENT_JOB_CONFIG_CHANGED | + CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED; + } + } + else if (!strcmp(attr->name, "job-state")) + { + /* + * Change the job state... + */ + + if (attr->value_tag != IPP_TAG_ENUM) + { + send_ipp_status(con, IPP_REQUEST_VALUE, _("Bad job-state value!")); + + if ((attr2 = copy_attribute(con->response, attr, 0)) != NULL) + attr2->group_tag = IPP_TAG_UNSUPPORTED_GROUP; + } + else + { + switch (attr->values[0].integer) + { + case IPP_JOB_PENDING : + case IPP_JOB_HELD : + if (job->state_value > IPP_JOB_HELD) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job state cannot be changed.")); + return; + } + else if (con->response->request.status.status_code == IPP_OK) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-state to %d", + attr->values[0].integer); + cupsdSetJobState(job, attr->values[0].integer, + CUPSD_JOB_DEFAULT, + "Job state changed by \"%s\"", username); + check_jobs = 1; + } + break; + + case IPP_JOB_PROCESSING : + case IPP_JOB_STOPPED : + if (job->state_value != attr->values[0].integer) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job state cannot be changed.")); + return; + } + break; + + case IPP_JOB_CANCELED : + case IPP_JOB_ABORTED : + case IPP_JOB_COMPLETED : + if (job->state_value > IPP_JOB_PROCESSING) + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Job state cannot be changed.")); + return; + } + else if (con->response->request.status.status_code == IPP_OK) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-state to %d", + attr->values[0].integer); + cupsdSetJobState(job, (ipp_jstate_t)attr->values[0].integer, + CUPSD_JOB_DEFAULT, + "Job state changed by \"%s\"", username); + check_jobs = 1; + } + break; + } + } + } + else if (con->response->request.status.status_code != IPP_OK) + continue; + else if ((attr2 = ippFindAttribute(job->attrs, attr->name, + IPP_TAG_ZERO)) != NULL) + { + /* + * Some other value; first free the old value... + */ + + if (job->attrs->prev) + job->attrs->prev->next = attr2->next; + else + job->attrs->attrs = attr2->next; + + if (job->attrs->last == attr2) + job->attrs->last = job->attrs->prev; + + _ippFreeAttr(attr2); + + /* + * Then copy the attribute... + */ + + copy_attribute(job->attrs, attr, 0); + + /* + * See if the job-name or job-hold-until is being changed. + */ + + if (!strcmp(attr->name, "job-hold-until")) + { + cupsdLogJob(job, CUPSD_LOG_DEBUG, "Setting job-hold-until to %s", + attr->values[0].string.text); + cupsdSetJobHoldUntil(job, attr->values[0].string.text, 0); + + if (!strcmp(attr->values[0].string.text, "no-hold")) + { + cupsdReleaseJob(job); + check_jobs = 1; + } + else + cupsdSetJobState(job, IPP_JOB_HELD, CUPSD_JOB_DEFAULT, + "Job held by \"%s\".", username); + + event |= CUPSD_EVENT_JOB_CONFIG_CHANGED | CUPSD_EVENT_JOB_STATE; + } + } + else if (attr->value_tag == IPP_TAG_DELETEATTR) + { + /* + * Delete the attribute... + */ + + if ((attr2 = ippFindAttribute(job->attrs, attr->name, + IPP_TAG_ZERO)) != NULL) + { + if (job->attrs->prev) + job->attrs->prev->next = attr2->next; + else + job->attrs->attrs = attr2->next; + + if (attr2 == job->attrs->last) + job->attrs->last = job->attrs->prev; + + _ippFreeAttr(attr2); + + event |= CUPSD_EVENT_JOB_CONFIG_CHANGED; + } + } + else + { + /* + * Add new option by copying it... + */ + + copy_attribute(job->attrs, attr, 0); + + event |= CUPSD_EVENT_JOB_CONFIG_CHANGED; + } + } + + /* + * Save the job... + */ + + job->dirty = 1; + cupsdMarkDirty(CUPSD_DIRTY_JOBS); + + /* + * Send events as needed... + */ + + if (event & CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED) + cupsdAddEvent(CUPSD_EVENT_PRINTER_QUEUE_ORDER_CHANGED, + cupsdFindDest(job->dest), job, + "Job priority changed by user."); + + if (event & CUPSD_EVENT_JOB_STATE) + cupsdAddEvent(CUPSD_EVENT_JOB_STATE, cupsdFindDest(job->dest), job, + job->state_value == IPP_JOB_HELD ? + "Job held by user." : "Job restarted by user."); + + if (event & CUPSD_EVENT_JOB_CONFIG_CHANGED) + cupsdAddEvent(CUPSD_EVENT_JOB_CONFIG_CHANGED, cupsdFindDest(job->dest), job, + "Job options changed by user."); + + /* + * Start jobs if possible... + */ + + if (check_jobs) + cupsdCheckJobs(); +} + + +/* + * 'set_printer_attrs()' - Set printer attributes. + */ + +static void +set_printer_attrs(cupsd_client_t *con, /* I - Client connection */ + ipp_attribute_t *uri) /* I - Printer */ +{ + http_status_t status; /* Policy status */ + cups_ptype_t dtype; /* Destination type (printer/class) */ + cupsd_printer_t *printer; /* Printer/class */ + ipp_attribute_t *attr; /* Printer attribute */ + int changed = 0; /* Was anything changed? */ + + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_printer_attrs(%p[%d], %s)", con, + con->http.fd, uri->values[0].string.text); + + /* + * Is the destination valid? + */ + + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) + { + /* + * Bad URI... + */ + + send_ipp_status(con, IPP_NOT_FOUND, + _("The printer or class was not found.")); + return; + } + + /* + * Check policy... + */ + + if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) + { + send_http_error(con, status, printer); + return; + } + + /* + * Return a list of attributes that can be set via Set-Printer-Attributes. + */ + + if ((attr = ippFindAttribute(con->request, "printer-location", + IPP_TAG_TEXT)) != NULL) + { + cupsdSetString(&printer->location, attr->values[0].string.text); + changed = 1; + } + + if ((attr = ippFindAttribute(con->request, "printer-info", + IPP_TAG_TEXT)) != NULL) + { + cupsdSetString(&printer->info, attr->values[0].string.text); + changed = 1; + } + + /* + * Update the printer attributes and return... + */ + + if (changed) + { + cupsdSetPrinterAttrs(printer); + cupsdMarkDirty(CUPSD_DIRTY_PRINTERS); + + cupsdAddEvent(CUPSD_EVENT_PRINTER_CONFIG, printer, NULL, + "Printer \"%s\" description or location changed by \"%s\".", + printer->name, get_username(con)); + + cupsdLogMessage(CUPSD_LOG_INFO, + "Printer \"%s\" description or location changed by \"%s\".", + printer->name, get_username(con)); + } + + con->response->request.status.status_code = IPP_OK; +} + + +/* + * 'set_printer_defaults()' - Set printer default options from a request. + */ + +static void +set_printer_defaults( + cupsd_client_t *con, /* I - Client connection */ + cupsd_printer_t *printer) /* I - Printer */ +{ + int i; /* Looping var */ + ipp_attribute_t *attr; /* Current attribute */ + int namelen; /* Length of attribute name */ + char name[256], /* New attribute name */ + value[256]; /* String version of integer attrs */ + + + for (attr = con->request->attrs; attr; attr = attr->next) + { + /* + * Skip non-printer attributes... + */ + + if (attr->group_tag != IPP_TAG_PRINTER || !attr->name) + continue; + + cupsdLogMessage(CUPSD_LOG_DEBUG2, "set_printer_defaults: %s", attr->name); + + if (!strcmp(attr->name, "job-sheets-default")) + { + /* + * Only allow keywords and names... + */ + + if (attr->value_tag != IPP_TAG_NAME && attr->value_tag != IPP_TAG_KEYWORD) + continue; + + /* + * Only allow job-sheets-default to be set when running without a + * system high classification level... + */ + + if (Classification) + continue; + + cupsdSetString(&printer->job_sheets[0], attr->values[0].string.text); + + if (attr->num_values > 1) + cupsdSetString(&printer->job_sheets[1], attr->values[1].string.text); + else + cupsdSetString(&printer->job_sheets[1], "none"); + } + else if (!strcmp(attr->name, "requesting-user-name-allowed")) + { + cupsdFreePrinterUsers(printer); + + printer->deny_users = 0; + + if (attr->value_tag == IPP_TAG_NAME && + (attr->num_values > 1 || + strcmp(attr->values[0].string.text, "all"))) { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job is completed and cannot be changed.")); - return; + for (i = 0; i < attr->num_values; i ++) + cupsdAddPrinterUser(printer, attr->values[i].string.text); } - else if (con->response->request.status.status_code == IPP_OK) - cupsdSetJobPriority(job, attr->values[0].integer); } - else if (!strcmp(attr->name, "job-state")) + else if (!strcmp(attr->name, "requesting-user-name-denied")) { - /* - * Change the job state... - */ + cupsdFreePrinterUsers(printer); - if (attr->value_tag != IPP_TAG_ENUM) - { - send_ipp_status(con, IPP_REQUEST_VALUE, _("Bad job-state value!")); + printer->deny_users = 1; - if ((attr2 = copy_attribute(con->response, attr, 0)) != NULL) - attr2->group_tag = IPP_TAG_UNSUPPORTED_GROUP; - } - else + if (attr->value_tag == IPP_TAG_NAME && + (attr->num_values > 1 || + strcmp(attr->values[0].string.text, "none"))) { - switch (attr->values[0].integer) - { - case IPP_JOB_PENDING : - case IPP_JOB_HELD : - if (job->state->values[0].integer > IPP_JOB_HELD) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job state cannot be changed.")); - return; - } - else if (con->response->request.status.status_code == IPP_OK) - job->state->values[0].integer = attr->values[0].integer; - break; - - case IPP_JOB_PROCESSING : - case IPP_JOB_STOPPED : - if (job->state->values[0].integer != attr->values[0].integer) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job state cannot be changed.")); - return; - } - break; + for (i = 0; i < attr->num_values; i ++) + cupsdAddPrinterUser(printer, attr->values[i].string.text); + } + } + else if (!strcmp(attr->name, "job-quota-period")) + { + if (attr->value_tag != IPP_TAG_INTEGER) + continue; - case IPP_JOB_CANCELLED : - case IPP_JOB_ABORTED : - case IPP_JOB_COMPLETED : - if (job->state->values[0].integer > IPP_JOB_PROCESSING) - { - send_ipp_status(con, IPP_NOT_POSSIBLE, - _("Job state cannot be changed.")); - return; - } - else if (con->response->request.status.status_code == IPP_OK) - { - cupsdCancelJob(job, 0); + cupsdLogMessage(CUPSD_LOG_DEBUG, "Setting job-quota-period to %d...", + attr->values[0].integer); + cupsdFreeQuotas(printer); - if (JobHistory) - { - job->state->values[0].integer = attr->values[0].integer; - cupsdSaveJob(job); - } - } - break; - } - } + printer->quota_period = attr->values[0].integer; } - else if (con->response->request.status.status_code != IPP_OK) - continue; - else if ((attr2 = ippFindAttribute(job->attrs, attr->name, - IPP_TAG_ZERO)) != NULL) + else if (!strcmp(attr->name, "job-k-limit")) { - /* - * Some other value; first free the old value... - */ + if (attr->value_tag != IPP_TAG_INTEGER) + continue; - if (job->attrs->prev) - job->attrs->prev->next = attr2->next; - else - job->attrs->attrs = attr2->next; + cupsdLogMessage(CUPSD_LOG_DEBUG, "Setting job-k-limit to %d...", + attr->values[0].integer); + cupsdFreeQuotas(printer); - if (job->attrs->last == attr2) - job->attrs->last = job->attrs->prev; + printer->k_limit = attr->values[0].integer; + } + else if (!strcmp(attr->name, "job-page-limit")) + { + if (attr->value_tag != IPP_TAG_INTEGER) + continue; - _ipp_free_attr(attr2); + cupsdLogMessage(CUPSD_LOG_DEBUG, "Setting job-page-limit to %d...", + attr->values[0].integer); + cupsdFreeQuotas(printer); - /* - * Then copy the attribute... - */ + printer->page_limit = attr->values[0].integer; + } + else if (!strcmp(attr->name, "printer-op-policy")) + { + cupsd_policy_t *p; /* Policy */ - copy_attribute(job->attrs, attr, 0); - /* - * See if the job-name or job-hold-until is being changed. - */ + if (attr->value_tag != IPP_TAG_NAME) + continue; - if (!strcmp(attr->name, "job-hold-until")) + if ((p = cupsdFindPolicy(attr->values[0].string.text)) != NULL) { - cupsdSetJobHoldUntil(job, attr->values[0].string.text); - - if (!strcmp(attr->values[0].string.text, "no-hold")) - cupsdReleaseJob(job); - else - cupsdHoldJob(job); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting printer-op-policy to \"%s\"...", + attr->values[0].string.text); + cupsdSetString(&printer->op_policy, attr->values[0].string.text); + printer->op_policy_ptr = p; + } + else + { + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Unknown printer-op-policy \"%s\"."), + attr->values[0].string.text); + return; } } - else if (attr->value_tag == IPP_TAG_DELETEATTR) + else if (!strcmp(attr->name, "printer-error-policy")) { - /* - * Delete the attribute... - */ + if (attr->value_tag != IPP_TAG_NAME && attr->value_tag != IPP_TAG_KEYWORD) + continue; - if ((attr2 = ippFindAttribute(job->attrs, attr->name, - IPP_TAG_ZERO)) != NULL) + if (strcmp(attr->values[0].string.text, "retry-current-job") && + ((printer->type & (CUPS_PRINTER_IMPLICIT | CUPS_PRINTER_CLASS)) || + (strcmp(attr->values[0].string.text, "abort-job") && + strcmp(attr->values[0].string.text, "retry-job") && + strcmp(attr->values[0].string.text, "stop-printer")))) { - if (job->attrs->prev) - job->attrs->prev->next = attr2->next; - else - job->attrs->attrs = attr2->next; - - if (attr2 == job->attrs->last) - job->attrs->last = job->attrs->prev; - - _ipp_free_attr(attr2); + send_ipp_status(con, IPP_NOT_POSSIBLE, + _("Unknown printer-error-policy \"%s\"."), + attr->values[0].string.text); + return; } + + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting printer-error-policy to \"%s\"...", + attr->values[0].string.text); + cupsdSetString(&printer->error_policy, attr->values[0].string.text); } - else + + /* + * Skip any other non-default attributes... + */ + + namelen = strlen(attr->name); + if (namelen < 9 || strcmp(attr->name + namelen - 8, "-default") || + namelen > (sizeof(name) - 1) || attr->num_values != 1) + continue; + + /* + * OK, anything else must be a user-defined default... + */ + + strlcpy(name, attr->name, sizeof(name)); + name[namelen - 8] = '\0'; /* Strip "-default" */ + + switch (attr->value_tag) { - /* - * Add new option by copying it... - */ + case IPP_TAG_DELETEATTR : + printer->num_options = cupsRemoveOption(name, + printer->num_options, + &(printer->options)); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Deleting %s", attr->name); + break; - copy_attribute(job->attrs, attr, 0); - } - } + case IPP_TAG_NAME : + case IPP_TAG_KEYWORD : + case IPP_TAG_URI : + printer->num_options = cupsAddOption(name, + attr->values[0].string.text, + printer->num_options, + &(printer->options)); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting %s to \"%s\"...", attr->name, + attr->values[0].string.text); + break; - /* - * Save the job... - */ + case IPP_TAG_BOOLEAN : + printer->num_options = cupsAddOption(name, + attr->values[0].boolean ? + "true" : "false", + printer->num_options, + &(printer->options)); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting %s to %s...", attr->name, + attr->values[0].boolean ? "true" : "false"); + break; - cupsdSaveJob(job); + case IPP_TAG_INTEGER : + case IPP_TAG_ENUM : + sprintf(value, "%d", attr->values[0].integer); + printer->num_options = cupsAddOption(name, value, + printer->num_options, + &(printer->options)); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting %s to %s...", attr->name, value); + break; - /* - * Start jobs if possible... - */ + case IPP_TAG_RANGE : + sprintf(value, "%d-%d", attr->values[0].range.lower, + attr->values[0].range.upper); + printer->num_options = cupsAddOption(name, value, + printer->num_options, + &(printer->options)); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting %s to %s...", attr->name, value); + break; - cupsdCheckJobs(); + case IPP_TAG_RESOLUTION : + sprintf(value, "%dx%d%s", attr->values[0].resolution.xres, + attr->values[0].resolution.yres, + attr->values[0].resolution.units == IPP_RES_PER_INCH ? + "dpi" : "dpc"); + printer->num_options = cupsAddOption(name, value, + printer->num_options, + &(printer->options)); + cupsdLogMessage(CUPSD_LOG_DEBUG, + "Setting %s to %s...", attr->name, value); + break; + + default : + /* Do nothing for other values */ + break; + } + } } @@ -8648,17 +10956,7 @@ start_printer(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer URI */ { http_status_t status; /* Policy status */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ - username[HTTP_MAX_URI], - /* Username portion of URI */ - host[HTTP_MAX_URI], - /* Host portion of URI */ - resource[HTTP_MAX_URI]; - /* Resource portion of URI */ - int port; /* Port portion of URI */ - const char *name; /* Printer name */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cupsd_printer_t *printer; /* Printer data */ @@ -8669,11 +10967,7 @@ start_printer(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((name = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -8690,7 +10984,7 @@ start_printer(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } @@ -8703,10 +10997,11 @@ start_printer(cupsd_client_t *con, /* I - Client connection */ cupsdStartPrinter(printer, 1); if (dtype & CUPS_PRINTER_CLASS) - cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" started by \"%s\".", name, - con->username); - cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" started by \"%s\".", name, - con->username); + cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" started by \"%s\".", + printer->name, get_username(con)); + else + cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" started by \"%s\".", + printer->name, get_username(con)); cupsdCheckJobs(); @@ -8727,17 +11022,7 @@ stop_printer(cupsd_client_t *con, /* I - Client connection */ ipp_attribute_t *uri) /* I - Printer URI */ { http_status_t status; /* Policy status */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ - username[HTTP_MAX_URI], - /* Username portion of URI */ - host[HTTP_MAX_URI], - /* Host portion of URI */ - resource[HTTP_MAX_URI]; - /* Resource portion of URI */ - int port; /* Port portion of URI */ - const char *name; /* Printer name */ + cups_ptype_t dtype; /* Destination type (printer/class) */ cupsd_printer_t *printer; /* Printer data */ ipp_attribute_t *attr; /* printer-state-message attribute */ @@ -8749,11 +11034,7 @@ stop_printer(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if ((name = cupsdValidateDest(host, resource, &dtype, &printer)) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -8770,7 +11051,7 @@ stop_printer(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } @@ -8790,11 +11071,11 @@ stop_printer(cupsd_client_t *con, /* I - Client connection */ cupsdStopPrinter(printer, 1); if (dtype & CUPS_PRINTER_CLASS) - cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" stopped by \"%s\".", name, - con->username); + cupsdLogMessage(CUPSD_LOG_INFO, "Class \"%s\" stopped by \"%s\".", + printer->name, get_username(con)); else - cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" stopped by \"%s\".", name, - con->username); + cupsdLogMessage(CUPSD_LOG_INFO, "Printer \"%s\" stopped by \"%s\".", + printer->name, get_username(con)); /* * Everything was ok, so return OK status... @@ -8804,6 +11085,101 @@ stop_printer(cupsd_client_t *con, /* I - Client connection */ } +/* + * 'url_encode_attr()' - URL-encode a string attribute. + */ + +static void +url_encode_attr(ipp_attribute_t *attr, /* I - Attribute */ + char *buffer,/* I - String buffer */ + int bufsize)/* I - Size of buffer */ +{ + int i; /* Looping var */ + char *bufptr, /* Pointer into buffer */ + *bufend; /* End of buffer */ + + + strlcpy(buffer, attr->name, bufsize); + bufptr = buffer + strlen(buffer); + bufend = buffer + bufsize - 1; + + for (i = 0; i < attr->num_values; i ++) + { + if (bufptr >= bufend) + break; + + if (i) + *bufptr++ = ','; + else + *bufptr++ = '='; + + if (bufptr >= bufend) + break; + + *bufptr++ = '\''; + + bufptr = url_encode_string(attr->values[i].string.text, + bufptr, bufend - bufptr + 1); + + if (bufptr >= bufend) + break; + + *bufptr++ = '\''; + } + + *bufptr = '\0'; +} + + +/* + * 'url_encode_string()' - URL-encode a string. + */ + +static char * /* O - End of string */ +url_encode_string(const char *s, /* I - String */ + char *buffer, /* I - String buffer */ + int bufsize) /* I - Size of buffer */ +{ + char *bufptr, /* Pointer into buffer */ + *bufend; /* End of buffer */ + static const char *hex = "0123456789ABCDEF"; + /* Hex digits */ + + + bufptr = buffer; + bufend = buffer + bufsize - 1; + + while (*s && bufptr < bufend) + { + if (*s == ' ' || *s == '%') + { + if (bufptr >= (bufend - 2)) + break; + + *bufptr++ = '%'; + *bufptr++ = hex[(*s >> 4) & 15]; + *bufptr++ = hex[*s & 15]; + + s ++; + } + else if (*s == '\'' || *s == '\\') + { + if (bufptr >= (bufend - 1)) + break; + + *bufptr++ = '\\'; + *bufptr++ = *s++; + } + else + *bufptr++ = *s++; + } + + *bufptr = '\0'; + + return (bufptr); +} + + /* * 'user_allowed()' - See if a user is allowed to print to a queue. */ @@ -8814,6 +11190,8 @@ user_allowed(cupsd_printer_t *p, /* I - Printer or class */ { int i; /* Looping var */ struct passwd *pw; /* User password data */ + char baseuser[256], /* Base username */ + *baseptr; /* Pointer to "@" in base username */ if (p->num_users == 0) @@ -8822,6 +11200,20 @@ user_allowed(cupsd_printer_t *p, /* I - Printer or class */ if (!strcmp(username, "root")) return (1); + if (strchr(username, '@')) + { + /* + * Strip @REALM for username check... + */ + + strlcpy(baseuser, username, sizeof(baseuser)); + + if ((baseptr = strchr(baseuser, '@')) != NULL) + *baseptr = '\0'; + + username = baseuser; + } + pw = getpwnam(username); endpwent(); @@ -8836,6 +11228,15 @@ user_allowed(cupsd_printer_t *p, /* I - Printer or class */ if (cupsdCheckGroup(username, pw, p->users[i] + 1)) break; } + else if (p->users[i][0] == '#') + { + /* + * Check UUID... + */ + + if (cupsdCheckGroup(username, pw, p->users[i])) + break; + } else if (!strcasecmp(username, p->users[i])) break; } @@ -8855,16 +11256,7 @@ validate_job(cupsd_client_t *con, /* I - Client connection */ http_status_t status; /* Policy status */ ipp_attribute_t *attr; /* Current attribute */ ipp_attribute_t *format; /* Document-format attribute */ - cups_ptype_t dtype; /* Destination type (printer or class) */ - char method[HTTP_MAX_URI], - /* Method portion of URI */ - username[HTTP_MAX_URI], - /* Username portion of URI */ - host[HTTP_MAX_URI], - /* Host portion of URI */ - resource[HTTP_MAX_URI]; - /* Resource portion of URI */ - int port; /* Port portion of URI */ + cups_ptype_t dtype; /* Destination type (printer/class) */ char super[MIME_MAX_SUPER], /* Supertype of file */ type[MIME_MAX_TYPE]; @@ -8899,7 +11291,8 @@ validate_job(cupsd_client_t *con, /* I - Client connection */ if ((format = ippFindAttribute(con->request, "document-format", IPP_TAG_MIMETYPE)) != NULL) { - if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", super, type) != 2) + if (sscanf(format->values[0].string.text, "%15[^/]/%31[^;]", + super, type) != 2) { send_ipp_status(con, IPP_BAD_REQUEST, _("Bad document-format \"%s\"!"), format->values[0].string.text); @@ -8924,11 +11317,7 @@ validate_job(cupsd_client_t *con, /* I - Client connection */ * Is the destination valid? */ - httpSeparateURI(HTTP_URI_CODING_ALL, uri->values[0].string.text, method, - sizeof(method), username, sizeof(username), host, - sizeof(host), &port, resource, sizeof(resource)); - - if (cupsdValidateDest(host, resource, &dtype, &printer) == NULL) + if (!cupsdValidateDest(uri->values[0].string.text, &dtype, &printer)) { /* * Bad URI... @@ -8945,7 +11334,7 @@ validate_job(cupsd_client_t *con, /* I - Client connection */ if ((status = cupsdCheckPolicy(printer->op_policy_ptr, con, NULL)) != HTTP_OK) { - send_http_error(con, status); + send_http_error(con, status, printer); return; } @@ -8961,7 +11350,7 @@ validate_job(cupsd_client_t *con, /* I - Client connection */ * 'validate_name()' - Make sure the printer name only contains valid chars. */ -static int /* O - 0 if name is no good, 1 if name is good */ +static int /* O - 0 if name is no good, 1 if good */ validate_name(const char *name) /* I - Name to check */ { const char *ptr; /* Pointer into name */ @@ -8972,7 +11361,7 @@ validate_name(const char *name) /* I - Name to check */ */ for (ptr = name; *ptr; ptr ++) - if ((*ptr >= 0 && *ptr <= ' ') || *ptr == 127 || *ptr == '/' || *ptr == '#') + if ((*ptr > 0 && *ptr <= ' ') || *ptr == 127 || *ptr == '/' || *ptr == '#') return (0); /* @@ -8994,15 +11383,14 @@ validate_user(cupsd_job_t *job, /* I - Job */ char *username, /* O - Authenticated username */ int userlen) /* I - Length of username */ { - ipp_attribute_t *attr; /* requesting-user-name attribute */ cupsd_printer_t *printer; /* Printer for job */ cupsdLogMessage(CUPSD_LOG_DEBUG2, "validate_user(job=%d, con=%d, owner=\"%s\", username=%p, " "userlen=%d)", - job ? job->id : 0, con->http.fd, owner ? owner : "(null)", - username, userlen); + job->id, con ? con->http.fd : 0, + owner ? owner : "(null)", username, userlen); /* * Validate input... @@ -9015,22 +11403,13 @@ validate_user(cupsd_job_t *job, /* I - Job */ * Get the best authenticated username that is available. */ - if (con->username[0]) - strlcpy(username, con->username, userlen); - else if ((attr = ippFindAttribute(con->request, "requesting-user-name", - IPP_TAG_NAME)) != NULL) - strlcpy(username, attr->values[0].string.text, userlen); - else - strlcpy(username, "anonymous", userlen); + strlcpy(username, get_username(con), userlen); /* * Check the username against the owner... */ - if (job->dtype & CUPS_PRINTER_CLASS) - printer = cupsdFindClass(job->dest); - else - printer = cupsdFindPrinter(job->dest); + printer = cupsdFindDest(job->dest); return (cupsdCheckPolicy(printer ? printer->op_policy_ptr : DefaultPolicyPtr, con, owner) == HTTP_OK); @@ -9038,5 +11417,5 @@ validate_user(cupsd_job_t *job, /* I - Job */ /* - * End of "$Id: ipp.c 5023 2006-01-29 14:39:44Z mike $". + * End of "$Id: ipp.c 7944 2008-09-16 22:32:42Z mike $". */