1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"$Id: ipp-support.c,v 1.8 2006/02/27 06:10:10 njacobs Exp $"
7 
8 #include <papi_impl.h>
9 #include <stdlib.h>
10 #include <pwd.h>
11 #include <locale.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <sys/stat.h>
15 
16 #include <config-site.h>
17 
18 papi_status_t
http_to_papi_status(http_status_t status)19 http_to_papi_status(http_status_t status)
20 {
21 	switch (status) {
22 	case HTTP_OK:
23 		return (PAPI_OK);
24 	case HTTP_BAD_REQUEST:
25 		return (PAPI_BAD_REQUEST);
26 	case HTTP_UNAUTHORIZED:
27 	case HTTP_FORBIDDEN:
28 		return (PAPI_NOT_AUTHORIZED);
29 	case HTTP_NOT_FOUND:
30 		return (PAPI_NOT_FOUND);
31 	case HTTP_GONE:
32 		return (PAPI_GONE);
33 	case HTTP_SERVICE_UNAVAILABLE:
34 		return (PAPI_SERVICE_UNAVAILABLE);
35 	default:
36 		return ((papi_status_t)status);
37 	}
38 }
39 
40 papi_status_t
ipp_to_papi_status(uint16_t status)41 ipp_to_papi_status(uint16_t status)
42 {
43 	switch (status) {
44 	case IPP_OK:
45 		return (PAPI_OK);
46 	case IPP_OK_IGNORED_ATTRIBUTES:
47 		return (PAPI_OK);
48 	case IPP_OK_CONFLICTING_ATTRIBUTES:
49 		return (PAPI_OK);
50 	case IPP_OK_IGNORED_SUBSCRIPTIONS:
51 		return (PAPI_OK_IGNORED_SUBSCRIPTIONS);
52 	case IPP_OK_IGNORED_NOTIFICATIONS:
53 		return (PAPI_OK_IGNORED_NOTIFICATIONS);
54 	case IPP_CERR_BAD_REQUEST:
55 		return (PAPI_BAD_REQUEST);
56 	case IPP_CERR_FORBIDDEN:
57 		return (PAPI_FORBIDDEN);
58 	case IPP_CERR_NOT_AUTHENTICATED:
59 		return (PAPI_NOT_AUTHENTICATED);
60 	case IPP_CERR_NOT_AUTHORIZED:
61 		return (PAPI_NOT_AUTHORIZED);
62 	case IPP_CERR_NOT_POSSIBLE:
63 		return (PAPI_NOT_POSSIBLE);
64 	case IPP_CERR_TIMEOUT:
65 		return (PAPI_TIMEOUT);
66 	case IPP_CERR_NOT_FOUND:
67 		return (PAPI_NOT_FOUND);
68 	case IPP_CERR_GONE:
69 		return (PAPI_GONE);
70 	case IPP_CERR_REQUEST_ENTITY:
71 		return (PAPI_REQUEST_ENTITY);
72 	case IPP_CERR_REQUEST_VALUE:
73 		return (PAPI_REQUEST_VALUE);
74 	case IPP_CERR_DOCUMENT_FORMAT:
75 		return (PAPI_DOCUMENT_FORMAT);
76 	case IPP_CERR_ATTRIBUTES:
77 		return (PAPI_ATTRIBUTES);
78 	case IPP_CERR_URI_SCHEME:
79 		return (PAPI_URI_SCHEME);
80 	case IPP_CERR_CHARSET:
81 		return (PAPI_CHARSET);
82 	case IPP_CERR_CONFLICT:
83 		return (PAPI_CONFLICT);
84 	case IPP_CERR_COMPRESSION_NOT_SUPPORTED:
85 		return (PAPI_COMPRESSION_NOT_SUPPORTED);
86 	case IPP_CERR_COMPRESSION_ERROR:
87 		return (PAPI_COMPRESSION_ERROR);
88 	case IPP_CERR_DOCUMENT_FORMAT_ERROR:
89 		return (PAPI_DOCUMENT_FORMAT_ERROR);
90 	case IPP_CERR_DOCUMENT_ACCESS_ERROR:
91 		return (PAPI_DOCUMENT_ACCESS_ERROR);
92 	case IPP_CERR_ATTRIBUTES_NOT_SETTABLE:
93 		return (PAPI_ATTRIBUTES_NOT_SETTABLE);
94 	case IPP_CERR_IGNORED_ALL_SUBSCRIPTIONS:
95 		return (PAPI_IGNORED_ALL_SUBSCRIPTIONS);
96 	case IPP_CERR_TOO_MANY_SUBSCRIPTIONS:
97 		return (PAPI_TOO_MANY_SUBSCRIPTIONS);
98 	case IPP_CERR_IGNORED_ALL_NOTIFICATIONS:
99 		return (PAPI_IGNORED_ALL_NOTIFICATIONS);
100 	case IPP_CERR_PRINT_SUPPORT_FILE_NOT_FOUND:
101 		return (PAPI_PRINT_SUPPORT_FILE_NOT_FOUND);
102 	case IPP_SERR_INTERNAL:
103 		return (PAPI_INTERNAL_ERROR);
104 	case IPP_SERR_OPERATION_NOT_SUPPORTED:
105 		return (PAPI_OPERATION_NOT_SUPPORTED);
106 	case IPP_SERR_SERVICE_UNAVAILABLE:
107 		return (PAPI_SERVICE_UNAVAILABLE);
108 	case IPP_SERR_VERSION_NOT_SUPPORTED:
109 		return (PAPI_VERSION_NOT_SUPPORTED);
110 	case IPP_SERR_DEVICE_ERROR:
111 		return (PAPI_DEVICE_ERROR);
112 	case IPP_SERR_TEMPORARY_ERROR:
113 		return (PAPI_TEMPORARY_ERROR);
114 	case IPP_SERR_NOT_ACCEPTING:
115 		return (PAPI_NOT_ACCEPTING);
116 	case IPP_SERR_BUSY:
117 	case IPP_SERR_CANCELLED:
118 	default:
119 		return (PAPI_TEMPORARY_ERROR);
120 	}
121 }
122 
123 void
ipp_initialize_request(service_t * svc,papi_attribute_t *** request,uint16_t operation)124 ipp_initialize_request(service_t *svc, papi_attribute_t ***request,
125 		uint16_t operation)
126 {
127 	papiAttributeListAddInteger(request, PAPI_ATTR_EXCL,
128 			"version-major", 1);
129 	papiAttributeListAddInteger(request, PAPI_ATTR_EXCL,
130 			"version-minor", 1);
131 	papiAttributeListAddInteger(request, PAPI_ATTR_EXCL,
132 			"request-id", (short)lrand48());
133 	papiAttributeListAddInteger(request, PAPI_ATTR_EXCL,
134 			"operation-id", operation);
135 }
136 
137 void
ipp_initialize_operational_attributes(service_t * svc,papi_attribute_t *** op,papi_attribute_t ** attributes)138 ipp_initialize_operational_attributes(service_t *svc, papi_attribute_t ***op,
139 		papi_attribute_t **attributes)
140 {
141 	char *charset = "utf-8"; /* default to UTF-8 encoding */
142 	char *language = setlocale(LC_ALL, "");
143 	char *user = "nobody";
144 	struct passwd *pw = NULL;
145 
146 	/*
147 	 * All IPP requests must contain the following:
148 	 * 	attributes-charset		(UTF-8)
149 	 *	attributes-natural-language	(our current locale)
150 	 *	requesting-user-name		(process user)
151 	 */
152 	papiAttributeListGetString(attributes, NULL,
153 			"attributes-charset", &charset);
154 	papiAttributeListAddString(op, PAPI_ATTR_EXCL,
155 			"attributes-charset", charset);
156 
157 	papiAttributeListGetString(attributes, NULL,
158 			"attributes-natural-language", &language);
159 	papiAttributeListAddString(op, PAPI_ATTR_EXCL,
160 			"attributes-natural-language", language);
161 
162 	if ((pw = getpwuid(getuid())) != NULL)
163 		user = pw->pw_name;
164 	/*
165 	 * if our euid is 0 "super user", we will allow the system supplied
166 	 * user name to be overridden, if the requestor wants to.
167 	 */
168 	if (geteuid() == 0) {
169 		if (svc->user != NULL)
170 			user = svc->user;
171 		papiAttributeListGetString(attributes, NULL,
172 				"requesting-user-name", &user);
173 	}
174 	papiAttributeListAddString(op, PAPI_ATTR_REPLACE,
175 			"requesting-user-name", user);
176 }
177 
178 #ifndef OPID_CUPS_GET_DEFAULT	   /* for servers that will enumerate */
179 #define	OPID_CUPS_GET_DEFAULT	   0x4001
180 #endif  /* OPID_CUPS_GET_DEFAULT */
181 
182 static papi_status_t
_default_destination(service_t * svc,char ** uri)183 _default_destination(service_t *svc, char **uri)
184 {
185 	papi_status_t result = PAPI_INTERNAL_ERROR;
186 	printer_t *p = NULL;
187 	papi_attribute_t **request = NULL, **op = NULL, **response = NULL;
188 	char *tmp = NULL;
189 
190 	if ((svc == NULL) || (uri == NULL))
191 		return (PAPI_BAD_ARGUMENT);
192 
193 	/* we must be connected to find the default destination */
194 	if (svc->connection == NULL)
195 		return (PAPI_NOT_POSSIBLE);
196 
197 	if ((p = calloc(1, sizeof (*p))) == NULL)
198 		return (PAPI_TEMPORARY_ERROR);
199 
200 	ipp_initialize_request(svc, &request, OPID_CUPS_GET_DEFAULT);
201 	ipp_initialize_operational_attributes(svc, &op, NULL);
202 	papiAttributeListAddString(&op, PAPI_ATTR_APPEND,
203 			"requested-attributes", "printer-uri-supported");
204 	papiAttributeListAddCollection(&request, PAPI_ATTR_REPLACE,
205 			"operational-attributes-group", op);
206 	papiAttributeListFree(op);
207 	result = ipp_send_request(svc, request, &response);
208 	papiAttributeListFree(request);
209 
210 	op = NULL;
211 	papiAttributeListGetCollection(response, NULL,
212 			"printer-attributes-group", &op);
213 
214 	if (uri != NULL) {
215 		char *tmp = NULL;
216 
217 		papiAttributeListGetString(op, NULL, "printer-uri", &tmp);
218 		papiAttributeListGetString(op, NULL,
219 					"printer-uri-supported", &tmp);
220 		if (tmp != NULL)
221 			*uri = strdup(tmp);
222 	}
223 
224 	papiAttributeListFree(response);
225 
226 	return (result);
227 }
228 
229 void
ipp_add_printer_uri(service_t * svc,char * name,papi_attribute_t *** op)230 ipp_add_printer_uri(service_t *svc, char *name, papi_attribute_t ***op)
231 {
232 	char *uri = name;
233 	char buf[BUFSIZ];
234 	uri_t *tmp = NULL;
235 
236 	if (strstr(name, "://") == NULL) { /* not in URI form */
237 		if (strcmp(name, DEFAULT_DEST) != 0) {
238 			/* not the "default" */
239 			snprintf(buf, sizeof (buf), "%s/%s", svc->name, name);
240 			uri = buf;
241 		} else
242 			_default_destination(svc, &uri);
243 	}
244 
245 	papiAttributeListAddString(op, PAPI_ATTR_EXCL, "printer-uri", uri);
246 
247 	/* save the printer-uri's path to be used by http POST request */
248 	if ((uri_from_string(uri, &tmp) == 0) && (tmp != NULL)) {
249 		if (svc->post != NULL)
250 			free(svc->post);
251 		svc->post = strdup(tmp->path);
252 		uri_free(tmp);
253 	}
254 }
255 
256 
257 /*
258  * don't actually write anything, just add to the total size and return the
259  * size of what would be written, so we can figure out how big the request
260  * is going to be.
261  */
262 static ssize_t
size_calculate(void * fd,void * buffer,size_t length)263 size_calculate(void *fd, void *buffer, size_t length)
264 {
265 	ssize_t *size = (ssize_t *)fd;
266 
267 	*size += length;
268 	return (length);
269 }
270 
271 
272 static ssize_t
build_chunk(void * fd,void * buffer,size_t length)273 build_chunk(void *fd, void *buffer, size_t length)
274 {
275 	char **s1 = fd;
276 
277 	memcpy(*s1, buffer, length);
278 	*s1 = *s1 + length;
279 
280 	return (length);
281 }
282 
283 ssize_t
ipp_request_write(void * fd,void * buffer,size_t length)284 ipp_request_write(void *fd, void *buffer, size_t length)
285 {
286 	service_t *svc = (service_t *)fd;
287 
288 #ifdef DEBUG
289 	printf("ipp_request_write(0x%8.8x, 0x%8.8x, %d)\n", fd, buffer, length);
290 	httpDumpData(stdout, "ipp_request_write:", buffer, length);
291 #endif
292 	return (httpWrite(svc->connection, buffer, length));
293 }
294 
295 ssize_t
ipp_request_read(void * fd,void * buffer,size_t length)296 ipp_request_read(void *fd, void *buffer, size_t length)
297 {
298 	service_t *svc = (service_t *)fd;
299 	ssize_t rc, i = length;
300 	void *p = buffer;
301 
302 	while ((rc = httpRead(svc->connection, p, i)) != i) {
303 		if (rc == 0)
304 			return (rc);
305 		if (rc < 0)
306 			return (rc);
307 		i -= rc;
308 		p += rc;
309 	}
310 #ifdef DEBUG
311 	printf("ipp_request_read(0x%8.8x, 0x%8.8x, %d) = %d\n",
312 			fd, buffer, length, rc);
313 	httpDumpData(stdout, "ipp_request_read:", buffer, length);
314 #endif
315 
316 	return (length);
317 }
318 
319 papi_status_t
ipp_send_initial_request_block(service_t * svc,papi_attribute_t ** request,ssize_t file_size)320 ipp_send_initial_request_block(service_t *svc, papi_attribute_t **request,
321 		ssize_t file_size)
322 {
323 	papi_status_t result = PAPI_OK;
324 	ssize_t chunk_size = 0;
325 	char length[32];
326 	void *chunk, *ptr;
327 	http_status_t status;
328 
329 	/* calculate the request size */
330 	(void) ipp_write_message(&size_calculate, &chunk_size, request);
331 
332 	/* Fill in the HTTP Header information */
333 	httpClearFields(svc->connection);
334 	if (svc->transfer_encoding == TRANSFER_ENCODING_CHUNKED)
335 		httpSetField(svc->connection, HTTP_FIELD_TRANSFER_ENCODING,
336 			"chunked");
337 	else {
338 		sprintf(length, "%lu", (unsigned long)(file_size + chunk_size));
339 		httpSetField(svc->connection, HTTP_FIELD_CONTENT_LENGTH,
340 			length);
341 	}
342 	httpSetField(svc->connection, HTTP_FIELD_CONTENT_TYPE,
343 			"application/ipp");
344 	httpSetField(svc->connection, HTTP_FIELD_AUTHORIZATION,
345 			svc->connection->authstring);
346 
347 	/* flush any state information about this connection */
348 	httpFlush(svc->connection);
349 
350 	/* if we have don't have a POST path, use the service uri path */
351 	if (svc->post == NULL)
352 		svc->post = strdup(svc->uri->path);
353 	/* send the HTTP POST message for the IPP request */
354 	/* if the POST fails, return the error */
355 	status = httpPost(svc->connection, svc->post);
356 	if (status != 0)
357 		return (http_to_papi_status(status));
358 
359 	if (httpCheck(svc->connection) != 0) {
360 		status = httpUpdate(svc->connection);
361 		if (status != HTTP_OK)
362 			return (http_to_papi_status(status));
363 	}
364 
365 	/* build the request chunk */
366 	chunk = ptr = calloc(1, chunk_size);
367 	result = ipp_write_message(&build_chunk, &ptr, request);
368 #ifdef DEBUG
369 	printf("request: %d (0x%x) bytes\n", chunk_size, chunk_size);
370 	httpDumpData(stdout, "request:", chunk, chunk_size);
371 #endif
372 
373 	/* send the actual IPP request */
374 	if (ipp_request_write(svc, chunk, chunk_size) != chunk_size)
375 		result = PAPI_TEMPORARY_ERROR;
376 	free(chunk);
377 
378 	if (httpCheck(svc->connection) != 0) {
379 		status = httpUpdate(svc->connection);
380 		if (status != HTTP_OK)
381 			return (http_to_papi_status(status));
382 	}
383 
384 	return (result);
385 }
386 
387 static int
setAuthString(service_t * svc)388 setAuthString(service_t *svc)
389 {
390 	http_t *http;
391 	char *user, *passphrase;
392 	char encoded[BUFSIZ];
393 
394 	if ((svc == NULL) || (svc->connection == NULL) || (svc->name == NULL))
395 		return (-1);
396 
397 	http = svc->connection;
398 
399 	if (svc->user == NULL) {
400 		struct passwd *p;
401 
402 		if ((p = getpwuid(getuid())) != NULL) {
403 			user = p->pw_name;
404 		} else if ((user = getenv("LOGNAME")) == NULL)
405 			user = getenv("USER");
406 		if (user == NULL)
407 			user = "nobody";
408 	} else
409 		user = svc->user;
410 
411 	/* if the passphrase is not set, use the Authentication Callback */
412 	if (((svc->password == NULL) || (svc->password[0] == '\0')) &&
413 	    (svc->authCB != NULL))
414 		(svc->authCB)(svc);
415 	passphrase = svc->password;
416 
417 	/* if there is still no passphrase, we have to fail */
418 	if ((passphrase == NULL) || (passphrase[0] == '\0'))
419 		return (-1);
420 
421 	if (strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE],
422 			"Basic", 5) == 0) {
423 		char plain[BUFSIZ];
424 
425 		snprintf(plain, sizeof (plain), "%s:%s", user, passphrase);
426 		httpEncode64(encoded, plain);
427 		snprintf(http->authstring, sizeof (http->authstring),
428 			"Basic %s", encoded);
429 	} else if (strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE],
430 			"Digest", 6) == 0) {
431 		char realm[HTTP_MAX_VALUE];
432 		char nonce[HTTP_MAX_VALUE];
433 		char *uri = svc->post;
434 
435 		httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE,
436 				"realm", realm);
437 		httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE,
438 				"nonce", nonce);
439 		httpMD5(user, realm, passphrase, encoded);
440 		httpMD5Final(nonce, "POST", uri, encoded);
441 
442 		snprintf(http->authstring, sizeof (http->authstring),
443 			"Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", "
444 			"uri=\"%s\", response=\"%s\"", user, realm, nonce, uri,
445 			encoded);
446 	}
447 
448 	return (0);
449 }
450 
451 papi_status_t
ipp_status_info(service_t * svc,papi_attribute_t ** response)452 ipp_status_info(service_t *svc, papi_attribute_t **response)
453 {
454 	papi_attribute_t **operational = NULL;
455 	int32_t status = 0;
456 
457 	papiAttributeListGetCollection(response, NULL,
458 			"operational-attributes-group", &operational);
459 	if (operational != NULL) {
460 		char *message = NULL;
461 
462 		papiAttributeListGetString(response, NULL,
463 					"status-message", &message);
464 		papiAttributeListAddString(&svc->attributes, PAPI_ATTR_REPLACE,
465 					"detailed-status-message", message);
466 	}
467 	papiAttributeListGetInteger(response, NULL, "status-code", &status);
468 
469 	return (ipp_to_papi_status(status));
470 }
471 
472 papi_status_t
ipp_send_request_with_file(service_t * svc,papi_attribute_t ** request,papi_attribute_t *** response,char * file)473 ipp_send_request_with_file(service_t *svc, papi_attribute_t **request,
474 		papi_attribute_t ***response, char *file)
475 {
476 	papi_status_t result = PAPI_OK;
477 	ssize_t size = 0;
478 	int fd;
479 
480 #ifdef DEBUG
481 	fprintf(stderr, "\nIPP-REQUEST: (%s)", (file ? file : ""));
482 	papiAttributeListPrint(stderr, request, "    ");
483 	putc('\n', stderr);
484 	fflush(stderr);
485 #endif
486 
487 	/*
488 	 * if we are sending a file, open it and include it's size in the
489 	 * message size.
490 	 */
491 	if (file != NULL) {
492 		if ((fd = open(file, O_RDONLY)) < 0) {
493 			detailed_error(svc, "%s: %s", file, strerror(errno));
494 			return (PAPI_DOCUMENT_ACCESS_ERROR);
495 		} else if (svc->transfer_encoding !=
496 						TRANSFER_ENCODING_CHUNKED) {
497 			struct stat st;
498 
499 			if (fstat(fd, &st) >= 0)
500 				size = st.st_size;
501 		}
502 	}
503 
504 	*response = NULL;
505 	while (*response == NULL) {
506 		http_status_t status = HTTP_CONTINUE;
507 
508 		result = ipp_send_initial_request_block(svc, request, size);
509 
510 		if (result == PAPI_OK) {
511 			if (file != NULL) {
512 				/* send the file contents if we have it */
513 				int rc;
514 				char buf[BUFSIZ];
515 
516 				lseek(fd, 0L, SEEK_SET);
517 				while ((rc = read(fd, buf, sizeof (buf))) > 0) {
518 					if (ipp_request_write(svc, buf, rc)
519 							< rc) {
520 						break;
521 					}
522 				}
523 			}
524 
525 			(void) ipp_request_write(svc, "", 0);
526 		}
527 
528 		/* update our connection info */
529 		while (status == HTTP_CONTINUE)
530 			status = httpUpdate(svc->connection);
531 
532 		if (status == HTTP_UNAUTHORIZED) {
533 			httpFlush(svc->connection);
534 			if ((svc->connection->authstring[0] == '\0') &&
535 			    (setAuthString(svc) == 0)) {
536 				httpReconnect(svc->connection);
537 				continue;
538 			}
539 		} else if (status == HTTP_UPGRADE_REQUIRED) {
540 			/*
541 			 * If the transport was built with TLS support, we can
542 			 * try to use it.
543 			 */
544 			httpFlush(svc->connection);
545 			httpReconnect(svc->connection);
546 			httpEncryption(svc->connection, HTTP_ENCRYPT_REQUIRED);
547 			continue;
548 		}
549 
550 		if (status != HTTP_OK)
551 			return (http_to_papi_status(status));
552 
553 		/* read the IPP response */
554 		result = ipp_read_message(&ipp_request_read, svc, response,
555 				IPP_TYPE_RESPONSE);
556 
557 		if (result == PAPI_OK)
558 			result = ipp_status_info(svc, *response);
559 #ifdef DEBUG
560 		fprintf(stderr, "\nIPP-RESPONSE: (%s) (%s)", (file ? file : ""),
561 				papiStatusString(result));
562 		papiAttributeListPrint(stderr, *response, "    ");
563 		putc('\n', stderr);
564 		fflush(stderr);
565 #endif
566 	}
567 
568 	return (result);
569 }
570 
571 papi_status_t
ipp_send_request(service_t * svc,papi_attribute_t ** request,papi_attribute_t *** response)572 ipp_send_request(service_t *svc, papi_attribute_t **request,
573 		papi_attribute_t ***response)
574 {
575 	return (ipp_send_request_with_file(svc, request, response, NULL));
576 }
577