2022-05-13 20:08:20 +08:00
|
|
|
/*
|
|
|
|
* Line Printer Daemon interface for CUPS.
|
|
|
|
*
|
2023-10-26 10:19:35 +08:00
|
|
|
* Copyright © 2021-2023 by OpenPrinting.
|
2023-01-11 16:57:48 +08:00
|
|
|
* Copyright © 2007-2016 by Apple Inc.
|
|
|
|
* Copyright © 1997-2006 by Easy Software Products, all rights reserved.
|
2022-05-13 20:08:20 +08:00
|
|
|
*
|
2023-01-11 16:57:48 +08:00
|
|
|
* Licensed under Apache License v2.0. See the file "LICENSE" for more
|
|
|
|
* information.
|
2022-05-13 20:08:20 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Include necessary headers...
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define _CUPS_NO_DEPRECATED
|
|
|
|
#include <cups/cups-private.h>
|
|
|
|
#include <syslog.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
|
|
|
|
#ifdef HAVE_INTTYPES_H
|
|
|
|
# include <inttypes.h>
|
|
|
|
#endif /* HAVE_INTTYPES_H */
|
|
|
|
#ifdef __APPLE__
|
|
|
|
# include <xpc/xpc.h>
|
|
|
|
#endif /* __APPLE__ */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* LPD "mini-daemon" for CUPS. This program must be used in conjunction
|
|
|
|
* with inetd or another similar program that monitors ports and starts
|
|
|
|
* daemons for each client connection. A typical configuration is:
|
|
|
|
*
|
|
|
|
* printer stream tcp nowait lp /usr/lib/cups/daemon/cups-lpd cups-lpd
|
|
|
|
*
|
|
|
|
* This daemon implements most of RFC 1179 (the unofficial LPD specification)
|
|
|
|
* except for:
|
|
|
|
*
|
|
|
|
* - This daemon does not check to make sure that the source port is
|
|
|
|
* between 721 and 731, since it isn't necessary for proper
|
|
|
|
* functioning and port-based security is no security at all!
|
|
|
|
*
|
|
|
|
* - The "Print any waiting jobs" command is a no-op.
|
|
|
|
*
|
|
|
|
* The LPD-to-IPP mapping is as defined in RFC 2569. The report formats
|
|
|
|
* currently match the Solaris LPD mini-daemon.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prototypes...
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int create_job(http_t *http, const char *dest, const char *title, const char *user, int num_options, cups_option_t *options);
|
|
|
|
static int get_printer(http_t *http, const char *name, char *dest,
|
|
|
|
size_t destsize, cups_option_t **options,
|
|
|
|
int *accepting, int *shared, ipp_pstate_t *state);
|
|
|
|
static int print_file(http_t *http, int id, const char *filename,
|
|
|
|
const char *docname, const char *user,
|
|
|
|
const char *format, int last);
|
|
|
|
static int recv_print_job(const char *name, int num_defaults,
|
|
|
|
cups_option_t *defaults);
|
|
|
|
static int remove_jobs(const char *name, const char *agent,
|
|
|
|
const char *list);
|
|
|
|
static int send_state(const char *name, const char *list,
|
|
|
|
int longstatus);
|
|
|
|
static char *smart_gets(char *s, int len, FILE *fp);
|
|
|
|
static void smart_strlcpy(char *dst, const char *src, size_t dstsize);
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'main()' - Process an incoming LPD request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
int /* O - Exit status */
|
|
|
|
main(int argc, /* I - Number of command-line arguments */
|
|
|
|
char *argv[]) /* I - Command-line arguments */
|
|
|
|
{
|
|
|
|
int i; /* Looping var */
|
|
|
|
int num_defaults; /* Number of default options */
|
|
|
|
cups_option_t *defaults; /* Default options */
|
|
|
|
char line[256], /* Command string */
|
|
|
|
command, /* Command code */
|
|
|
|
*dest, /* Pointer to destination */
|
|
|
|
*list, /* Pointer to list */
|
|
|
|
*agent, /* Pointer to user */
|
|
|
|
status; /* Status for client */
|
|
|
|
socklen_t hostlen; /* Size of client address */
|
|
|
|
http_addr_t hostaddr; /* Address of client */
|
|
|
|
char hostname[256], /* Name of client */
|
|
|
|
hostip[256], /* IP address */
|
|
|
|
*hostfamily; /* Address family */
|
|
|
|
int hostlookups; /* Do hostname lookups? */
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
xpc_transaction_begin();
|
|
|
|
#endif /* __APPLE__ */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Don't buffer the output...
|
|
|
|
*/
|
|
|
|
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log things using the "cups-lpd" name...
|
|
|
|
*/
|
|
|
|
|
|
|
|
openlog("cups-lpd", LOG_PID, LOG_LPR);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Scan the command-line for options...
|
|
|
|
*/
|
|
|
|
|
|
|
|
num_defaults = 0;
|
|
|
|
defaults = NULL;
|
|
|
|
hostlookups = 1;
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i ++)
|
|
|
|
if (argv[i][0] == '-')
|
|
|
|
{
|
|
|
|
switch (argv[i][1])
|
|
|
|
{
|
|
|
|
case 'h' : /* -h hostname[:port] */
|
|
|
|
if (argv[i][2])
|
|
|
|
cupsSetServer(argv[i] + 2);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
i ++;
|
|
|
|
if (i < argc)
|
|
|
|
cupsSetServer(argv[i]);
|
|
|
|
else
|
|
|
|
syslog(LOG_WARNING, "Expected hostname string after -h option!");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'o' : /* Option */
|
|
|
|
if (argv[i][2])
|
|
|
|
num_defaults = cupsParseOptions(argv[i] + 2, num_defaults,
|
|
|
|
&defaults);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
i ++;
|
|
|
|
if (i < argc)
|
|
|
|
num_defaults = cupsParseOptions(argv[i], num_defaults,
|
|
|
|
&defaults);
|
|
|
|
else
|
|
|
|
syslog(LOG_WARNING, "Expected option string after -o option!");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'n' : /* Don't do hostname lookups */
|
|
|
|
hostlookups = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default :
|
|
|
|
syslog(LOG_WARNING, "Unknown option \"%c\" ignored!", argv[i][1]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
syslog(LOG_WARNING, "Unknown command-line option \"%s\" ignored!",
|
|
|
|
argv[i]);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the address of the client...
|
|
|
|
*/
|
|
|
|
|
|
|
|
hostlen = sizeof(hostaddr);
|
|
|
|
|
|
|
|
if (getpeername(0, (struct sockaddr *)&hostaddr, &hostlen))
|
|
|
|
{
|
|
|
|
syslog(LOG_WARNING, "Unable to get client address - %s", strerror(errno));
|
|
|
|
strlcpy(hostname, "unknown", sizeof(hostname));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
httpAddrString(&hostaddr, hostip, sizeof(hostip));
|
|
|
|
|
|
|
|
if (hostlookups)
|
|
|
|
httpAddrLookup(&hostaddr, hostname, sizeof(hostname));
|
|
|
|
else
|
|
|
|
strlcpy(hostname, hostip, sizeof(hostname));
|
|
|
|
|
|
|
|
#ifdef AF_INET6
|
|
|
|
if (hostaddr.addr.sa_family == AF_INET6)
|
|
|
|
hostfamily = "IPv6";
|
|
|
|
else
|
|
|
|
#endif /* AF_INET6 */
|
|
|
|
hostfamily = "IPv4";
|
|
|
|
|
|
|
|
syslog(LOG_INFO, "Connection from %s (%s %s)", hostname, hostfamily,
|
|
|
|
hostip);
|
|
|
|
}
|
|
|
|
|
|
|
|
num_defaults = cupsAddOption("job-originating-host-name", hostname,
|
|
|
|
num_defaults, &defaults);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* RFC1179 specifies that only 1 daemon command can be received for
|
|
|
|
* every connection.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (smart_gets(line, sizeof(line), stdin) == NULL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Unable to get command from client! Send an error status and return.
|
|
|
|
*/
|
|
|
|
|
|
|
|
syslog(LOG_ERR, "Unable to get command line from client!");
|
|
|
|
putchar(1);
|
|
|
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
xpc_transaction_end();
|
|
|
|
#endif /* __APPLE__ */
|
|
|
|
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The first byte is the command byte. After that will be the queue name,
|
|
|
|
* resource list, and/or user name.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((command = line[0]) == '\0')
|
|
|
|
dest = line;
|
|
|
|
else
|
|
|
|
dest = line + 1;
|
|
|
|
|
|
|
|
if (command == 0x02)
|
|
|
|
list = NULL;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (list = dest; *list && !isspace(*list & 255); list ++);
|
|
|
|
|
|
|
|
while (isspace(*list & 255))
|
|
|
|
*list++ = '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the command...
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch (command)
|
|
|
|
{
|
|
|
|
default : /* Unknown command */
|
|
|
|
syslog(LOG_ERR, "Unknown LPD command 0x%02X!", command);
|
|
|
|
syslog(LOG_ERR, "Command line = %s", line + 1);
|
|
|
|
putchar(1);
|
|
|
|
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x01 : /* Print any waiting jobs */
|
|
|
|
syslog(LOG_INFO, "Print waiting jobs (no-op)");
|
|
|
|
putchar(0);
|
|
|
|
|
|
|
|
status = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x02 : /* Receive a printer job */
|
|
|
|
syslog(LOG_INFO, "Receive print job for %s", dest);
|
|
|
|
/* recv_print_job() sends initial status byte */
|
|
|
|
|
|
|
|
status = (char)recv_print_job(dest, num_defaults, defaults);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x03 : /* Send queue state (short) */
|
|
|
|
syslog(LOG_INFO, "Send queue state (short) for %s %s", dest, list);
|
|
|
|
/* no status byte for this command */
|
|
|
|
|
|
|
|
status = (char)send_state(dest, list, 0);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x04 : /* Send queue state (long) */
|
|
|
|
syslog(LOG_INFO, "Send queue state (long) for %s %s", dest, list);
|
|
|
|
/* no status byte for this command */
|
|
|
|
|
|
|
|
status = (char)send_state(dest, list, 1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x05 : /* Remove jobs */
|
2023-01-11 16:57:48 +08:00
|
|
|
/*
|
|
|
|
* Grab the agent and skip to the list of users and/or jobs.
|
|
|
|
*/
|
2022-05-13 20:08:20 +08:00
|
|
|
|
2023-01-11 16:57:48 +08:00
|
|
|
agent = list;
|
2022-05-13 20:08:20 +08:00
|
|
|
|
2023-01-11 16:57:48 +08:00
|
|
|
for (; *list && !isspace(*list & 255); list ++);
|
|
|
|
while (isspace(*list & 255))
|
|
|
|
*list++ = '\0';
|
2022-05-13 20:08:20 +08:00
|
|
|
|
2023-01-11 16:57:48 +08:00
|
|
|
syslog(LOG_INFO, "Remove jobs %s on %s by %s", list, dest, agent);
|
2022-05-13 20:08:20 +08:00
|
|
|
|
2023-01-11 16:57:48 +08:00
|
|
|
status = (char)remove_jobs(dest, agent, list);
|
2022-05-13 20:08:20 +08:00
|
|
|
|
|
|
|
putchar(status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
syslog(LOG_INFO, "Closing connection");
|
|
|
|
closelog();
|
|
|
|
|
|
|
|
#ifdef __APPLE__
|
|
|
|
xpc_transaction_end();
|
|
|
|
#endif /* __APPLE__ */
|
|
|
|
|
|
|
|
return (status);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'create_job()' - Create a new print job.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int /* O - Job ID or -1 on error */
|
|
|
|
create_job(http_t *http, /* I - HTTP connection */
|
|
|
|
const char *dest, /* I - Destination name */
|
|
|
|
const char *title, /* I - job-name */
|
|
|
|
const char *user, /* I - requesting-user-name */
|
|
|
|
int num_options, /* I - Number of options for job */
|
|
|
|
cups_option_t *options) /* I - Options for job */
|
|
|
|
{
|
|
|
|
ipp_t *request; /* IPP request */
|
|
|
|
ipp_t *response; /* IPP response */
|
|
|
|
ipp_attribute_t *attr; /* IPP attribute */
|
|
|
|
char uri[HTTP_MAX_URI]; /* Printer URI */
|
|
|
|
int id; /* Job ID */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup the Create-Job request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
request = ippNewRequest(IPP_OP_CREATE_JOB);
|
|
|
|
|
|
|
|
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
|
|
|
|
"localhost", 0, "/printers/%s", dest);
|
|
|
|
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
|
|
|
|
NULL, uri);
|
|
|
|
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
|
|
|
|
"requesting-user-name", NULL, user);
|
|
|
|
|
|
|
|
if (title[0])
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME, "job-name",
|
|
|
|
NULL, title);
|
|
|
|
|
|
|
|
cupsEncodeOptions(request, num_options, options);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
snprintf(uri, sizeof(uri), "/printers/%s", dest);
|
|
|
|
|
|
|
|
response = cupsDoRequest(http, request, uri);
|
|
|
|
|
|
|
|
if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to create job - %s", cupsLastErrorString());
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the job-id value from the response and return it...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((attr = ippFindAttribute(response, "job-id", IPP_TAG_INTEGER)) == NULL)
|
|
|
|
{
|
|
|
|
id = -1;
|
|
|
|
|
|
|
|
syslog(LOG_ERR, "No job-id attribute found in response from server!");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
id = attr->values[0].integer;
|
|
|
|
|
|
|
|
syslog(LOG_INFO, "Print file - job ID = %d", id);
|
|
|
|
}
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
|
|
|
|
return (id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'get_printer()' - Get the named printer and its options.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int /* O - Number of options or -1 on error */
|
|
|
|
get_printer(http_t *http, /* I - HTTP connection */
|
|
|
|
const char *name, /* I - Printer name from request */
|
|
|
|
char *dest, /* I - Destination buffer */
|
|
|
|
size_t destsize, /* I - Size of destination buffer */
|
|
|
|
cups_option_t **options, /* O - Printer options */
|
|
|
|
int *accepting, /* O - printer-is-accepting-jobs value */
|
|
|
|
int *shared, /* O - printer-is-shared value */
|
|
|
|
ipp_pstate_t *state) /* O - printer-state value */
|
|
|
|
{
|
|
|
|
int num_options; /* Number of options */
|
|
|
|
cups_file_t *fp; /* lpoptions file */
|
|
|
|
char line[1024], /* Line from lpoptions file */
|
|
|
|
*value, /* Pointer to value on line */
|
|
|
|
*optptr; /* Pointer to options on line */
|
|
|
|
int linenum; /* Line number in file */
|
|
|
|
const char *cups_serverroot; /* CUPS_SERVERROOT env var */
|
|
|
|
ipp_t *request; /* IPP request */
|
|
|
|
ipp_t *response; /* IPP response */
|
|
|
|
ipp_attribute_t *attr; /* IPP attribute */
|
|
|
|
char uri[HTTP_MAX_URI]; /* Printer URI */
|
|
|
|
static const char * const requested[] =
|
|
|
|
{ /* Requested attributes */
|
|
|
|
"printer-info",
|
|
|
|
"printer-is-accepting-jobs",
|
|
|
|
"printer-is-shared",
|
|
|
|
"printer-name",
|
|
|
|
"printer-state"
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Initialize everything...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (accepting)
|
|
|
|
*accepting = 0;
|
|
|
|
if (shared)
|
|
|
|
*shared = 0;
|
|
|
|
if (state)
|
|
|
|
*state = IPP_PSTATE_STOPPED;
|
|
|
|
if (options)
|
|
|
|
*options = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the name is a queue name optionally with an instance name.
|
|
|
|
*/
|
|
|
|
|
|
|
|
strlcpy(dest, name, destsize);
|
|
|
|
if ((value = strchr(dest, '/')) != NULL)
|
|
|
|
*value = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup the Get-Printer-Attributes request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
request = ippNewRequest(IPP_OP_GET_PRINTER_ATTRIBUTES);
|
|
|
|
|
|
|
|
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
|
|
|
|
"localhost", 0, "/printers/%s", dest);
|
|
|
|
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
|
|
|
|
NULL, uri);
|
|
|
|
|
|
|
|
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
|
|
|
|
"requested-attributes",
|
|
|
|
(int)(sizeof(requested) / sizeof(requested[0])),
|
|
|
|
NULL, requested);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
response = cupsDoRequest(http, request, "/");
|
|
|
|
|
|
|
|
if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If we can't find the printer by name, look up the printer-name
|
|
|
|
* using the printer-info values...
|
|
|
|
*/
|
|
|
|
|
|
|
|
ipp_attribute_t *accepting_attr,/* printer-is-accepting-jobs */
|
|
|
|
*info_attr, /* printer-info */
|
|
|
|
*name_attr, /* printer-name */
|
|
|
|
*shared_attr, /* printer-is-shared */
|
|
|
|
*state_attr; /* printer-state */
|
|
|
|
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup the CUPS-Get-Printers request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
request = ippNewRequest(IPP_OP_CUPS_GET_PRINTERS);
|
|
|
|
|
|
|
|
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
|
|
|
|
"requested-attributes",
|
|
|
|
(int)(sizeof(requested) / sizeof(requested[0])),
|
|
|
|
NULL, requested);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
response = cupsDoRequest(http, request, "/");
|
|
|
|
|
|
|
|
if (!response || cupsLastError() > IPP_STATUS_OK_CONFLICTING)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to get list of printers - %s",
|
|
|
|
cupsLastErrorString());
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Scan the response for printers...
|
|
|
|
*/
|
|
|
|
|
|
|
|
*dest = '\0';
|
|
|
|
attr = response->attrs;
|
|
|
|
|
|
|
|
while (attr)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Skip to the next printer...
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (attr && attr->group_tag != IPP_TAG_PRINTER)
|
|
|
|
attr = attr->next;
|
|
|
|
|
|
|
|
if (!attr)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get all of the attributes for the current printer...
|
|
|
|
*/
|
|
|
|
|
|
|
|
accepting_attr = NULL;
|
|
|
|
info_attr = NULL;
|
|
|
|
name_attr = NULL;
|
|
|
|
shared_attr = NULL;
|
|
|
|
state_attr = NULL;
|
|
|
|
|
|
|
|
while (attr && attr->group_tag == IPP_TAG_PRINTER)
|
|
|
|
{
|
|
|
|
if (!strcmp(attr->name, "printer-is-accepting-jobs") &&
|
|
|
|
attr->value_tag == IPP_TAG_BOOLEAN)
|
|
|
|
accepting_attr = attr;
|
|
|
|
else if (!strcmp(attr->name, "printer-info") &&
|
|
|
|
attr->value_tag == IPP_TAG_TEXT)
|
|
|
|
info_attr = attr;
|
|
|
|
else if (!strcmp(attr->name, "printer-name") &&
|
|
|
|
attr->value_tag == IPP_TAG_NAME)
|
|
|
|
name_attr = attr;
|
|
|
|
else if (!strcmp(attr->name, "printer-is-shared") &&
|
|
|
|
attr->value_tag == IPP_TAG_BOOLEAN)
|
|
|
|
shared_attr = attr;
|
|
|
|
else if (!strcmp(attr->name, "printer-state") &&
|
|
|
|
attr->value_tag == IPP_TAG_ENUM)
|
|
|
|
state_attr = attr;
|
|
|
|
|
|
|
|
attr = attr->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (info_attr && name_attr &&
|
|
|
|
!_cups_strcasecmp(name, info_attr->values[0].string.text))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Found a match, use this one!
|
|
|
|
*/
|
|
|
|
|
|
|
|
strlcpy(dest, name_attr->values[0].string.text, destsize);
|
|
|
|
|
|
|
|
if (accepting && accepting_attr)
|
|
|
|
*accepting = accepting_attr->values[0].boolean;
|
|
|
|
|
|
|
|
if (shared && shared_attr)
|
|
|
|
*shared = shared_attr->values[0].boolean;
|
|
|
|
|
|
|
|
if (state && state_attr)
|
|
|
|
*state = (ipp_pstate_t)state_attr->values[0].integer;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
|
|
|
|
if (!*dest)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to find \"%s\" in list of printers!", name);
|
|
|
|
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
name = dest;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Get values from the response...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (accepting)
|
|
|
|
{
|
|
|
|
if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs",
|
|
|
|
IPP_TAG_BOOLEAN)) == NULL)
|
|
|
|
syslog(LOG_ERR, "No printer-is-accepting-jobs attribute found in "
|
|
|
|
"response from server!");
|
|
|
|
else
|
|
|
|
*accepting = attr->values[0].boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shared)
|
|
|
|
{
|
|
|
|
if ((attr = ippFindAttribute(response, "printer-is-shared",
|
|
|
|
IPP_TAG_BOOLEAN)) == NULL)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "No printer-is-shared attribute found in "
|
|
|
|
"response from server!");
|
|
|
|
*shared = 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
*shared = attr->values[0].boolean;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (state)
|
|
|
|
{
|
|
|
|
if ((attr = ippFindAttribute(response, "printer-state",
|
|
|
|
IPP_TAG_ENUM)) == NULL)
|
|
|
|
syslog(LOG_ERR, "No printer-state attribute found in "
|
|
|
|
"response from server!");
|
|
|
|
else
|
|
|
|
*state = (ipp_pstate_t)attr->values[0].integer;
|
|
|
|
}
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Next look for the printer in the lpoptions file...
|
|
|
|
*/
|
|
|
|
|
|
|
|
num_options = 0;
|
|
|
|
|
|
|
|
if (options && shared && accepting)
|
|
|
|
{
|
|
|
|
if ((cups_serverroot = getenv("CUPS_SERVERROOT")) == NULL)
|
|
|
|
cups_serverroot = CUPS_SERVERROOT;
|
|
|
|
|
|
|
|
snprintf(line, sizeof(line), "%s/lpoptions", cups_serverroot);
|
|
|
|
if ((fp = cupsFileOpen(line, "r")) != NULL)
|
|
|
|
{
|
|
|
|
linenum = 0;
|
|
|
|
while (cupsFileGetConf(fp, line, sizeof(line), &value, &linenum))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Make sure we have "Dest name options" or "Default name options"...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((_cups_strcasecmp(line, "Dest") && _cups_strcasecmp(line, "Default")) || !value)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Separate destination name from options...
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (optptr = value; *optptr && !isspace(*optptr & 255); optptr ++);
|
|
|
|
|
|
|
|
while (*optptr == ' ')
|
|
|
|
*optptr++ = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If this is our destination, parse the options and break out of
|
|
|
|
* the loop - we're done!
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!_cups_strcasecmp(value, name))
|
|
|
|
{
|
|
|
|
num_options = cupsParseOptions(optptr, num_options, options);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cupsFileClose(fp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (options)
|
|
|
|
*options = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the number of options for this destination...
|
|
|
|
*/
|
|
|
|
|
|
|
|
return (num_options);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'print_file()' - Add a file to the current job.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int /* O - 0 on success, -1 on failure */
|
|
|
|
print_file(http_t *http, /* I - HTTP connection */
|
|
|
|
int id, /* I - Job ID */
|
|
|
|
const char *filename, /* I - File to print */
|
|
|
|
const char *docname, /* I - document-name */
|
|
|
|
const char *user, /* I - requesting-user-name */
|
|
|
|
const char *format, /* I - document-format */
|
|
|
|
int last) /* I - 1 = last file in job */
|
|
|
|
{
|
|
|
|
ipp_t *request; /* IPP request */
|
|
|
|
char uri[HTTP_MAX_URI]; /* Printer URI */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup the Send-Document request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
request = ippNewRequest(IPP_OP_SEND_DOCUMENT);
|
|
|
|
|
|
|
|
snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id);
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
|
|
|
|
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
|
|
|
|
"requesting-user-name", NULL, user);
|
|
|
|
|
|
|
|
if (docname)
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
|
|
|
|
"document-name", NULL, docname);
|
|
|
|
|
|
|
|
if (format)
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_MIMETYPE,
|
|
|
|
"document-format", NULL, format);
|
|
|
|
|
|
|
|
ippAddBoolean(request, IPP_TAG_OPERATION, "last-document", (char)last);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
snprintf(uri, sizeof(uri), "/jobs/%d", id);
|
|
|
|
|
|
|
|
ippDelete(cupsDoFileRequest(http, request, uri, filename));
|
|
|
|
|
|
|
|
if (cupsLastError() > IPP_STATUS_OK_CONFLICTING)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to send document - %s", cupsLastErrorString());
|
|
|
|
|
|
|
|
return (-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'recv_print_job()' - Receive a print job from the client.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int /* O - Command status */
|
|
|
|
recv_print_job(
|
|
|
|
const char *queue, /* I - Printer name */
|
|
|
|
int num_defaults, /* I - Number of default options */
|
|
|
|
cups_option_t *defaults) /* I - Default options */
|
|
|
|
{
|
|
|
|
http_t *http; /* HTTP connection */
|
|
|
|
int i; /* Looping var */
|
|
|
|
int status; /* Command status */
|
|
|
|
int fd; /* Temporary file */
|
|
|
|
FILE *fp; /* File pointer */
|
|
|
|
char filename[1024]; /* Temporary filename */
|
|
|
|
ssize_t bytes; /* Bytes received */
|
|
|
|
size_t total; /* Total bytes */
|
|
|
|
char line[256], /* Line from file/stdin */
|
|
|
|
command, /* Command from line */
|
|
|
|
*count, /* Number of bytes */
|
|
|
|
*name; /* Name of file */
|
|
|
|
const char *job_sheets; /* Job sheets */
|
|
|
|
int num_data; /* Number of data files */
|
|
|
|
char control[1024], /* Control filename */
|
|
|
|
data[100][256], /* Data files */
|
|
|
|
temp[100][1024]; /* Temporary files */
|
|
|
|
char user[1024], /* User name */
|
|
|
|
title[1024], /* Job title */
|
|
|
|
docname[1024], /* Document name */
|
|
|
|
dest[256]; /* Printer/class queue */
|
|
|
|
int accepting, /* printer-is-accepting */
|
|
|
|
shared, /* printer-is-shared */
|
|
|
|
num_options; /* Number of options */
|
|
|
|
cups_option_t *options; /* Options */
|
|
|
|
int id; /* Job ID */
|
|
|
|
int docnumber, /* Current document number */
|
|
|
|
doccount; /* Count of documents */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Connect to the server...
|
|
|
|
*/
|
|
|
|
|
|
|
|
http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL);
|
|
|
|
if (!http)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to connect to server: %s", strerror(errno));
|
|
|
|
|
|
|
|
putchar(1);
|
|
|
|
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if the printer is available...
|
|
|
|
*/
|
|
|
|
|
|
|
|
num_options = get_printer(http, queue, dest, sizeof(dest), &options,
|
|
|
|
&accepting, &shared, NULL);
|
|
|
|
|
|
|
|
if (num_options < 0 || !accepting || !shared)
|
|
|
|
{
|
|
|
|
if (dest[0])
|
|
|
|
syslog(LOG_INFO, "Rejecting job because \"%s\" is not %s", dest,
|
|
|
|
!accepting ? "accepting jobs" : "shared");
|
|
|
|
else
|
|
|
|
syslog(LOG_ERR, "Unable to get printer information for \"%s\"", queue);
|
|
|
|
|
|
|
|
httpClose(http);
|
|
|
|
|
|
|
|
putchar(1);
|
|
|
|
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
putchar(0); /* OK so far... */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the request...
|
|
|
|
*/
|
|
|
|
|
|
|
|
status = 0;
|
|
|
|
num_data = 0;
|
|
|
|
fd = -1;
|
|
|
|
|
|
|
|
control[0] = '\0';
|
|
|
|
|
|
|
|
while (smart_gets(line, sizeof(line), stdin) != NULL)
|
|
|
|
{
|
|
|
|
if (strlen(line) < 2)
|
|
|
|
{
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
command = line[0];
|
|
|
|
count = line + 1;
|
|
|
|
|
|
|
|
for (name = count + 1; *name && !isspace(*name & 255); name ++);
|
|
|
|
while (isspace(*name & 255))
|
|
|
|
*name++ = '\0';
|
|
|
|
|
|
|
|
switch (command)
|
|
|
|
{
|
|
|
|
default :
|
|
|
|
case 0x01 : /* Abort */
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x02 : /* Receive control file */
|
|
|
|
if (strlen(name) < 2)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Bad control file name \"%s\"", name);
|
|
|
|
putchar(1);
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (control[0])
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Append to the existing control file - the LPD spec is
|
|
|
|
* not entirely clear, but at least the OS/2 LPD code sends
|
|
|
|
* multiple control files per connection...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((fd = open(control, O_WRONLY)) < 0)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR,
|
|
|
|
"Unable to append to temporary control file \"%s\" - %s",
|
|
|
|
control, strerror(errno));
|
|
|
|
putchar(1);
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
lseek(fd, 0, SEEK_END);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ((fd = cupsTempFd(control, sizeof(control))) < 0)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to open temporary control file \"%s\" - %s",
|
|
|
|
control, strerror(errno));
|
|
|
|
putchar(1);
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
strlcpy(filename, control, sizeof(filename));
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x03 : /* Receive data file */
|
|
|
|
if (strlen(name) < 2)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Bad data file name \"%s\"", name);
|
|
|
|
putchar(1);
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_data >= (int)(sizeof(data) / sizeof(data[0])))
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Too many data files...
|
|
|
|
*/
|
|
|
|
|
|
|
|
syslog(LOG_ERR, "Too many data files (%d)", num_data);
|
|
|
|
putchar(1);
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
strlcpy(data[num_data], name, sizeof(data[0]));
|
|
|
|
|
|
|
|
if ((fd = cupsTempFd(temp[num_data], sizeof(temp[0]))) < 0)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to open temporary data file \"%s\" - %s",
|
|
|
|
temp[num_data], strerror(errno));
|
|
|
|
putchar(1);
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
strlcpy(filename, temp[num_data], sizeof(filename));
|
|
|
|
|
|
|
|
num_data ++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
putchar(status);
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Copy the data or control file from the client...
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (total = (size_t)strtoll(count, NULL, 10); total > 0; total -= (size_t)bytes)
|
|
|
|
{
|
|
|
|
if (total > sizeof(line))
|
|
|
|
bytes = (ssize_t)sizeof(line);
|
|
|
|
else
|
|
|
|
bytes = (ssize_t)total;
|
|
|
|
|
|
|
|
if ((bytes = (ssize_t)fread(line, 1, (size_t)bytes, stdin)) > 0)
|
|
|
|
bytes = write(fd, line, (size_t)bytes);
|
|
|
|
|
|
|
|
if (bytes < 1)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Error while reading file - %s",
|
|
|
|
strerror(errno));
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read trailing nul...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!status)
|
|
|
|
{
|
|
|
|
if (fread(line, 1, 1, stdin) < 1)
|
|
|
|
{
|
|
|
|
status = 1;
|
|
|
|
syslog(LOG_ERR, "Error while reading trailing nul - %s",
|
|
|
|
strerror(errno));
|
|
|
|
}
|
|
|
|
else if (line[0])
|
|
|
|
{
|
|
|
|
status = 1;
|
|
|
|
syslog(LOG_ERR, "Trailing character after file is not nul (%02X)!",
|
|
|
|
line[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close the file and send an acknowledgement...
|
|
|
|
*/
|
|
|
|
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
putchar(status);
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!status)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Process the control file and print stuff...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((fp = fopen(control, "rb")) == NULL)
|
|
|
|
status = 1;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Copy the default options...
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (i = 0; i < num_defaults; i ++)
|
|
|
|
num_options = cupsAddOption(defaults[i].name,
|
|
|
|
defaults[i].value,
|
|
|
|
num_options, &options);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Grab the job information...
|
|
|
|
*/
|
|
|
|
|
|
|
|
title[0] = '\0';
|
|
|
|
user[0] = '\0';
|
|
|
|
docname[0] = '\0';
|
|
|
|
doccount = 0;
|
|
|
|
|
|
|
|
while (smart_gets(line, sizeof(line), fp) != NULL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Process control lines...
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch (line[0])
|
|
|
|
{
|
|
|
|
case 'J' : /* Job name */
|
|
|
|
smart_strlcpy(title, line + 1, sizeof(title));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'N' : /* Document name */
|
|
|
|
smart_strlcpy(docname, line + 1, sizeof(docname));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'P' : /* User identification */
|
|
|
|
smart_strlcpy(user, line + 1, sizeof(user));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'L' : /* Print banner page */
|
|
|
|
/*
|
|
|
|
* If a banner was requested and it's not overridden by a
|
|
|
|
* command line option and the destination's default is none
|
|
|
|
* then add the standard banner...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (cupsGetOption("job-sheets", num_defaults, defaults) == NULL &&
|
|
|
|
((job_sheets = cupsGetOption("job-sheets", num_options,
|
|
|
|
options)) == NULL ||
|
|
|
|
!strcmp(job_sheets, "none,none")))
|
|
|
|
{
|
|
|
|
num_options = cupsAddOption("job-sheets", "standard",
|
|
|
|
num_options, &options);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'c' : /* Plot CIF file */
|
|
|
|
case 'd' : /* Print DVI file */
|
|
|
|
case 'f' : /* Print formatted file */
|
|
|
|
case 'g' : /* Plot file */
|
|
|
|
case 'l' : /* Print file leaving control characters (raw) */
|
|
|
|
case 'n' : /* Print ditroff output file */
|
|
|
|
case 'o' : /* Print PostScript output file */
|
|
|
|
case 'p' : /* Print file with 'pr' format (prettyprint) */
|
|
|
|
case 'r' : /* File to print with FORTRAN carriage control */
|
|
|
|
case 't' : /* Print troff output file */
|
|
|
|
case 'v' : /* Print raster file */
|
|
|
|
doccount ++;
|
|
|
|
|
|
|
|
if (line[0] == 'l' &&
|
|
|
|
!cupsGetOption("document-format", num_options, options))
|
|
|
|
num_options = cupsAddOption("raw", "", num_options, &options);
|
|
|
|
|
|
|
|
if (line[0] == 'p')
|
|
|
|
num_options = cupsAddOption("prettyprint", "", num_options,
|
|
|
|
&options);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check that we have a username...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!user[0])
|
|
|
|
{
|
|
|
|
syslog(LOG_WARNING, "No username specified by client! "
|
|
|
|
"Using \"anonymous\"...");
|
|
|
|
strlcpy(user, "anonymous", sizeof(user));
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Create the job...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((id = create_job(http, dest, title, user, num_options, options)) < 0)
|
|
|
|
status = 1;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Then print the job files...
|
|
|
|
*/
|
|
|
|
|
|
|
|
rewind(fp);
|
|
|
|
|
|
|
|
docname[0] = '\0';
|
|
|
|
docnumber = 0;
|
|
|
|
|
|
|
|
while (smart_gets(line, sizeof(line), fp) != NULL)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Process control lines...
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch (line[0])
|
|
|
|
{
|
|
|
|
case 'N' : /* Document name */
|
|
|
|
smart_strlcpy(docname, line + 1, sizeof(docname));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'c' : /* Plot CIF file */
|
|
|
|
case 'd' : /* Print DVI file */
|
|
|
|
case 'f' : /* Print formatted file */
|
|
|
|
case 'g' : /* Plot file */
|
|
|
|
case 'l' : /* Print file leaving control characters (raw) */
|
|
|
|
case 'n' : /* Print ditroff output file */
|
|
|
|
case 'o' : /* Print PostScript output file */
|
|
|
|
case 'p' : /* Print file with 'pr' format (prettyprint) */
|
|
|
|
case 'r' : /* File to print with FORTRAN carriage control */
|
|
|
|
case 't' : /* Print troff output file */
|
|
|
|
case 'v' : /* Print raster file */
|
|
|
|
/*
|
|
|
|
* Figure out which file we are printing...
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (i = 0; i < num_data; i ++)
|
|
|
|
if (!strcmp(data[i], line + 1))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (i >= num_data)
|
|
|
|
{
|
|
|
|
status = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send the print file...
|
|
|
|
*/
|
|
|
|
|
|
|
|
docnumber ++;
|
|
|
|
|
|
|
|
if (print_file(http, id, temp[i], docname, user,
|
|
|
|
cupsGetOption("document-format", num_options,
|
|
|
|
options),
|
|
|
|
docnumber == doccount))
|
|
|
|
status = 1;
|
|
|
|
else
|
|
|
|
status = 0;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cupsFreeOptions(num_options, options);
|
|
|
|
|
|
|
|
httpClose(http);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clean up all temporary files and return...
|
|
|
|
*/
|
|
|
|
|
|
|
|
unlink(control);
|
|
|
|
|
|
|
|
for (i = 0; i < num_data; i ++)
|
|
|
|
unlink(temp[i]);
|
|
|
|
|
|
|
|
return (status);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'remove_jobs()' - Cancel one or more jobs.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int /* O - Command status */
|
|
|
|
remove_jobs(const char *dest, /* I - Destination */
|
|
|
|
const char *agent, /* I - User agent */
|
|
|
|
const char *list) /* I - List of jobs or users */
|
|
|
|
{
|
|
|
|
int id; /* Job ID */
|
|
|
|
http_t *http; /* HTTP server connection */
|
|
|
|
ipp_t *request; /* IPP Request */
|
|
|
|
char uri[HTTP_MAX_URI]; /* Job URI */
|
|
|
|
|
|
|
|
|
|
|
|
(void)dest; /* Suppress compiler warnings... */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try connecting to the local server...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(),
|
|
|
|
strerror(errno));
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loop for each job...
|
|
|
|
*/
|
|
|
|
|
|
|
|
while ((id = atoi(list)) > 0)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Skip job ID in list...
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (isdigit(*list & 255))
|
|
|
|
list ++;
|
|
|
|
while (isspace(*list & 255))
|
|
|
|
list ++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build an IPP_OP_CANCEL_JOB request, which requires the following
|
|
|
|
* attributes:
|
|
|
|
*
|
|
|
|
* attributes-charset
|
|
|
|
* attributes-natural-language
|
|
|
|
* job-uri
|
|
|
|
* requesting-user-name
|
|
|
|
*/
|
|
|
|
|
|
|
|
request = ippNewRequest(IPP_OP_CANCEL_JOB);
|
|
|
|
|
2023-01-11 16:57:48 +08:00
|
|
|
snprintf(uri, sizeof(uri), "ipp://localhost/jobs/%d", id);
|
2022-05-13 20:08:20 +08:00
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "job-uri", NULL, uri);
|
|
|
|
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
|
|
|
|
"requesting-user-name", NULL, agent);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the request and get back a response...
|
|
|
|
*/
|
|
|
|
|
|
|
|
ippDelete(cupsDoRequest(http, request, "/jobs"));
|
|
|
|
|
|
|
|
if (cupsLastError() > IPP_STATUS_OK_CONFLICTING)
|
|
|
|
{
|
|
|
|
syslog(LOG_WARNING, "Cancel of job ID %d failed: %s\n", id,
|
|
|
|
cupsLastErrorString());
|
|
|
|
httpClose(http);
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
syslog(LOG_INFO, "Job ID %d canceled", id);
|
|
|
|
}
|
|
|
|
|
|
|
|
httpClose(http);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'send_state()' - Send the queue state.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int /* O - Command status */
|
|
|
|
send_state(const char *queue, /* I - Destination */
|
|
|
|
const char *list, /* I - Job or user */
|
|
|
|
int longstatus) /* I - List of jobs or users */
|
|
|
|
{
|
|
|
|
int id; /* Job ID from list */
|
|
|
|
http_t *http; /* HTTP server connection */
|
|
|
|
ipp_t *request, /* IPP Request */
|
|
|
|
*response; /* IPP Response */
|
|
|
|
ipp_attribute_t *attr; /* Current attribute */
|
|
|
|
ipp_pstate_t state; /* Printer state */
|
|
|
|
const char *jobdest, /* Pointer into job-printer-uri */
|
|
|
|
*jobuser, /* Pointer to job-originating-user-name */
|
|
|
|
*jobname; /* Pointer to job-name */
|
|
|
|
ipp_jstate_t jobstate; /* job-state */
|
|
|
|
int jobid, /* job-id */
|
|
|
|
jobsize, /* job-k-octets */
|
|
|
|
jobcount, /* Number of jobs */
|
|
|
|
jobcopies, /* Number of copies */
|
|
|
|
rank; /* Rank of job */
|
|
|
|
char rankstr[255]; /* Rank string */
|
|
|
|
char namestr[1024]; /* Job name string */
|
|
|
|
char uri[HTTP_MAX_URI]; /* Printer URI */
|
|
|
|
char dest[256]; /* Printer/class queue */
|
|
|
|
static const char * const ranks[10] = /* Ranking strings */
|
|
|
|
{
|
|
|
|
"th",
|
|
|
|
"st",
|
|
|
|
"nd",
|
|
|
|
"rd",
|
|
|
|
"th",
|
|
|
|
"th",
|
|
|
|
"th",
|
|
|
|
"th",
|
|
|
|
"th",
|
|
|
|
"th"
|
|
|
|
};
|
|
|
|
static const char * const requested[] =
|
|
|
|
{ /* Requested attributes */
|
|
|
|
"job-id",
|
|
|
|
"job-k-octets",
|
|
|
|
"job-state",
|
|
|
|
"job-printer-uri",
|
|
|
|
"job-originating-user-name",
|
|
|
|
"job-name",
|
|
|
|
"copies"
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Try connecting to the local server...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((http = httpConnect2(cupsServer(), ippPort(), NULL, AF_UNSPEC, cupsEncryption(), 1, 30000, NULL)) == NULL)
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to connect to server %s: %s", cupsServer(),
|
|
|
|
strerror(errno));
|
|
|
|
printf("Unable to connect to server %s: %s", cupsServer(), strerror(errno));
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get the actual destination name and printer state...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (get_printer(http, queue, dest, sizeof(dest), NULL, NULL, NULL, &state))
|
|
|
|
{
|
|
|
|
syslog(LOG_ERR, "Unable to get printer %s: %s", queue,
|
|
|
|
cupsLastErrorString());
|
|
|
|
printf("Unable to get printer %s: %s", queue, cupsLastErrorString());
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show the queue state...
|
|
|
|
*/
|
|
|
|
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
case IPP_PSTATE_IDLE :
|
|
|
|
printf("%s is ready\n", dest);
|
|
|
|
break;
|
|
|
|
case IPP_PSTATE_PROCESSING :
|
|
|
|
printf("%s is ready and printing\n", dest);
|
|
|
|
break;
|
|
|
|
case IPP_PSTATE_STOPPED :
|
|
|
|
printf("%s is not ready\n", dest);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Build an IPP_OP_GET_JOBS or IPP_OP_GET_JOB_ATTRIBUTES request, which requires
|
|
|
|
* the following attributes:
|
|
|
|
*
|
|
|
|
* attributes-charset
|
|
|
|
* attributes-natural-language
|
|
|
|
* job-uri or printer-uri
|
|
|
|
*/
|
|
|
|
|
|
|
|
id = atoi(list);
|
|
|
|
|
|
|
|
request = ippNewRequest(id ? IPP_OP_GET_JOB_ATTRIBUTES : IPP_OP_GET_JOBS);
|
|
|
|
|
|
|
|
httpAssembleURIf(HTTP_URI_CODING_ALL, uri, sizeof(uri), "ipp", NULL,
|
|
|
|
"localhost", 0, "/printers/%s", dest);
|
|
|
|
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI, "printer-uri",
|
|
|
|
NULL, uri);
|
|
|
|
|
|
|
|
if (id)
|
|
|
|
ippAddInteger(request, IPP_TAG_OPERATION, IPP_TAG_INTEGER, "job-id", id);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
|
|
|
|
"requesting-user-name", NULL, list);
|
|
|
|
ippAddBoolean(request, IPP_TAG_OPERATION, "my-jobs", 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_KEYWORD,
|
|
|
|
"requested-attributes",
|
|
|
|
sizeof(requested) / sizeof(requested[0]),
|
|
|
|
NULL, requested);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Do the request and get back a response...
|
|
|
|
*/
|
|
|
|
|
|
|
|
jobcount = 0;
|
|
|
|
response = cupsDoRequest(http, request, "/");
|
|
|
|
|
|
|
|
if (cupsLastError() > IPP_STATUS_OK_CONFLICTING)
|
|
|
|
{
|
|
|
|
printf("get-jobs failed: %s\n", cupsLastErrorString());
|
|
|
|
ippDelete(response);
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Loop through the job list and display them...
|
|
|
|
*/
|
|
|
|
|
|
|
|
for (attr = response->attrs, rank = 1; attr; attr = attr->next)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Skip leading attributes until we hit a job...
|
|
|
|
*/
|
|
|
|
|
|
|
|
while (attr && (attr->group_tag != IPP_TAG_JOB || !attr->name))
|
|
|
|
attr = attr->next;
|
|
|
|
|
|
|
|
if (!attr)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pull the needed attributes from this job...
|
|
|
|
*/
|
|
|
|
|
|
|
|
jobid = 0;
|
|
|
|
jobsize = 0;
|
|
|
|
jobstate = IPP_JSTATE_PENDING;
|
|
|
|
jobname = "untitled";
|
|
|
|
jobuser = NULL;
|
|
|
|
jobdest = NULL;
|
|
|
|
jobcopies = 1;
|
|
|
|
|
|
|
|
while (attr && attr->group_tag == IPP_TAG_JOB)
|
|
|
|
{
|
|
|
|
if (!strcmp(attr->name, "job-id") &&
|
|
|
|
attr->value_tag == IPP_TAG_INTEGER)
|
|
|
|
jobid = attr->values[0].integer;
|
|
|
|
|
|
|
|
if (!strcmp(attr->name, "job-k-octets") &&
|
|
|
|
attr->value_tag == IPP_TAG_INTEGER)
|
|
|
|
jobsize = attr->values[0].integer;
|
|
|
|
|
|
|
|
if (!strcmp(attr->name, "job-state") &&
|
|
|
|
attr->value_tag == IPP_TAG_ENUM)
|
|
|
|
jobstate = (ipp_jstate_t)attr->values[0].integer;
|
|
|
|
|
|
|
|
if (!strcmp(attr->name, "job-printer-uri") &&
|
|
|
|
attr->value_tag == IPP_TAG_URI)
|
|
|
|
if ((jobdest = strrchr(attr->values[0].string.text, '/')) != NULL)
|
|
|
|
jobdest ++;
|
|
|
|
|
|
|
|
if (!strcmp(attr->name, "job-originating-user-name") &&
|
|
|
|
attr->value_tag == IPP_TAG_NAME)
|
|
|
|
jobuser = attr->values[0].string.text;
|
|
|
|
|
|
|
|
if (!strcmp(attr->name, "job-name") &&
|
|
|
|
attr->value_tag == IPP_TAG_NAME)
|
|
|
|
jobname = attr->values[0].string.text;
|
|
|
|
|
|
|
|
if (!strcmp(attr->name, "copies") &&
|
|
|
|
attr->value_tag == IPP_TAG_INTEGER)
|
|
|
|
jobcopies = attr->values[0].integer;
|
|
|
|
|
|
|
|
attr = attr->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* See if we have everything needed...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!jobdest || !jobid)
|
|
|
|
{
|
|
|
|
if (!attr)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!longstatus && jobcount == 0)
|
|
|
|
puts("Rank Owner Job File(s) Total Size");
|
|
|
|
|
|
|
|
jobcount ++;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Display the job...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (jobstate == IPP_JSTATE_PROCESSING)
|
|
|
|
strlcpy(rankstr, "active", sizeof(rankstr));
|
|
|
|
else
|
|
|
|
{
|
|
|
|
snprintf(rankstr, sizeof(rankstr), "%d%s", rank, ranks[rank % 10]);
|
|
|
|
rank ++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (longstatus)
|
|
|
|
{
|
|
|
|
puts("");
|
|
|
|
|
|
|
|
if (jobcopies > 1)
|
|
|
|
snprintf(namestr, sizeof(namestr), "%d copies of %s", jobcopies,
|
|
|
|
jobname);
|
|
|
|
else
|
|
|
|
strlcpy(namestr, jobname, sizeof(namestr));
|
|
|
|
|
|
|
|
printf("%s: %-33.33s [job %d localhost]\n", jobuser, rankstr, jobid);
|
|
|
|
printf(" %-39.39s %.0f bytes\n", namestr, 1024.0 * jobsize);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
printf("%-7s %-7.7s %-7d %-31.31s %.0f bytes\n", rankstr, jobuser,
|
|
|
|
jobid, jobname, 1024.0 * jobsize);
|
|
|
|
|
|
|
|
if (!attr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
ippDelete(response);
|
|
|
|
|
|
|
|
if (jobcount == 0)
|
|
|
|
puts("no entries");
|
|
|
|
|
|
|
|
httpClose(http);
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'smart_gets()' - Get a line of text, removing the trailing CR and/or LF.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static char * /* O - Line read or NULL */
|
|
|
|
smart_gets(char *s, /* I - Pointer to line buffer */
|
|
|
|
int len, /* I - Size of line buffer */
|
|
|
|
FILE *fp) /* I - File to read from */
|
|
|
|
{
|
|
|
|
char *ptr, /* Pointer into line */
|
|
|
|
*end; /* End of line */
|
|
|
|
int ch; /* Character from file */
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read the line; unlike fgets(), we read the entire line but dump
|
|
|
|
* characters that go past the end of the buffer. Also, we accept
|
|
|
|
* CR, LF, or CR LF for the line endings to be "safe", although
|
|
|
|
* RFC 1179 specifically says "just use LF".
|
|
|
|
*/
|
|
|
|
|
|
|
|
ptr = s;
|
|
|
|
end = s + len - 1;
|
|
|
|
|
|
|
|
while ((ch = getc(fp)) != EOF)
|
|
|
|
{
|
|
|
|
if (ch == '\n')
|
|
|
|
break;
|
|
|
|
else if (ch == '\r')
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* See if a LF follows...
|
|
|
|
*/
|
|
|
|
|
|
|
|
ch = getc(fp);
|
|
|
|
|
|
|
|
if (ch != '\n')
|
|
|
|
ungetc(ch, fp);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (ptr < end)
|
|
|
|
*ptr++ = (char)ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
*ptr = '\0';
|
|
|
|
|
|
|
|
if (ch == EOF && ptr == s)
|
|
|
|
return (NULL);
|
|
|
|
else
|
|
|
|
return (s);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 'smart_strlcpy()' - Copy a string and convert from ISO-8859-1 to UTF-8 as needed.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
smart_strlcpy(char *dst, /* I - Output buffer */
|
|
|
|
const char *src, /* I - Input string */
|
|
|
|
size_t dstsize) /* I - Size of output buffer */
|
|
|
|
{
|
|
|
|
const unsigned char *srcptr; /* Pointer into input string */
|
|
|
|
unsigned char *dstptr, /* Pointer into output buffer */
|
|
|
|
*dstend; /* End of output buffer */
|
|
|
|
int saw_8859 = 0; /* Saw an extended character that was not UTF-8? */
|
|
|
|
|
|
|
|
|
|
|
|
for (srcptr = (unsigned char *)src, dstptr = (unsigned char *)dst, dstend = dstptr + dstsize - 1; *srcptr;)
|
|
|
|
{
|
|
|
|
if (*srcptr < 0x80)
|
|
|
|
*dstptr++ = *srcptr++; /* ASCII */
|
|
|
|
else if (saw_8859)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Map ISO-8859-1 (most likely character set for legacy LPD clients) to
|
|
|
|
* UTF-8...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (dstptr > (dstend - 2))
|
|
|
|
break;
|
|
|
|
|
|
|
|
*dstptr++ = 0xc0 | (*srcptr >> 6);
|
|
|
|
*dstptr++ = 0x80 | (*srcptr++ & 0x3f);
|
|
|
|
}
|
|
|
|
else if ((*srcptr & 0xe0) == 0xc0 && (srcptr[1] & 0xc0) == 0x80)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 2-byte UTF-8 sequence...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (dstptr > (dstend - 2))
|
|
|
|
break;
|
|
|
|
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
}
|
|
|
|
else if ((*srcptr & 0xf0) == 0xe0 && (srcptr[1] & 0xc0) == 0x80 && (srcptr[2] & 0xc0) == 0x80)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 3-byte UTF-8 sequence...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (dstptr > (dstend - 3))
|
|
|
|
break;
|
|
|
|
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
}
|
|
|
|
else if ((*srcptr & 0xf8) == 0xf0 && (srcptr[1] & 0xc0) == 0x80 && (srcptr[2] & 0xc0) == 0x80 && (srcptr[3] & 0xc0) == 0x80)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* 4-byte UTF-8 sequence...
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (dstptr > (dstend - 4))
|
|
|
|
break;
|
|
|
|
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
*dstptr++ = *srcptr++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Bad UTF-8 sequence, this must be an ISO-8859-1 string...
|
|
|
|
*/
|
|
|
|
|
|
|
|
saw_8859 = 1;
|
|
|
|
|
|
|
|
if (dstptr > (dstend - 2))
|
|
|
|
break;
|
|
|
|
|
|
|
|
*dstptr++ = 0xc0 | (*srcptr >> 6);
|
|
|
|
*dstptr++ = 0x80 | (*srcptr++ & 0x3f);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
*dstptr = '\0';
|
|
|
|
}
|