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