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