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 <strings.h>
33 #include <syslog.h>
34 #include <signal.h>
35 #include <pthread.h>
36 #include <synch.h>
37 #include <dlfcn.h>
38 #include <errno.h>
39 #include <smbsrv/smb.h>
40 #include <smbsrv/smb_share.h>
41 #include "smbd.h"
42
43 #ifdef HAVE_CUPS
44 #include <cups/cups.h>
45
46 #define SMB_SPOOL_WAIT 2
47 #define SMBD_PJOBLEN 256
48 #define SMBD_PRINTER "Postscript"
49 #define SMBD_FN_PREFIX "cifsprintjob-"
50 #define SMBD_CUPS_SPOOL_DIR "//var//spool//cups"
51 #define SMBD_CUPS_DOCNAME "generic_doc"
52
53 typedef struct smbd_printjob {
54 pid_t pj_pid;
55 int pj_sysjob;
56 int pj_fd;
57 time_t pj_start_time;
58 int pj_status;
59 size_t pj_size;
60 int pj_page_count;
61 boolean_t pj_isspooled;
62 boolean_t pj_jobnum;
63 char pj_filename[SMBD_PJOBLEN];
64 char pj_jobname[SMBD_PJOBLEN];
65 char pj_username[SMBD_PJOBLEN];
66 char pj_queuename[SMBD_PJOBLEN];
67 } smbd_printjob_t;
68
69 typedef struct smb_cups_ops {
70 void *cups_hdl;
71 cups_lang_t *(*cupsLangDefault)();
72 const char *(*cupsLangEncoding)(cups_lang_t *);
73 void (*cupsLangFree)(cups_lang_t *);
74 ipp_status_t (*cupsLastError)();
75 int (*cupsGetDests)(cups_dest_t **);
76 void (*cupsFreeDests)(int, cups_dest_t *);
77 ipp_t *(*cupsDoFileRequest)(http_t *, ipp_t *,
78 const char *, const char *);
79 ipp_t *(*ippNew)();
80 void (*ippDelete)();
81 char *(*ippErrorString)();
82 ipp_attribute_t *(*ippAddString)();
83 void (*httpClose)(http_t *);
84 http_t *(*httpConnect)(const char *, int);
85 } smb_cups_ops_t;
86
87 static uint32_t smbd_cups_jobnum = 1;
88 static smb_cups_ops_t smb_cups;
89 static mutex_t smbd_cups_mutex;
90
91 static void *smbd_spool_monitor(void *);
92 static smb_cups_ops_t *smbd_cups_ops(void);
93 static void smbd_print_share_comment(smb_share_t *, cups_dest_t *);
94 static void *smbd_share_printers(void *);
95 static void smbd_spool_copyfile(smb_inaddr_t *, char *, char *, char *);
96
97 extern smbd_t smbd;
98
99 /*
100 * Start the spool thread.
101 * Returns 0 on success, an error number if thread creation fails.
102 */
103 void
104 smbd_spool_start(void)
105 {
106 pthread_attr_t attr;
107 int rc;
108
109 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
110 return;
111
112 (void) pthread_attr_init(&attr);
113 (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
114 rc = pthread_create(&smbd.s_spool_tid, &attr, smbd_spool_monitor, NULL);
115 (void) pthread_attr_destroy(&attr);
116
117 if (rc != 0)
118 smb_log(smbd.s_loghd, LOG_NOTICE,
119 "failed to start print monitor: %s", strerror(errno));
120 }
121
122 /*
123 * A single pthread_kill should be sufficient but we include
124 * a couple of retries to avoid implementation idiosyncrasies
125 * around signal delivery.
126 */
127 void
128 smbd_spool_stop(void)
129 {
130 int i;
131
132 if (pthread_self() == smbd.s_spool_tid)
133 return;
134
135 for (i = 0; i < 3 && smbd.s_spool_tid != 0; ++i) {
136 if (pthread_kill(smbd.s_spool_tid, SIGTERM) == ESRCH)
137 break;
138
139 (void) sleep(1);
140 }
141 }
142
143 /*
144 * This thread blocks waiting for close print file in the kernel.
145 * It then uses the data returned from the ioctl to copy the spool file
146 * into the cups spooler.
147 *
148 * This mechanism is really only used by Windows Vista and Windows 7.
149 * Other versions of Windows create a zero size file, which is removed
150 * by smbd_spool_copyfile.
151 */
152 /*ARGSUSED*/
153 static void *
154 smbd_spool_monitor(void *arg)
155 {
156 uint32_t spool_num;
157 char username[MAXNAMELEN];
158 char path[MAXPATHLEN];
159 smb_inaddr_t ipaddr;
160 int error_retry_cnt = 5;
161
162 smbd_online_wait("smbd_spool_monitor");
163
164 spoolss_register_copyfile(smbd_spool_copyfile);
165
166 while (!smbd.s_shutting_down && (error_retry_cnt > 0)) {
167 errno = 0;
168
169 if (smb_kmod_get_spool_doc(&spool_num, username,
170 path, &ipaddr) == 0) {
171 smbd_spool_copyfile(&ipaddr,
172 username, path, SMBD_CUPS_DOCNAME);
173 error_retry_cnt = 5;
174 } else {
175 if (errno == ECANCELED)
176 break;
177 if ((errno != EINTR) && (errno != EAGAIN))
178 error_retry_cnt--;
179 (void) sleep(SMB_SPOOL_WAIT);
180 }
181 }
182
183 spoolss_register_copyfile(NULL);
184 smbd.s_spool_tid = 0;
185 return (NULL);
186 }
187
188 /*
189 * All versions of windows use this function to spool files to a printer
190 * via the cups interface
191 */
192 static void
193 smbd_spool_copyfile(smb_inaddr_t *ipaddr, char *username, char *path,
194 char *doc_name)
195 {
196 smb_cups_ops_t *cups;
197 http_t *http = NULL; /* HTTP connection to server */
198 ipp_t *request = NULL; /* IPP Request */
199 ipp_t *response = NULL; /* IPP Response */
200 cups_lang_t *language = NULL; /* Default language */
201 char uri[HTTP_MAX_URI]; /* printer-uri attribute */
202 char new_jobname[SMBD_PJOBLEN];
203 smbd_printjob_t pjob;
204 char clientname[INET6_ADDRSTRLEN];
205 struct stat sbuf;
206 int rc = 1;
207
208 if (stat(path, &sbuf)) {
209 smb_log(smbd.s_loghd, LOG_INFO, "smbd_spool_copyfile: %s: %s",
210 path, strerror(errno));
211 return;
212 }
213
214 /*
215 * Remove zero size files and return; these were inadvertantly
216 * created by XP or 2000.
217 */
218 if (sbuf.st_size == 0) {
219 if (remove(path) != 0)
220 smb_log(smbd.s_loghd, LOG_INFO,
221 "smbd_spool_copyfile: cannot remove %s: %s",
222 path, strerror(errno));
223 return;
224 }
225
226 if ((cups = smbd_cups_ops()) == NULL)
227 return;
228
229 if ((http = cups->httpConnect("localhost", 631)) == NULL) {
230 smb_log(smbd.s_loghd, LOG_INFO,
231 "smbd_spool_copyfile: cupsd not running");
232 return;
233 }
234
235 if ((request = cups->ippNew()) == NULL) {
236 smb_log(smbd.s_loghd, LOG_INFO,
237 "smbd_spool_copyfile: ipp not running");
238 return;
239 }
240
241 request->request.op.operation_id = IPP_PRINT_JOB;
242 request->request.op.request_id = 1;
243 language = cups->cupsLangDefault();
244
245 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_CHARSET,
246 "attributes-charset", NULL, cups->cupsLangEncoding(language));
247
248 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_LANGUAGE,
249 "attributes-natural-language", NULL, language->language);
250
251 (void) snprintf(uri, sizeof (uri), "ipp://localhost/printers/%s",
252 SMBD_PRINTER);
253 pjob.pj_pid = pthread_self();
254 pjob.pj_sysjob = 10;
255 (void) strlcpy(pjob.pj_filename, path, SMBD_PJOBLEN);
256 pjob.pj_start_time = time(NULL);
257 pjob.pj_status = 2;
258 pjob.pj_size = sbuf.st_blocks * 512;
259 pjob.pj_page_count = 1;
260 pjob.pj_isspooled = B_TRUE;
261 pjob.pj_jobnum = smbd_cups_jobnum;
262
263 (void) strlcpy(pjob.pj_jobname, doc_name, SMBD_PJOBLEN);
264 (void) strlcpy(pjob.pj_username, username, SMBD_PJOBLEN);
265 (void) strlcpy(pjob.pj_queuename, SMBD_CUPS_SPOOL_DIR, SMBD_PJOBLEN);
266
267 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_URI,
268 "printer-uri", NULL, uri);
269
270 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
271 "requesting-user-name", NULL, pjob.pj_username);
272
273 if (smb_inet_ntop(ipaddr, clientname,
274 SMB_IPSTRLEN(ipaddr->a_family)) == NULL) {
275 smb_log(smbd.s_loghd, LOG_INFO,
276 "smbd_spool_copyfile: %s: unknown client", clientname);
277 goto out;
278 }
279
280 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
281 "job-originating-host-name", NULL, clientname);
282
283 (void) snprintf(new_jobname, SMBD_PJOBLEN, "%s%d",
284 SMBD_FN_PREFIX, pjob.pj_jobnum);
285 cups->ippAddString(request, IPP_TAG_OPERATION, IPP_TAG_NAME,
286 "job-name", NULL, new_jobname);
287
288 (void) snprintf(uri, sizeof (uri) - 1, "/printers/%s", SMBD_PRINTER);
289
290 response = cups->cupsDoFileRequest(http, request, uri,
291 pjob.pj_filename);
292 if (response != NULL) {
293 if (response->request.status.status_code >= IPP_OK_CONFLICT) {
294 smb_log(smbd.s_loghd, LOG_ERR,
295 "smbd_spool_copyfile: printer %s: %s",
296 SMBD_PRINTER,
297 cups->ippErrorString(cups->cupsLastError()));
298 } else {
299 atomic_inc_32(&smbd_cups_jobnum);
300 rc = 0;
301 }
302 } else {
303 smb_log(smbd.s_loghd, LOG_ERR,
304 "smbd_spool_copyfile: unable to print to %s",
305 cups->ippErrorString(cups->cupsLastError()));
306 }
307
308 if (rc == 0)
309 (void) unlink(pjob.pj_filename);
310
311 out:
312 if (response)
313 cups->ippDelete(response);
314
315 if (language)
316 cups->cupsLangFree(language);
317
318 if (http)
319 cups->httpClose(http);
320 }
321
322 int
323 smbd_cups_init(void)
324 {
325 (void) mutex_lock(&smbd_cups_mutex);
326
327 if (smb_cups.cups_hdl != NULL) {
328 (void) mutex_unlock(&smbd_cups_mutex);
329 return (0);
330 }
331
332 if ((smb_cups.cups_hdl = dlopen("libcups.so.2", RTLD_NOW)) == NULL) {
333 (void) mutex_unlock(&smbd_cups_mutex);
334 smb_log(smbd.s_loghd, LOG_DEBUG,
335 "smbd_cups_init: cannot open libcups");
336 return (ENOENT);
337 }
338
339 smb_cups.cupsLangDefault =
340 (cups_lang_t *(*)())dlsym(smb_cups.cups_hdl, "cupsLangDefault");
341 smb_cups.cupsLangEncoding = (const char *(*)(cups_lang_t *))
342 dlsym(smb_cups.cups_hdl, "cupsLangEncoding");
343 smb_cups.cupsDoFileRequest =
344 (ipp_t *(*)(http_t *, ipp_t *, const char *, const char *))
345 dlsym(smb_cups.cups_hdl, "cupsDoFileRequest");
346 smb_cups.cupsLastError = (ipp_status_t (*)())
347 dlsym(smb_cups.cups_hdl, "cupsLastError");
348 smb_cups.cupsLangFree = (void (*)(cups_lang_t *))
349 dlsym(smb_cups.cups_hdl, "cupsLangFree");
350 smb_cups.cupsGetDests = (int (*)(cups_dest_t **))
351 dlsym(smb_cups.cups_hdl, "cupsGetDests");
352 smb_cups.cupsFreeDests = (void (*)(int, cups_dest_t *))
353 dlsym(smb_cups.cups_hdl, "cupsFreeDests");
354
355 smb_cups.httpClose = (void (*)(http_t *))
356 dlsym(smb_cups.cups_hdl, "httpClose");
357 smb_cups.httpConnect = (http_t *(*)(const char *, int))
358 dlsym(smb_cups.cups_hdl, "httpConnect");
359
360 smb_cups.ippNew = (ipp_t *(*)())dlsym(smb_cups.cups_hdl, "ippNew");
361 smb_cups.ippDelete = (void (*)())dlsym(smb_cups.cups_hdl, "ippDelete");
362 smb_cups.ippErrorString = (char *(*)())
363 dlsym(smb_cups.cups_hdl, "ippErrorString");
364 smb_cups.ippAddString = (ipp_attribute_t *(*)())
365 dlsym(smb_cups.cups_hdl, "ippAddString");
366
367 if (smb_cups.cupsLangDefault == NULL ||
368 smb_cups.cupsLangEncoding == NULL ||
369 smb_cups.cupsDoFileRequest == NULL ||
370 smb_cups.cupsLastError == NULL ||
371 smb_cups.cupsLangFree == NULL ||
372 smb_cups.cupsGetDests == NULL ||
373 smb_cups.cupsFreeDests == NULL ||
374 smb_cups.ippNew == NULL ||
375 smb_cups.httpClose == NULL ||
376 smb_cups.httpConnect == NULL ||
377 smb_cups.ippDelete == NULL ||
378 smb_cups.ippErrorString == NULL ||
379 smb_cups.ippAddString == NULL) {
380 (void) dlclose(smb_cups.cups_hdl);
381 smb_cups.cups_hdl = NULL;
382 (void) mutex_unlock(&smbd_cups_mutex);
383 smb_log(smbd.s_loghd, LOG_DEBUG,
384 "smbd_cups_init: cannot load libcups");
385 return (ENOENT);
386 }
387
388 (void) mutex_unlock(&smbd_cups_mutex);
389 return (0);
390 }
391
392 void
393 smbd_cups_fini(void)
394 {
395 (void) mutex_lock(&smbd_cups_mutex);
396
397 if (smb_cups.cups_hdl != NULL) {
398 (void) dlclose(smb_cups.cups_hdl);
399 smb_cups.cups_hdl = NULL;
400 }
401
402 (void) mutex_unlock(&smbd_cups_mutex);
403 }
404
405 static smb_cups_ops_t *
406 smbd_cups_ops(void)
407 {
408 if (smb_cups.cups_hdl == NULL)
409 return (NULL);
410
411 return (&smb_cups);
412 }
413
414 void
415 smbd_load_printers(void)
416 {
417 pthread_t tid;
418 pthread_attr_t attr;
419 int rc;
420
421 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
422 return;
423
424 (void) pthread_attr_init(&attr);
425 (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
426 rc = pthread_create(&tid, &attr, smbd_share_printers, &tid);
427 (void) pthread_attr_destroy(&attr);
428
429 if (rc != 0)
430 smb_log(smbd.s_loghd, LOG_NOTICE,
431 "unable to load printer shares: %s", strerror(errno));
432 }
433
434 /*
435 * All print shares use the path from print$.
436 */
437 /*ARGSUSED*/
438 static void *
439 smbd_share_printers(void *arg)
440 {
441 cups_dest_t *dests;
442 cups_dest_t *dest;
443 smb_cups_ops_t *cups;
444 smb_share_t si;
445 uint32_t nerr;
446 int num_dests;
447 int i;
448
449 if (!smb_config_getbool(SMB_CI_PRINT_ENABLE))
450 return (NULL);
451
452 if ((cups = smbd_cups_ops()) == NULL)
453 return (NULL);
454
455 if (smb_shr_get(SMB_SHARE_PRINT, &si) != NERR_Success) {
456 smb_log(smbd.s_loghd, LOG_DEBUG,
457 "smbd_share_printers unable to load %s", SMB_SHARE_PRINT);
458 return (NULL);
459 }
460
461 num_dests = cups->cupsGetDests(&dests);
462
463 for (i = num_dests, dest = dests; i > 0; i--, dest++) {
464 if (dest->instance != NULL)
465 continue;
466
467 (void) strlcpy(si.shr_name, dest->name, MAXPATHLEN);
468 smbd_print_share_comment(&si, dest);
469 si.shr_type = STYPE_PRINTQ;
470
471 nerr = smb_shr_add(&si);
472 if (nerr == NERR_Success || nerr == NERR_DuplicateShare)
473 smb_log(smbd.s_loghd, LOG_DEBUG,
474 "shared printer: %s", si.shr_name);
475 else
476 smb_log(smbd.s_loghd, LOG_DEBUG,
477 "smbd_share_printers: unable to add share %s: %u",
478 si.shr_name, nerr);
479 }
480
481 cups->cupsFreeDests(num_dests, dests);
482 return (NULL);
483 }
484
485 static void
486 smbd_print_share_comment(smb_share_t *si, cups_dest_t *dest)
487 {
488 cups_option_t *options;
489 char *comment;
490 char *name;
491 char *value;
492 int i;
493
494 comment = "Print Share";
495
496 if ((options = dest->options) == NULL) {
497 (void) strlcpy(si->shr_cmnt, comment, SMB_SHARE_CMNT_MAX);
498 return;
499 }
500
501 for (i = 0; i < dest->num_options; ++i) {
502 name = options[i].name;
503 value = options[i].value;
504
505 if (name == NULL || value == NULL ||
506 *name == '\0' || *value == '\0')
507 continue;
508
509 if (strcasecmp(name, "printer-info") == 0) {
510 comment = value;
511 break;
512 }
513 }
514
515 (void) strlcpy(si->shr_cmnt, comment, SMB_SHARE_CMNT_MAX);
516 }
517
518 #else /* HAVE_CUPS */
519
520 /*
521 * If not HAVE_CUPS, just provide a few "stubs".
522 */
523
524 int
525 smbd_cups_init(void)
526 {
527 return (ENOENT);
528 }
529
530 void
531 smbd_cups_fini(void)
532 {
533 }
534
535 void
536 smbd_load_printers(void)
537 {
538 }
539
540 void
541 smbd_spool_init(void)
542 {
543 }
544
545 void
546 smbd_spool_fini(void)
547 {
548 }
549
550 void
551 smbd_spool_start(void)
552 {
553 }
554
555 void
556 smbd_spool_stop(void)
557 {
558 }
559
560 #endif /* HAVE_CUPS */