4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
27 * CUPS support for the SMB and SPOOLSS print services.
30 #include <sys/types.h>
32 #include <sys/atomic.h>
40 #include <smbsrv/smb.h>
41 #include <smbsrv/smb_share.h>
45 #include <cups/cups.h>
47 #define SMB_SPOOL_WAIT 2
48 #define SMBD_PJOBLEN 256
49 #define SMBD_PRINTER "Postscript"
50 #define SMBD_FN_PREFIX "cifsprintjob-"
51 #define SMBD_CUPS_SPOOL_DIR "//var//spool//cups"
52 #define SMBD_CUPS_DOCNAME "generic_doc"
54 typedef struct smbd_printjob
{
62 boolean_t pj_isspooled
;
64 char pj_filename
[SMBD_PJOBLEN
];
65 char pj_jobname
[SMBD_PJOBLEN
];
66 char pj_username
[SMBD_PJOBLEN
];
67 char pj_queuename
[SMBD_PJOBLEN
];
70 typedef struct smb_cups_ops
{
72 cups_lang_t
*(*cupsLangDefault
)();
73 const char *(*cupsLangEncoding
)(cups_lang_t
*);
74 void (*cupsLangFree
)(cups_lang_t
*);
75 ipp_status_t (*cupsLastError
)();
76 int (*cupsGetDests
)(cups_dest_t
**);
77 void (*cupsFreeDests
)(int, cups_dest_t
*);
78 ipp_t
*(*cupsDoFileRequest
)(http_t
*, ipp_t
*,
79 const char *, const char *);
82 char *(*ippErrorString
)();
83 ipp_attribute_t
*(*ippAddString
)();
84 void (*httpClose
)(http_t
*);
85 http_t
*(*httpConnect
)(const char *, int);
88 static uint32_t smbd_cups_jobnum
= 1;
89 static smb_cups_ops_t smb_cups
;
90 static mutex_t smbd_cups_mutex
;
92 static void *smbd_spool_monitor(void *);
93 static smb_cups_ops_t
*smbd_cups_ops(void);
94 static void smbd_print_share_comment(smb_share_t
*, cups_dest_t
*);
95 static void *smbd_share_printers(void *);
96 static void smbd_spool_copyfile(smb_inaddr_t
*, char *, char *, char *);
101 * Start the spool thread.
102 * Returns 0 on success, an error number if thread creation fails.
105 smbd_spool_start(void)
110 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE
))
113 (void) pthread_attr_init(&attr
);
114 (void) pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
115 rc
= pthread_create(&smbd
.s_spool_tid
, &attr
, smbd_spool_monitor
, NULL
);
116 (void) pthread_attr_destroy(&attr
);
120 "failed to start print monitor: %s", strerror(errno
));
124 * A single pthread_kill should be sufficient but we include
125 * a couple of retries to avoid implementation idiosyncrasies
126 * around signal delivery.
129 smbd_spool_stop(void)
133 if (pthread_self() == smbd
.s_spool_tid
)
136 for (i
= 0; i
< 3 && smbd
.s_spool_tid
!= 0; ++i
) {
137 if (pthread_kill(smbd
.s_spool_tid
, SIGTERM
) == ESRCH
)
145 * This thread blocks waiting for close print file in the kernel.
146 * It then uses the data returned from the ioctl to copy the spool file
147 * into the cups spooler.
149 * This mechanism is really only used by Windows Vista and Windows 7.
150 * Other versions of Windows create a zero size file, which is removed
151 * by smbd_spool_copyfile.
155 smbd_spool_monitor(void *arg
)
158 char username
[MAXNAMELEN
];
159 char path
[MAXPATHLEN
];
161 int error_retry_cnt
= 5;
163 smbd_online_wait("smbd_spool_monitor");
165 spoolss_register_copyfile(smbd_spool_copyfile
);
167 while (!smbd
.s_shutting_down
&& (error_retry_cnt
> 0)) {
170 if (smb_kmod_get_spool_doc(&spool_num
, username
,
171 path
, &ipaddr
) == 0) {
172 smbd_spool_copyfile(&ipaddr
,
173 username
, path
, SMBD_CUPS_DOCNAME
);
176 if (errno
== ECANCELED
)
178 if ((errno
!= EINTR
) && (errno
!= EAGAIN
))
180 (void) sleep(SMB_SPOOL_WAIT
);
184 spoolss_register_copyfile(NULL
);
185 smbd
.s_spool_tid
= 0;
190 * All versions of windows use this function to spool files to a printer
191 * via the cups interface
194 smbd_spool_copyfile(smb_inaddr_t
*ipaddr
, char *username
, char *path
,
197 smb_cups_ops_t
*cups
;
198 http_t
*http
= NULL
; /* HTTP connection to server */
199 ipp_t
*request
= NULL
; /* IPP Request */
200 ipp_t
*response
= NULL
; /* IPP Response */
201 cups_lang_t
*language
= NULL
; /* Default language */
202 char uri
[HTTP_MAX_URI
]; /* printer-uri attribute */
203 char new_jobname
[SMBD_PJOBLEN
];
204 smbd_printjob_t pjob
;
205 char clientname
[INET6_ADDRSTRLEN
];
209 if (stat(path
, &sbuf
)) {
210 syslog(LOG_INFO
, "smbd_spool_copyfile: %s: %s",
211 path
, strerror(errno
));
216 * Remove zero size files and return; these were inadvertantly
217 * created by XP or 2000.
219 if (sbuf
.st_size
== 0) {
220 if (remove(path
) != 0)
222 "smbd_spool_copyfile: cannot remove %s: %s",
223 path
, strerror(errno
));
227 if ((cups
= smbd_cups_ops()) == NULL
)
230 if ((http
= cups
->httpConnect("localhost", 631)) == NULL
) {
232 "smbd_spool_copyfile: cupsd not running");
236 if ((request
= cups
->ippNew()) == NULL
) {
238 "smbd_spool_copyfile: ipp not running");
242 request
->request
.op
.operation_id
= IPP_PRINT_JOB
;
243 request
->request
.op
.request_id
= 1;
244 language
= cups
->cupsLangDefault();
246 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
247 "attributes-charset", NULL
, cups
->cupsLangEncoding(language
));
249 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
250 "attributes-natural-language", NULL
, language
->language
);
252 (void) snprintf(uri
, sizeof (uri
), "ipp://localhost/printers/%s",
254 pjob
.pj_pid
= pthread_self();
256 (void) strlcpy(pjob
.pj_filename
, path
, SMBD_PJOBLEN
);
257 pjob
.pj_start_time
= time(NULL
);
259 pjob
.pj_size
= sbuf
.st_blocks
* 512;
260 pjob
.pj_page_count
= 1;
261 pjob
.pj_isspooled
= B_TRUE
;
262 pjob
.pj_jobnum
= smbd_cups_jobnum
;
264 (void) strlcpy(pjob
.pj_jobname
, doc_name
, SMBD_PJOBLEN
);
265 (void) strlcpy(pjob
.pj_username
, username
, SMBD_PJOBLEN
);
266 (void) strlcpy(pjob
.pj_queuename
, SMBD_CUPS_SPOOL_DIR
, SMBD_PJOBLEN
);
268 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
,
269 "printer-uri", NULL
, uri
);
271 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
272 "requesting-user-name", NULL
, pjob
.pj_username
);
274 if (smb_inet_ntop(ipaddr
, clientname
,
275 SMB_IPSTRLEN(ipaddr
->a_family
)) == NULL
) {
277 "smbd_spool_copyfile: %s: unknown client", clientname
);
281 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
282 "job-originating-host-name", NULL
, clientname
);
284 (void) snprintf(new_jobname
, SMBD_PJOBLEN
, "%s%d",
285 SMBD_FN_PREFIX
, pjob
.pj_jobnum
);
286 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
287 "job-name", NULL
, new_jobname
);
289 (void) snprintf(uri
, sizeof (uri
) - 1, "/printers/%s", SMBD_PRINTER
);
291 response
= cups
->cupsDoFileRequest(http
, request
, uri
,
293 if (response
!= NULL
) {
294 if (response
->request
.status
.status_code
>= IPP_OK_CONFLICT
) {
296 "smbd_spool_copyfile: printer %s: %s",
298 cups
->ippErrorString(cups
->cupsLastError()));
300 atomic_inc_32(&smbd_cups_jobnum
);
305 "smbd_spool_copyfile: unable to print to %s",
306 cups
->ippErrorString(cups
->cupsLastError()));
310 (void) unlink(pjob
.pj_filename
);
314 cups
->ippDelete(response
);
317 cups
->cupsLangFree(language
);
320 cups
->httpClose(http
);
326 (void) mutex_lock(&smbd_cups_mutex
);
328 if (smb_cups
.cups_hdl
!= NULL
) {
329 (void) mutex_unlock(&smbd_cups_mutex
);
333 if ((smb_cups
.cups_hdl
= dlopen("libcups.so.2", RTLD_NOW
)) == NULL
) {
334 (void) mutex_unlock(&smbd_cups_mutex
);
336 "smbd_cups_init: cannot open libcups");
340 smb_cups
.cupsLangDefault
=
341 (cups_lang_t
*(*)())dlsym(smb_cups
.cups_hdl
, "cupsLangDefault");
342 smb_cups
.cupsLangEncoding
= (const char *(*)(cups_lang_t
*))
343 dlsym(smb_cups
.cups_hdl
, "cupsLangEncoding");
344 smb_cups
.cupsDoFileRequest
=
345 (ipp_t
*(*)(http_t
*, ipp_t
*, const char *, const char *))
346 dlsym(smb_cups
.cups_hdl
, "cupsDoFileRequest");
347 smb_cups
.cupsLastError
= (ipp_status_t (*)())
348 dlsym(smb_cups
.cups_hdl
, "cupsLastError");
349 smb_cups
.cupsLangFree
= (void (*)(cups_lang_t
*))
350 dlsym(smb_cups
.cups_hdl
, "cupsLangFree");
351 smb_cups
.cupsGetDests
= (int (*)(cups_dest_t
**))
352 dlsym(smb_cups
.cups_hdl
, "cupsGetDests");
353 smb_cups
.cupsFreeDests
= (void (*)(int, cups_dest_t
*))
354 dlsym(smb_cups
.cups_hdl
, "cupsFreeDests");
356 smb_cups
.httpClose
= (void (*)(http_t
*))
357 dlsym(smb_cups
.cups_hdl
, "httpClose");
358 smb_cups
.httpConnect
= (http_t
*(*)(const char *, int))
359 dlsym(smb_cups
.cups_hdl
, "httpConnect");
361 smb_cups
.ippNew
= (ipp_t
*(*)())dlsym(smb_cups
.cups_hdl
, "ippNew");
362 smb_cups
.ippDelete
= (void (*)())dlsym(smb_cups
.cups_hdl
, "ippDelete");
363 smb_cups
.ippErrorString
= (char *(*)())
364 dlsym(smb_cups
.cups_hdl
, "ippErrorString");
365 smb_cups
.ippAddString
= (ipp_attribute_t
*(*)())
366 dlsym(smb_cups
.cups_hdl
, "ippAddString");
368 if (smb_cups
.cupsLangDefault
== NULL
||
369 smb_cups
.cupsLangEncoding
== NULL
||
370 smb_cups
.cupsDoFileRequest
== NULL
||
371 smb_cups
.cupsLastError
== NULL
||
372 smb_cups
.cupsLangFree
== NULL
||
373 smb_cups
.cupsGetDests
== NULL
||
374 smb_cups
.cupsFreeDests
== NULL
||
375 smb_cups
.ippNew
== NULL
||
376 smb_cups
.httpClose
== NULL
||
377 smb_cups
.httpConnect
== NULL
||
378 smb_cups
.ippDelete
== NULL
||
379 smb_cups
.ippErrorString
== NULL
||
380 smb_cups
.ippAddString
== NULL
) {
381 (void) dlclose(smb_cups
.cups_hdl
);
382 smb_cups
.cups_hdl
= NULL
;
383 (void) mutex_unlock(&smbd_cups_mutex
);
385 "smbd_cups_init: cannot load libcups");
389 (void) mutex_unlock(&smbd_cups_mutex
);
396 (void) mutex_lock(&smbd_cups_mutex
);
398 if (smb_cups
.cups_hdl
!= NULL
) {
399 (void) dlclose(smb_cups
.cups_hdl
);
400 smb_cups
.cups_hdl
= NULL
;
403 (void) mutex_unlock(&smbd_cups_mutex
);
406 static smb_cups_ops_t
*
409 if (smb_cups
.cups_hdl
== NULL
)
416 smbd_load_printers(void)
422 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE
))
425 (void) pthread_attr_init(&attr
);
426 (void) pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
427 rc
= pthread_create(&tid
, &attr
, smbd_share_printers
, &tid
);
428 (void) pthread_attr_destroy(&attr
);
432 "unable to load printer shares: %s", strerror(errno
));
436 * All print shares use the path from print$.
440 smbd_share_printers(void *arg
)
444 smb_cups_ops_t
*cups
;
450 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE
))
453 if ((cups
= smbd_cups_ops()) == NULL
)
456 if (smb_shr_get(SMB_SHARE_PRINT
, &si
) != NERR_Success
) {
458 "smbd_share_printers unable to load %s", SMB_SHARE_PRINT
);
462 num_dests
= cups
->cupsGetDests(&dests
);
464 for (i
= num_dests
, dest
= dests
; i
> 0; i
--, dest
++) {
465 if (dest
->instance
!= NULL
)
468 (void) strlcpy(si
.shr_name
, dest
->name
, MAXPATHLEN
);
469 smbd_print_share_comment(&si
, dest
);
470 si
.shr_type
= STYPE_PRINTQ
;
472 nerr
= smb_shr_add(&si
);
473 if (nerr
== NERR_Success
|| nerr
== NERR_DuplicateShare
)
475 "shared printer: %s", si
.shr_name
);
478 "smbd_share_printers: unable to add share %s: %u",
482 cups
->cupsFreeDests(num_dests
, dests
);
487 smbd_print_share_comment(smb_share_t
*si
, cups_dest_t
*dest
)
489 cups_option_t
*options
;
495 comment
= "Print Share";
497 if ((options
= dest
->options
) == NULL
) {
498 (void) strlcpy(si
->shr_cmnt
, comment
, SMB_SHARE_CMNT_MAX
);
502 for (i
= 0; i
< dest
->num_options
; ++i
) {
503 name
= options
[i
].name
;
504 value
= options
[i
].value
;
506 if (name
== NULL
|| value
== NULL
||
507 *name
== '\0' || *value
== '\0')
510 if (strcasecmp(name
, "printer-info") == 0) {
516 (void) strlcpy(si
->shr_cmnt
, comment
, SMB_SHARE_CMNT_MAX
);
519 #else /* HAVE_CUPS */
522 * If not HAVE_CUPS, just provide a few "stubs".
537 smbd_load_printers(void)
542 smbd_spool_init(void)
547 smbd_spool_fini(void)
552 smbd_spool_start(void)
557 smbd_spool_stop(void)
561 #endif /* HAVE_CUPS */