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