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 <sys/cfgparam.h>
41 #include <smbsrv/smb.h>
42 #include <smbsrv/smb_share.h>
45 #ifdef CONFIG_SMB_PRINTING
46 #define _IPP_PRIVATE_STRUCTURES 1
47 #include <cups/cups.h>
49 #define SMB_SPOOL_WAIT 2
50 #define SMBD_PJOBLEN 256
51 #define SMBD_PRINTER "Postscript"
52 #define SMBD_FN_PREFIX "cifsprintjob-"
53 #define SMBD_CUPS_SPOOL_DIR "//var//spool//cups"
54 #define SMBD_CUPS_DOCNAME "generic_doc"
56 typedef struct smbd_printjob
{
64 boolean_t pj_isspooled
;
66 char pj_filename
[SMBD_PJOBLEN
];
67 char pj_jobname
[SMBD_PJOBLEN
];
68 char pj_username
[SMBD_PJOBLEN
];
69 char pj_queuename
[SMBD_PJOBLEN
];
72 typedef struct smb_cups_ops
{
74 cups_lang_t
*(*cupsLangDefault
)();
75 const char *(*cupsLangEncoding
)(cups_lang_t
*);
76 void (*cupsLangFree
)(cups_lang_t
*);
77 ipp_status_t (*cupsLastError
)();
78 int (*cupsGetDests
)(cups_dest_t
**);
79 void (*cupsFreeDests
)(int, cups_dest_t
*);
80 ipp_t
*(*cupsDoFileRequest
)(http_t
*, ipp_t
*,
81 const char *, const char *);
84 char *(*ippErrorString
)();
85 ipp_attribute_t
*(*ippAddString
)();
86 void (*httpClose
)(http_t
*);
87 http_t
*(*httpConnect
)(const char *, int);
90 static uint32_t smbd_cups_jobnum
= 1;
91 static smb_cups_ops_t smb_cups
;
92 static mutex_t smbd_cups_mutex
;
94 static void *smbd_spool_monitor(void *);
95 static smb_cups_ops_t
*smbd_cups_ops(void);
96 static void smbd_print_share_comment(smb_share_t
*, cups_dest_t
*);
97 static void *smbd_share_printers(void *);
98 static void smbd_spool_copyfile(smb_inaddr_t
*, char *, char *, char *);
103 * Start the spool thread.
104 * Returns 0 on success, an error number if thread creation fails.
107 smbd_spool_start(void)
112 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE
))
115 (void) pthread_attr_init(&attr
);
116 (void) pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
117 rc
= pthread_create(&smbd
.s_spool_tid
, &attr
, smbd_spool_monitor
, NULL
);
118 (void) pthread_attr_destroy(&attr
);
122 "failed to start print monitor: %s", strerror(errno
));
126 * A single pthread_kill should be sufficient but we include
127 * a couple of retries to avoid implementation idiosyncrasies
128 * around signal delivery.
131 smbd_spool_stop(void)
135 if (pthread_self() == smbd
.s_spool_tid
)
138 for (i
= 0; i
< 3 && smbd
.s_spool_tid
!= 0; ++i
) {
139 if (pthread_kill(smbd
.s_spool_tid
, SIGTERM
) == ESRCH
)
147 * This thread blocks waiting for close print file in the kernel.
148 * It then uses the data returned from the ioctl to copy the spool file
149 * into the cups spooler.
151 * This mechanism is really only used by Windows Vista and Windows 7.
152 * Other versions of Windows create a zero size file, which is removed
153 * by smbd_spool_copyfile.
157 smbd_spool_monitor(void *arg
)
160 char username
[MAXNAMELEN
];
161 char path
[MAXPATHLEN
];
163 int error_retry_cnt
= 5;
165 smbd_online_wait("smbd_spool_monitor");
167 spoolss_register_copyfile(smbd_spool_copyfile
);
169 while (!smbd
.s_shutting_down
&& (error_retry_cnt
> 0)) {
172 if (smb_kmod_get_spool_doc(&spool_num
, username
,
173 path
, &ipaddr
) == 0) {
174 smbd_spool_copyfile(&ipaddr
,
175 username
, path
, SMBD_CUPS_DOCNAME
);
178 if (errno
== ECANCELED
)
180 if ((errno
!= EINTR
) && (errno
!= EAGAIN
))
182 (void) sleep(SMB_SPOOL_WAIT
);
186 spoolss_register_copyfile(NULL
);
187 smbd
.s_spool_tid
= 0;
192 * All versions of windows use this function to spool files to a printer
193 * via the cups interface
196 smbd_spool_copyfile(smb_inaddr_t
*ipaddr
, char *username
, char *path
,
199 smb_cups_ops_t
*cups
;
200 http_t
*http
= NULL
; /* HTTP connection to server */
201 ipp_t
*request
= NULL
; /* IPP Request */
202 ipp_t
*response
= NULL
; /* IPP Response */
203 cups_lang_t
*language
= NULL
; /* Default language */
204 char uri
[HTTP_MAX_URI
]; /* printer-uri attribute */
205 char new_jobname
[SMBD_PJOBLEN
];
206 smbd_printjob_t pjob
;
207 char clientname
[INET6_ADDRSTRLEN
];
211 if (stat(path
, &sbuf
)) {
212 syslog(LOG_INFO
, "smbd_spool_copyfile: %s: %s",
213 path
, strerror(errno
));
218 * Remove zero size files and return; these were inadvertantly
219 * created by XP or 2000.
221 if (sbuf
.st_size
== 0) {
222 if (remove(path
) != 0)
224 "smbd_spool_copyfile: cannot remove %s: %s",
225 path
, strerror(errno
));
229 if ((cups
= smbd_cups_ops()) == NULL
)
232 if ((http
= cups
->httpConnect("localhost", 631)) == NULL
) {
234 "smbd_spool_copyfile: cupsd not running");
238 if ((request
= cups
->ippNew()) == NULL
) {
240 "smbd_spool_copyfile: ipp not running");
244 request
->request
.op
.operation_id
= IPP_PRINT_JOB
;
245 request
->request
.op
.request_id
= 1;
246 language
= cups
->cupsLangDefault();
248 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_CHARSET
,
249 "attributes-charset", NULL
, cups
->cupsLangEncoding(language
));
251 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_LANGUAGE
,
252 "attributes-natural-language", NULL
, language
->language
);
254 (void) snprintf(uri
, sizeof (uri
), "ipp://localhost/printers/%s",
256 pjob
.pj_pid
= pthread_self();
258 (void) strlcpy(pjob
.pj_filename
, path
, SMBD_PJOBLEN
);
259 pjob
.pj_start_time
= time(NULL
);
261 pjob
.pj_size
= sbuf
.st_blocks
* 512;
262 pjob
.pj_page_count
= 1;
263 pjob
.pj_isspooled
= B_TRUE
;
264 pjob
.pj_jobnum
= smbd_cups_jobnum
;
266 (void) strlcpy(pjob
.pj_jobname
, doc_name
, SMBD_PJOBLEN
);
267 (void) strlcpy(pjob
.pj_username
, username
, SMBD_PJOBLEN
);
268 (void) strlcpy(pjob
.pj_queuename
, SMBD_CUPS_SPOOL_DIR
, SMBD_PJOBLEN
);
270 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_URI
,
271 "printer-uri", NULL
, uri
);
273 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
274 "requesting-user-name", NULL
, pjob
.pj_username
);
276 if (smb_inet_ntop(ipaddr
, clientname
,
277 SMB_IPSTRLEN(ipaddr
->a_family
)) == NULL
) {
279 "smbd_spool_copyfile: %s: unknown client", clientname
);
283 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
284 "job-originating-host-name", NULL
, clientname
);
286 (void) snprintf(new_jobname
, SMBD_PJOBLEN
, "%s%d",
287 SMBD_FN_PREFIX
, pjob
.pj_jobnum
);
288 cups
->ippAddString(request
, IPP_TAG_OPERATION
, IPP_TAG_NAME
,
289 "job-name", NULL
, new_jobname
);
291 (void) snprintf(uri
, sizeof (uri
) - 1, "/printers/%s", SMBD_PRINTER
);
293 response
= cups
->cupsDoFileRequest(http
, request
, uri
,
295 if (response
!= NULL
) {
296 if (response
->request
.status
.status_code
>= IPP_OK_CONFLICT
) {
298 "smbd_spool_copyfile: printer %s: %s",
300 cups
->ippErrorString(cups
->cupsLastError()));
302 atomic_inc_32(&smbd_cups_jobnum
);
307 "smbd_spool_copyfile: unable to print to %s",
308 cups
->ippErrorString(cups
->cupsLastError()));
312 (void) unlink(pjob
.pj_filename
);
316 cups
->ippDelete(response
);
319 cups
->cupsLangFree(language
);
322 cups
->httpClose(http
);
328 (void) mutex_lock(&smbd_cups_mutex
);
330 if (smb_cups
.cups_hdl
!= NULL
) {
331 (void) mutex_unlock(&smbd_cups_mutex
);
335 if ((smb_cups
.cups_hdl
= dlopen("libcups.so.2", RTLD_NOW
)) == NULL
) {
336 (void) mutex_unlock(&smbd_cups_mutex
);
338 "smbd_cups_init: cannot open libcups");
342 smb_cups
.cupsLangDefault
=
343 (cups_lang_t
*(*)())dlsym(smb_cups
.cups_hdl
, "cupsLangDefault");
344 smb_cups
.cupsLangEncoding
= (const char *(*)(cups_lang_t
*))
345 dlsym(smb_cups
.cups_hdl
, "cupsLangEncoding");
346 smb_cups
.cupsDoFileRequest
=
347 (ipp_t
*(*)(http_t
*, ipp_t
*, const char *, const char *))
348 dlsym(smb_cups
.cups_hdl
, "cupsDoFileRequest");
349 smb_cups
.cupsLastError
= (ipp_status_t (*)())
350 dlsym(smb_cups
.cups_hdl
, "cupsLastError");
351 smb_cups
.cupsLangFree
= (void (*)(cups_lang_t
*))
352 dlsym(smb_cups
.cups_hdl
, "cupsLangFree");
353 smb_cups
.cupsGetDests
= (int (*)(cups_dest_t
**))
354 dlsym(smb_cups
.cups_hdl
, "cupsGetDests");
355 smb_cups
.cupsFreeDests
= (void (*)(int, cups_dest_t
*))
356 dlsym(smb_cups
.cups_hdl
, "cupsFreeDests");
358 smb_cups
.httpClose
= (void (*)(http_t
*))
359 dlsym(smb_cups
.cups_hdl
, "httpClose");
360 smb_cups
.httpConnect
= (http_t
*(*)(const char *, int))
361 dlsym(smb_cups
.cups_hdl
, "httpConnect");
363 smb_cups
.ippNew
= (ipp_t
*(*)())dlsym(smb_cups
.cups_hdl
, "ippNew");
364 smb_cups
.ippDelete
= (void (*)())dlsym(smb_cups
.cups_hdl
, "ippDelete");
365 smb_cups
.ippErrorString
= (char *(*)())
366 dlsym(smb_cups
.cups_hdl
, "ippErrorString");
367 smb_cups
.ippAddString
= (ipp_attribute_t
*(*)())
368 dlsym(smb_cups
.cups_hdl
, "ippAddString");
370 if (smb_cups
.cupsLangDefault
== NULL
||
371 smb_cups
.cupsLangEncoding
== NULL
||
372 smb_cups
.cupsDoFileRequest
== NULL
||
373 smb_cups
.cupsLastError
== NULL
||
374 smb_cups
.cupsLangFree
== NULL
||
375 smb_cups
.cupsGetDests
== NULL
||
376 smb_cups
.cupsFreeDests
== NULL
||
377 smb_cups
.ippNew
== NULL
||
378 smb_cups
.httpClose
== NULL
||
379 smb_cups
.httpConnect
== NULL
||
380 smb_cups
.ippDelete
== NULL
||
381 smb_cups
.ippErrorString
== NULL
||
382 smb_cups
.ippAddString
== NULL
) {
383 (void) dlclose(smb_cups
.cups_hdl
);
384 smb_cups
.cups_hdl
= NULL
;
385 (void) mutex_unlock(&smbd_cups_mutex
);
387 "smbd_cups_init: cannot load libcups");
391 (void) mutex_unlock(&smbd_cups_mutex
);
398 (void) mutex_lock(&smbd_cups_mutex
);
400 if (smb_cups
.cups_hdl
!= NULL
) {
401 (void) dlclose(smb_cups
.cups_hdl
);
402 smb_cups
.cups_hdl
= NULL
;
405 (void) mutex_unlock(&smbd_cups_mutex
);
408 static smb_cups_ops_t
*
411 if (smb_cups
.cups_hdl
== NULL
)
418 smbd_load_printers(void)
424 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE
))
427 (void) pthread_attr_init(&attr
);
428 (void) pthread_attr_setdetachstate(&attr
, PTHREAD_CREATE_DETACHED
);
429 rc
= pthread_create(&tid
, &attr
, smbd_share_printers
, &tid
);
430 (void) pthread_attr_destroy(&attr
);
434 "unable to load printer shares: %s", strerror(errno
));
438 * All print shares use the path from print$.
442 smbd_share_printers(void *arg
)
446 smb_cups_ops_t
*cups
;
452 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE
))
455 if ((cups
= smbd_cups_ops()) == NULL
)
458 if (smb_shr_get(SMB_SHARE_PRINT
, &si
) != NERR_Success
) {
460 "smbd_share_printers unable to load %s", SMB_SHARE_PRINT
);
464 num_dests
= cups
->cupsGetDests(&dests
);
466 for (i
= num_dests
, dest
= dests
; i
> 0; i
--, dest
++) {
467 if (dest
->instance
!= NULL
)
470 (void) strlcpy(si
.shr_name
, dest
->name
, MAXPATHLEN
);
471 smbd_print_share_comment(&si
, dest
);
472 si
.shr_type
= STYPE_PRINTQ
;
474 nerr
= smb_shr_add(&si
);
475 if (nerr
== NERR_Success
|| nerr
== NERR_DuplicateShare
)
477 "shared printer: %s", si
.shr_name
);
480 "smbd_share_printers: unable to add share %s: %u",
484 cups
->cupsFreeDests(num_dests
, dests
);
489 smbd_print_share_comment(smb_share_t
*si
, cups_dest_t
*dest
)
491 cups_option_t
*options
;
497 comment
= "Print Share";
499 if ((options
= dest
->options
) == NULL
) {
500 (void) strlcpy(si
->shr_cmnt
, comment
, SMB_SHARE_CMNT_MAX
);
504 for (i
= 0; i
< dest
->num_options
; ++i
) {
505 name
= options
[i
].name
;
506 value
= options
[i
].value
;
508 if (name
== NULL
|| value
== NULL
||
509 *name
== '\0' || *value
== '\0')
512 if (strcasecmp(name
, "printer-info") == 0) {
518 (void) strlcpy(si
->shr_cmnt
, comment
, SMB_SHARE_CMNT_MAX
);
521 #else /* CONFIG_SMB_PRINTING */
524 * If not CONFIG_SMB_PRINTING, just provide a few "stubs".
539 smbd_load_printers(void)
544 smbd_spool_init(void)
549 smbd_spool_fini(void)
554 smbd_spool_start(void)
559 smbd_spool_stop(void)
563 #endif /* CONFIG_SMB_PRINTING */