1/*
2 * $Id: print_cups.c,v 1.6 2010-01-26 20:43:11 didg Exp $
3 *
4 * Copyright 2004 Bjoern Fernhomberg.
5 *
6 * Some code copied or adapted from print_cups.c for samba
7 * Copyright 1999-2003 by Michael R Sweet.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24#ifdef HAVE_CONFIG_H
25#include "config.h"
26#endif /* HAVE_CONFIG_H */
27
28#include <ctype.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#ifdef HAVE_UNISTD_H
33#include <unistd.h>
34#endif /* HAVE_UNISTD_H */
35#include <sys/types.h>
36#include <sys/param.h>
37#include <errno.h>
38
39
40#ifdef HAVE_CUPS
41
42#include <cups/cups.h>
43#include <cups/language.h>
44#include <atalk/unicode.h>
45#include <atalk/logger.h>
46#include <atalk/atp.h>
47#include <atalk/pap.h>
48#include <atalk/util.h>
49
50#include "printer.h"
51#include "print_cups.h"
52
53#define MAXCHOOSERLEN 31
54#define HTTP_MAX_URI 1024
55
56static const char* cups_status_msg[] = {
57        "status: busy; info: \"%s\" is rejecting jobs; ",
58        "status: idle; info: \"%s\" is stopped, accepting jobs ;",
59        "status: idle; info: \"%s\" is ready ; ",
60};
61
62/* Local functions */
63static int 	convert_to_mac_name ( const char *encoding, char * inptr, char * outptr, size_t outlen);
64static size_t	to_ascii ( char *inbuf, char **outbuf);
65static int 	cups_mangle_printer_name ( struct printer *pr, struct printer *printers);
66static void     cups_free_printer ( struct printer *pr);
67
68
69const char * cups_get_language (void)
70{
71        cups_lang_t *language;
72
73        language = cupsLangDefault();           /* needed for conversion */
74        return cupsLangEncoding(language);
75}
76
77/*
78 * 'cups_passwd_cb()' - The CUPS password callback...
79 */
80
81static const char *                            /* O - Password or NULL */
82cups_passwd_cb(const char *prompt _U_)      /* I - Prompt */
83{
84 /*
85  * Always return NULL to indicate that no password is available...
86  */
87  return (NULL);
88}
89
90
91/*
92 * 'cups_printername_ok()' - Verify supplied printer name is a valid cups printer
93 */
94
95int                                     /* O - 1 if printer name OK */
96cups_printername_ok(char *name)         /* I - Name of printer */
97{
98        http_t          *http;          /* HTTP connection to server */
99        ipp_t           *request,       /* IPP Request */
100                        *response;      /* IPP Response */
101        cups_lang_t     *language;      /* Default language */
102        char            uri[HTTP_MAX_URI]; /* printer-uri attribute */
103
104       /*
105        * Make sure we don't ask for passwords...
106        */
107
108        cupsSetPasswordCB(cups_passwd_cb);
109
110       /*
111        * Try to connect to the server...
112        */
113
114        if ((http = httpConnect(cupsServer(), ippPort())) == NULL)
115        {
116		LOG(log_error, logtype_papd, "Unable to connect to CUPS server %s - %s",
117                         cupsServer(), strerror(errno));
118                return (0);
119        }
120
121
122       /*
123        * Build an IPP_GET_PRINTER_ATTRS request, which requires the following
124        * attributes:
125        *
126        *    attributes-charset
127        *    attributes-natural-language
128        *    requested-attributes
129        *    printer-uri
130        */
131
132        request = ippNew();
133
134        request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES;
135        request->request.op.request_id   = 1;
136
137        language = cupsLangDefault();
138
139        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
140                     "attributes-charset", NULL, cupsLangEncoding(language));
141
142        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
143                     "attributes-natural-language", NULL, language->language);
144
145        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
146                     "requested-attributes", NULL, "printer-uri");
147
148        sprintf(uri, "ipp://localhost/printers/%s", name);
149
150        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
151                     "printer-uri", NULL, uri);
152
153       /*
154        * Do the request and get back a response...
155        */
156
157        if ((response = cupsDoRequest(http, request, "/")) == NULL)
158        {
159      		LOG(log_error, logtype_papd, "Unable to get printer status for %s - %s", name,
160                         ippErrorString(cupsLastError()));
161                httpClose(http);
162                return (0);
163        }
164
165        httpClose(http);
166
167        if (response->request.status.status_code >= IPP_OK_CONFLICT)
168        {
169      		LOG(log_error, logtype_papd, "Unable to get printer status for %s - %s", name,
170                         ippErrorString(response->request.status.status_code));
171                ippDelete(response);
172                return (0);
173        }
174        else
175        {
176                ippDelete(response);
177                return (1);
178        }
179
180	return (0);
181}
182
183const char *
184cups_get_printer_ppd ( char * name)
185{
186	cupsSetPasswordCB(cups_passwd_cb);
187	return cupsGetPPD( name );
188}
189
190int
191cups_get_printer_status (struct printer *pr)
192{
193
194        http_t          *http;          /* HTTP connection to server */
195        ipp_t           *request,       /* IPP Request */
196                        *response;      /* IPP Response */
197        ipp_attribute_t *attr;          /* Current attribute */
198        cups_lang_t     *language;      /* Default language */
199        char            uri[HTTP_MAX_URI]; /* printer-uri attribute */
200	int 		status = -1;
201
202        static const char *pattrs[] =   /* Requested printer attributes */
203                        {
204                          "printer-state",
205                          "printer-state-message",
206			  "printer-is-accepting-jobs"
207                        };
208
209       /*
210        * Make sure we don't ask for passwords...
211        */
212
213        cupsSetPasswordCB(cups_passwd_cb);
214
215       /*
216        * Try to connect to the server...
217        */
218
219        if ((http = httpConnect(cupsServer(), ippPort())) == NULL)
220        {
221		LOG(log_error, logtype_papd, "Unable to connect to CUPS server %s - %s",
222                         cupsServer(), strerror(errno));
223                return (0);
224        }
225
226       /*
227        * Generate the printer URI...
228        */
229
230        sprintf(uri, "ipp://localhost/printers/%s", pr->p_printer);
231
232
233
234       /*
235        * Build an IPP_GET_PRINTER_ATTRIBUTES request, which requires the
236        * following attributes:
237        *
238        *    attributes-charset
239        *    attributes-natural-language
240        *    requested-attributes
241        *    printer-uri
242        */
243
244        request = ippNew();
245
246        request->request.op.operation_id = IPP_GET_PRINTER_ATTRIBUTES;
247        request->request.op.request_id   = 1;
248
249        language = cupsLangDefault();
250
251        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
252                     "attributes-charset", NULL, cupsLangEncoding(language));
253
254        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
255                     "attributes-natural-language", NULL, language->language);
256
257        ippAddStrings(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
258                      "requested-attributes",
259                      (sizeof(pattrs) / sizeof(pattrs[0])),
260                      NULL, pattrs);
261
262        ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
263                     "printer-uri", NULL, uri);
264
265       /*
266        * Do the request and get back a response...
267        */
268
269        if ((response = cupsDoRequest(http, request, "/")) == NULL)
270        {
271      		LOG(log_error, logtype_papd, "Unable to get printer status for %s - %s", pr->p_printer,
272                         ippErrorString(cupsLastError()));
273                httpClose(http);
274                return (0);
275        }
276
277        if (response->request.status.status_code >= IPP_OK_CONFLICT)
278        {
279      		LOG(log_error, logtype_papd, "Unable to get printer status for %s - %s", pr->p_printer,
280                         ippErrorString(response->request.status.status_code));
281                ippDelete(response);
282                httpClose(http);
283                return (0);
284        }
285
286       /*
287        * Get the current printer status and convert it to the status values.
288        */
289
290	memset ( pr->p_status, 0 ,sizeof(pr->p_status));
291
292        if ((attr = ippFindAttribute(response, "printer-state", IPP_TAG_ENUM)) != NULL)
293        {
294                if (attr->values[0].integer == IPP_PRINTER_STOPPED)
295			status = 1;
296                else if (attr->values[0].integer == IPP_NOT_ACCEPTING)
297			status = 0;
298		else
299			status = 2;
300        }
301
302	if ((attr = ippFindAttribute(response, "printer-is-accepting-jobs", IPP_TAG_BOOLEAN)) != NULL)
303	{
304		if ( attr->values[0].integer == 0 )
305			status = 0;
306	}
307
308	snprintf ( pr->p_status, 255, cups_status_msg[status], pr->p_printer );
309
310        if ((attr = ippFindAttribute(response, "printer-state-message", IPP_TAG_TEXT)) != NULL)
311		strncat ( pr->p_status, attr->values[0].string.text, 255-strlen(pr->p_status));
312
313        ippDelete(response);
314
315       /*
316        * Return the print status ...
317        */
318
319        httpClose(http);
320
321	return (status);
322}
323
324
325/*------------------------------------------------------------------------*/
326
327/* pass the job to cups */
328
329int cups_print_job ( char * name, char *filename, char *job, char *username, char * cupsoptions )
330{
331	int 		jobid;
332	char 		filepath[MAXPATHLEN];
333	int           	num_options;
334	cups_option_t 	*options;
335
336	/* Initialize the options array */
337	num_options = 0;
338	options     = (cups_option_t *)0;
339
340        cupsSetPasswordCB(cups_passwd_cb);
341
342	if ( username != NULL )
343	{
344		/* Add options using cupsAddOption() */
345		num_options = cupsAddOption("job-originating-user-name", username, num_options, &options);
346		num_options = cupsAddOption("originating-user-name", username, num_options, &options);
347		cupsSetUser ( username );
348	}
349
350	if (cupsoptions != NULL)
351	{
352              num_options = cupsParseOptions(cupsoptions, num_options, &options);
353	}
354
355	strlcpy ( filepath, SPOOLDIR, sizeof(filepath));
356	strlcat ( filepath , "/", sizeof(filepath));
357	strlcat ( filepath , filename, sizeof(filepath));
358
359	if ((jobid = cupsPrintFile( name, filepath, job, 0, options)) == 0)
360		LOG(log_error, logtype_papd, "Unable to print job '%s' (%s) to printer '%s' for user '%s' - CUPS error : '%s'", job, filepath, name, username, ippErrorString(cupsLastError()));
361	else
362		LOG(log_info, logtype_papd, "Job '%s' queued to printer '%s' with id '%d'", job, name, jobid);
363
364	cupsFreeOptions(num_options, options);
365	return (jobid);
366}
367
368
369/*------------------------------------------------------------------------*/
370
371struct printer	*
372cups_autoadd_printers ( struct printer	*defprinter, struct printer *printers)
373{
374	struct printer 	*pr;
375        int         	num_dests,i;
376	int 	    	ret;
377        cups_dest_t 	*dests;
378        cups_lang_t 	*language;
379	char 	    	name[MAXCHOOSERLEN+1], *p;
380
381        language  = cupsLangDefault();		/* needed for conversion */
382        num_dests = cupsGetDests(&dests);	/* get the available destination from CUPS */
383
384        for  (i=0; i< num_dests; i++)
385        {
386		if (( pr = (struct printer *)malloc( sizeof( struct printer )))	== NULL ) {
387			LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
388			exit( 1 );
389		}
390
391		memcpy( pr, defprinter, sizeof( struct printer ) );
392
393		/* convert from CUPS to local encoding */
394                convert_string_allocate( add_charset(cupsLangEncoding(language)), CH_UNIX,
395                                         dests[i].name, -1, &pr->p_u_name);
396
397		/* convert CUPS name to Mac charset */
398		if ( convert_to_mac_name ( cupsLangEncoding(language), dests[i].name, name, sizeof(name)) <= 0)
399		{
400			LOG (log_error, logtype_papd, "Conversion from CUPS to MAC name failed for %s", dests[i].name);
401			free (pr);
402			continue;
403		}
404
405		if (( pr->p_name = (char *)malloc( strlen( name ) + 1 )) == NULL ) {
406			LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
407			exit( 1 );
408		}
409		strcpy( pr->p_name, name );
410
411		/* set printer flags */
412		pr->p_flags &= ~P_PIPED;
413		pr->p_flags |= P_SPOOLED;
414		pr->p_flags |= P_CUPS;
415		pr->p_flags |= P_CUPS_AUTOADDED;
416
417		if (( pr->p_printer = (char *)malloc( strlen( dests[i].name ) + 1 )) == NULL ) {
418			LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
419               		exit( 1 );
420        	}
421        	strcpy( pr->p_printer, dests[i].name );
422
423        	if ( (p = (char *) cups_get_printer_ppd ( pr->p_printer )) != NULL ) {
424        		if (( pr->p_ppdfile = (char *)malloc( strlen( p ) + 1 )) == NULL ) {
425				LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
426               			exit( 1 );
427        		}
428        		strcpy( pr->p_ppdfile, p );
429			pr->p_flags |= P_CUPS_PPD;
430        	}
431
432		if ( (ret = cups_check_printer ( pr, printers, 0)) == -1)
433			ret = cups_mangle_printer_name ( pr, printers );
434
435		if (ret) {
436			cups_free_printer (pr);
437			LOG(log_info, logtype_papd, "Printer %s not added: reason %d", name, ret);
438		}
439		else {
440			pr->p_next = printers;
441			printers = pr;
442		}
443	}
444
445        cupsFreeDests(num_dests, dests);
446        cupsLangFree(language);
447
448	return printers;
449}
450
451
452/*------------------------------------------------------------------------*/
453
454/* cups_mangle_printer_name
455 *    Mangles the printer name if two CUPS printer provide the same Chooser Name
456 *    Append '#nn' to the chooser name, if it is longer than 28 char we overwrite the last three chars
457 * Returns: 0 on Success, 2 on Error
458 */
459
460static int cups_mangle_printer_name ( struct printer *pr, struct printer *printers)
461{
462	size_t 	count, name_len;
463	char	name[MAXCHOOSERLEN];
464
465	count = 1;
466	name_len = strlen (pr->p_name);
467	strncpy ( name, pr->p_name, MAXCHOOSERLEN-3);
468
469	/* Reallocate necessary space */
470	(name_len >= MAXCHOOSERLEN-3) ? (name_len = MAXCHOOSERLEN+1) : (name_len = name_len + 4);
471	pr->p_name = (char *) realloc (pr->p_name, name_len );
472
473	while ( ( cups_check_printer ( pr, printers, 0 )) && count < 100)
474	{
475		memset ( pr->p_name, 0, name_len);
476		strncpy ( pr->p_name, name, MAXCHOOSERLEN-3);
477		sprintf ( pr->p_name, "%s#%2.2u", pr->p_name, count++);
478	}
479
480	if ( count > 99)
481		return (2);
482
483	return (0);
484}
485
486/*------------------------------------------------------------------------*/
487
488/* fallback ASCII conversion */
489
490static size_t
491to_ascii ( char  *inptr, char **outptr)
492{
493	char *out, *osav;
494
495	if ( NULL == (out = (char*) malloc ( strlen ( inptr) + 1 )) ) {
496		LOG(log_error, logtype_papd, "malloc: %s", strerror(errno) );
497		exit (1);
498	}
499
500	osav = out;
501
502	while ( *inptr != '\0' ) {
503		if ( *inptr & 0x80 ) {
504			*out = '_';
505			out++;
506			inptr++;
507		}
508		else
509			*out++ = *inptr++;
510	}
511	*out = '\0';
512	*outptr = osav;
513	return ( strlen (osav) );
514}
515
516
517/*------------------------------------------------------------------------*/
518
519/* convert_to_mac_name
520 * 	1) Convert from encoding to MacRoman
521 *	2) Shorten to MAXCHOOSERLEN (31)
522 *      3) Replace @ and _ as they are illegal
523 * Returns: -1 on failure, length of name on success; outpr contains name in MacRoman
524 */
525
526static int convert_to_mac_name ( const char * encoding, char * inptr, char * outptr, size_t outlen)
527{
528	char   	*outbuf;
529	char	*soptr;
530	size_t 	name_len = 0;
531	size_t 	i;
532	charset_t chCups;
533
534	/* Change the encoding */
535	if ((charset_t)-1 != (chCups = add_charset(encoding))) {
536		name_len = convert_string_allocate( chCups, CH_MAC, inptr, -1, &outbuf);
537	}
538
539	if (name_len == 0 || name_len == (size_t)-1) {
540		/* charset conversion failed, use ascii fallback */
541		name_len = to_ascii ( inptr, &outbuf );
542	}
543
544	soptr = outptr;
545
546	for ( i=0; i< name_len && i < outlen-1 ; i++)
547	{
548		if ( outbuf[i] == '_' )
549			*soptr = ' '; /* Replace '_' with a space (just for the looks) */
550		else if ( outbuf[i] == '@' || outbuf[i] == ':' )
551			*soptr = '_'; /* Replace @ and : with '_' as they are illegal chars */
552		else
553			*soptr = outbuf[i];
554		soptr++;
555	}
556	*soptr = '\0';
557	free (outbuf);
558	return (i);
559}
560
561
562/*------------------------------------------------------------------------*/
563
564/*
565 * cups_check_printer:
566 * check if a printer with this name already exists.
567 * if yes, and replace = 1 the existing printer is replaced with
568 * the new one. This allows to overwrite printer settings
569 * created by cupsautoadd. It also used by cups_mangle_printer.
570 */
571
572int cups_check_printer ( struct printer *pr, struct printer *printers, int replace)
573{
574	struct printer *listptr, *listprev;
575
576	listptr = printers;
577	listprev = NULL;
578
579	while ( listptr != NULL) {
580		if ( strcasecmp (pr->p_name, listptr->p_name) == 0) {
581			if ( pr->p_flags & P_CUPS_AUTOADDED ) {  /* Check if printer has been autoadded */
582				if ( listptr->p_flags & P_CUPS_AUTOADDED )
583					return (-1);		 /* Conflicting Cups Auto Printer (mangling issue?) */
584				else
585					return (1);		 /* Never replace a hand edited printer with auto one */
586			}
587
588			if ( replace ) {
589				/* Replace printer */
590				if ( listprev != NULL) {
591					pr->p_next = listptr->p_next;
592					listprev->p_next = pr;
593					cups_free_printer (listptr);
594				}
595				else {
596					printers = pr;
597					printers->p_next = listptr->p_next;
598					cups_free_printer (listptr);
599				}
600			}
601			return (1);  /* Conflicting Printers */
602		}
603		listprev = listptr;
604		listptr = listptr->p_next;
605	}
606	return (0);	/* No conflict */
607}
608
609
610/*------------------------------------------------------------------------*/
611
612
613void
614cups_free_printer ( struct printer *pr)
615{
616	if ( pr->p_name != NULL)
617		free (pr->p_name);
618	if ( pr->p_printer != NULL)
619		free (pr->p_printer);
620	if ( pr->p_ppdfile != NULL)
621		free (pr->p_ppdfile);
622
623	/* CUPS autoadded printers share the other informations
624	 * so if the printer is autoadded we won't free them.
625	 * We might leak some bytes here though.
626	 */
627	if ( pr->p_flags & P_CUPS_AUTOADDED ) {
628		free (pr);
629		return;
630	}
631
632	if ( pr->p_operator != NULL )
633		free (pr->p_operator);
634	if ( pr->p_zone != NULL )
635		free (pr->p_zone);
636	if ( pr->p_type != NULL )
637		free (pr->p_type);
638	if ( pr->p_authprintdir != NULL )
639		free (pr->p_authprintdir);
640
641	free ( pr );
642	return;
643
644}
645
646#endif /* HAVE_CUPS*/
647