1 /*
2 * CDDL HEADER START
3 *
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.
7 *
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.
12 *
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]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
24 */
25
26 /*
27 * CUPS support for the SMB and SPOOLSS print services.
28 */
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <sys/atomic.h>
33 #include <strings.h>
34 #include <syslog.h>
35 #include <signal.h>
36 #include <pthread.h>
37 #include <synch.h>
38 #include <dlfcn.h>
39 #include <errno.h>
40 #include <smbsrv/smb.h>
41 #include <smbsrv/smb_share.h>
42 #include "smbd.h"
43
44 #ifdef HAVE_CUPS
45 #include <cups/cups.h>
46
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"
53
54 typedef struct smbd_printjob {
55 pid_t pj_pid;
56 int pj_sysjob;
57 int pj_fd;
58 time_t pj_start_time;
59 int pj_status;
60 size_t pj_size;
61 int pj_page_count;
62 boolean_t pj_isspooled;
63 boolean_t pj_jobnum;
64 char pj_filename[SMBD_PJOBLEN];
65 char pj_jobname[SMBD_PJOBLEN];
66 char pj_username[SMBD_PJOBLEN];
67 char pj_queuename[SMBD_PJOBLEN];
68 } smbd_printjob_t;
69
70 typedef struct smb_cups_ops {
71 void *cups_hdl;
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 *);
80 ipp_t *(*ippNew)();
81 void (*ippDelete)();
82 char *(*ippErrorString)();
83 ipp_attribute_t *(*ippAddString)();
84 void (*httpClose)(http_t *);
85 http_t *(*httpConnect)(const char *, int);
86 } smb_cups_ops_t;
87
88 static uint32_t smbd_cups_jobnum = 1;
89 static smb_cups_ops_t smb_cups;
90 static mutex_t smbd_cups_mutex;
91
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 *);
97
98 extern smbd_t smbd;
99
100 /*
101 * Start the spool thread.
102 * Returns 0 on success, an error number if thread creation fails.
103 */
104 void
smbd_spool_start(void)105 smbd_spool_start(void)
106 {
107 pthread_attr_t attr;
108 int rc;
109
110 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
111 return;
112
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);
117
118 if (rc != 0)
119 syslog(LOG_NOTICE,
120 "failed to start print monitor: %s", strerror(errno));
121 }
122
123 /*
124 * A single pthread_kill should be sufficient but we include
125 * a couple of retries to avoid implementation idiosyncrasies
126 * around signal delivery.
127 */
128 void
smbd_spool_stop(void)129 smbd_spool_stop(void)
130 {
131 int i;
132
133 if (pthread_self() == smbd.s_spool_tid)
134 return;
135
136 for (i = 0; i < 3 && smbd.s_spool_tid != 0; ++i) {
137 if (pthread_kill(smbd.s_spool_tid, SIGTERM) == ESRCH)
138 break;
139
140 (void) sleep(1);
141 }
142 }
143
144 /*
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.
148 *
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.
152 */
153 /*ARGSUSED*/
154 static void *
smbd_spool_monitor(void * arg)155 smbd_spool_monitor(void *arg)
156 {
157 uint32_t spool_num;
158 char username[MAXNAMELEN];
159 char path[MAXPATHLEN];
160 smb_inaddr_t ipaddr;
161 int error_retry_cnt = 5;
162
163 smbd_online_wait("smbd_spool_monitor");
164
165 spoolss_register_copyfile(smbd_spool_copyfile);
166
167 while (!smbd.s_shutting_down && (error_retry_cnt > 0)) {
168 errno = 0;
169
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);
174 error_retry_cnt = 5;
175 } else {
176 if (errno == ECANCELED)
177 break;
178 if ((errno != EINTR) && (errno != EAGAIN))
179 error_retry_cnt--;
180 (void) sleep(SMB_SPOOL_WAIT);
181 }
182 }
183
184 spoolss_register_copyfile(NULL);
185 smbd.s_spool_tid = 0;
186 return (NULL);
187 }
188
189 /*
190 * All versions of windows use this function to spool files to a printer
191 * via the cups interface
192 */
193 static void
smbd_spool_copyfile(smb_inaddr_t * ipaddr,char * username,char * path,char * doc_name)194 smbd_spool_copyfile(smb_inaddr_t *ipaddr, char *username, char *path,
195 char *doc_name)
196 {
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];
206 struct stat sbuf;
207 int rc = 1;
208
209 if (stat(path, &sbuf)) {
210 syslog(LOG_INFO, "smbd_spool_copyfile: %s: %s",
211 path, strerror(errno));
212 return;
213 }
214
215 /*
216 * Remove zero size files and return; these were inadvertantly
217 * created by XP or 2000.
218 */
219 if (sbuf.st_size == 0) {
220 if (remove(path) != 0)
221 syslog(LOG_INFO,
222 "smbd_spool_copyfile: cannot remove %s: %s",
223 path, strerror(errno));
224 return;
225 }
226
227 if ((cups = smbd_cups_ops()) == NULL)
228 return;
229
230 if ((http = cups->httpConnect("localhost", 631)) == NULL) {
231 syslog(LOG_INFO,
232 "smbd_spool_copyfile: cupsd not running");
233 return;
234 }
235
236 if ((request = cups->ippNew()) == NULL) {
237 syslog(LOG_INFO,
238 "smbd_spool_copyfile: ipp not running");
239 return;
240 }
241
242 request->request.op.operation_id = IPP_PRINT_JOB;
243 request->request.op.request_id = 1;
244 language = cups->cupsLangDefault();
245
246 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
247 "attributes-charset", NULL, cups->cupsLangEncoding(language));
248
249 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
250 "attributes-natural-language", NULL, language->language);
251
252 (void) snprintf(uri, sizeof (uri), "ipp://localhost/printers/%s",
253 SMBD_PRINTER);
254 pjob.pj_pid = pthread_self();
255 pjob.pj_sysjob = 10;
256 (void) strlcpy(pjob.pj_filename, path, SMBD_PJOBLEN);
257 pjob.pj_start_time = time(NULL);
258 pjob.pj_status = 2;
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;
263
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);
267
268 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
269 "printer-uri", NULL, uri);
270
271 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
272 "requesting-user-name", NULL, pjob.pj_username);
273
274 if (smb_inet_ntop(ipaddr, clientname,
275 SMB_IPSTRLEN(ipaddr->a_family)) == NULL) {
276 syslog(LOG_INFO,
277 "smbd_spool_copyfile: %s: unknown client", clientname);
278 goto out;
279 }
280
281 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
282 "job-originating-host-name", NULL, clientname);
283
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);
288
289 (void) snprintf(uri, sizeof (uri) - 1, "/printers/%s", SMBD_PRINTER);
290
291 response = cups->cupsDoFileRequest(http, request, uri,
292 pjob.pj_filename);
293 if (response != NULL) {
294 if (response->request.status.status_code >= IPP_OK_CONFLICT) {
295 syslog(LOG_ERR,
296 "smbd_spool_copyfile: printer %s: %s",
297 SMBD_PRINTER,
298 cups->ippErrorString(cups->cupsLastError()));
299 } else {
300 atomic_inc_32(&smbd_cups_jobnum);
301 rc = 0;
302 }
303 } else {
304 syslog(LOG_ERR,
305 "smbd_spool_copyfile: unable to print to %s",
306 cups->ippErrorString(cups->cupsLastError()));
307 }
308
309 if (rc == 0)
310 (void) unlink(pjob.pj_filename);
311
312 out:
313 if (response)
314 cups->ippDelete(response);
315
316 if (language)
317 cups->cupsLangFree(language);
318
319 if (http)
320 cups->httpClose(http);
321 }
322
323 int
smbd_cups_init(void)324 smbd_cups_init(void)
325 {
326 (void) mutex_lock(&smbd_cups_mutex);
327
328 if (smb_cups.cups_hdl != NULL) {
329 (void) mutex_unlock(&smbd_cups_mutex);
330 return (0);
331 }
332
333 if ((smb_cups.cups_hdl = dlopen("libcups.so.2", RTLD_NOW)) == NULL) {
334 (void) mutex_unlock(&smbd_cups_mutex);
335 syslog(LOG_DEBUG,
336 "smbd_cups_init: cannot open libcups");
337 return (ENOENT);
338 }
339
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");
355
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");
360
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");
367
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);
384 syslog(LOG_DEBUG,
385 "smbd_cups_init: cannot load libcups");
386 return (ENOENT);
387 }
388
389 (void) mutex_unlock(&smbd_cups_mutex);
390 return (0);
391 }
392
393 void
smbd_cups_fini(void)394 smbd_cups_fini(void)
395 {
396 (void) mutex_lock(&smbd_cups_mutex);
397
398 if (smb_cups.cups_hdl != NULL) {
399 (void) dlclose(smb_cups.cups_hdl);
400 smb_cups.cups_hdl = NULL;
401 }
402
403 (void) mutex_unlock(&smbd_cups_mutex);
404 }
405
406 static smb_cups_ops_t *
smbd_cups_ops(void)407 smbd_cups_ops(void)
408 {
409 if (smb_cups.cups_hdl == NULL)
410 return (NULL);
411
412 return (&smb_cups);
413 }
414
415 void
smbd_load_printers(void)416 smbd_load_printers(void)
417 {
418 pthread_t tid;
419 pthread_attr_t attr;
420 int rc;
421
422 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
423 return;
424
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);
429
430 if (rc != 0)
431 syslog(LOG_NOTICE,
432 "unable to load printer shares: %s", strerror(errno));
433 }
434
435 /*
436 * All print shares use the path from print$.
437 */
438 /*ARGSUSED*/
439 static void *
smbd_share_printers(void * arg)440 smbd_share_printers(void *arg)
441 {
442 cups_dest_t *dests;
443 cups_dest_t *dest;
444 smb_cups_ops_t *cups;
445 smb_share_t si;
446 uint32_t nerr;
447 int num_dests;
448 int i;
449
450 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
451 return (NULL);
452
453 if ((cups = smbd_cups_ops()) == NULL)
454 return (NULL);
455
456 if (smb_shr_get(SMB_SHARE_PRINT, &si) != NERR_Success) {
457 syslog(LOG_DEBUG,
458 "smbd_share_printers unable to load %s", SMB_SHARE_PRINT);
459 return (NULL);
460 }
461
462 num_dests = cups->cupsGetDests(&dests);
463
464 for (i = num_dests, dest = dests; i > 0; i--, dest++) {
465 if (dest->instance != NULL)
466 continue;
467
468 (void) strlcpy(si.shr_name, dest->name, MAXPATHLEN);
469 smbd_print_share_comment(&si, dest);
470 si.shr_type = STYPE_PRINTQ;
471
472 nerr = smb_shr_add(&si);
473 if (nerr == NERR_Success || nerr == NERR_DuplicateShare)
474 syslog(LOG_DEBUG,
475 "shared printer: %s", si.shr_name);
476 else
477 syslog(LOG_DEBUG,
478 "smbd_share_printers: unable to add share %s: %u",
479 si.shr_name, nerr);
480 }
481
482 cups->cupsFreeDests(num_dests, dests);
483 return (NULL);
484 }
485
486 static void
smbd_print_share_comment(smb_share_t * si,cups_dest_t * dest)487 smbd_print_share_comment(smb_share_t *si, cups_dest_t *dest)
488 {
489 cups_option_t *options;
490 char *comment;
491 char *name;
492 char *value;
493 int i;
494
495 comment = "Print Share";
496
497 if ((options = dest->options) == NULL) {
498 (void) strlcpy(si->shr_cmnt, comment, SMB_SHARE_CMNT_MAX);
499 return;
500 }
501
502 for (i = 0; i < dest->num_options; ++i) {
503 name = options[i].name;
504 value = options[i].value;
505
506 if (name == NULL || value == NULL ||
507 *name == '\0' || *value == '\0')
508 continue;
509
510 if (strcasecmp(name, "printer-info") == 0) {
511 comment = value;
512 break;
513 }
514 }
515
516 (void) strlcpy(si->shr_cmnt, comment, SMB_SHARE_CMNT_MAX);
517 }
518
519 #else /* HAVE_CUPS */
520
521 /*
522 * If not HAVE_CUPS, just provide a few "stubs".
523 */
524
525 int
smbd_cups_init(void)526 smbd_cups_init(void)
527 {
528 return (ENOENT);
529 }
530
531 void
smbd_cups_fini(void)532 smbd_cups_fini(void)
533 {
534 }
535
536 void
smbd_load_printers(void)537 smbd_load_printers(void)
538 {
539 }
540
541 void
smbd_spool_init(void)542 smbd_spool_init(void)
543 {
544 }
545
546 void
smbd_spool_fini(void)547 smbd_spool_fini(void)
548 {
549 }
550
551 void
smbd_spool_start(void)552 smbd_spool_start(void)
553 {
554 }
555
556 void
smbd_spool_stop(void)557 smbd_spool_stop(void)
558 {
559 }
560
561 #endif /* HAVE_CUPS */
562