]> git.ipfire.org Git - thirdparty/cups.git/blob - cups/auth.c
Merge changes from CUPS 1.5svn-r9000.
[thirdparty/cups.git] / cups / auth.c
1 /*
2 * "$Id: auth.c 7720 2008-07-11 22:46:21Z mike $"
3 *
4 * Authentication functions for the Common UNIX Printing System (CUPS).
5 *
6 * Copyright 2007-2009 by Apple Inc.
7 * Copyright 1997-2007 by Easy Software Products.
8 *
9 * This file contains Kerberos support code, copyright 2006 by
10 * Jelmer Vernooij.
11 *
12 * These coded instructions, statements, and computer programs are the
13 * property of Apple Inc. and are protected by Federal copyright
14 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
15 * which should have been included with this file. If this file is
16 * file is missing or damaged, see the license at "http://www.cups.org/".
17 *
18 * This file is subject to the Apple OS-Developed Software exception.
19 *
20 * Contents:
21 *
22 * cupsDoAuthentication() - Authenticate a request.
23 * cups_get_gssname() - Get GSSAPI name for authentication.
24 * cups_gss_printf() - Show error messages from GSSAPI...
25 * cups_local_auth() - Get the local authorization certificate if
26 * available/applicable...
27 */
28
29 /*
30 * Include necessary headers...
31 */
32
33 #include "globals.h"
34 #include "debug.h"
35 #include <stdlib.h>
36 #include <ctype.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <sys/stat.h>
40 #if defined(WIN32) || defined(__EMX__)
41 # include <io.h>
42 #else
43 # include <unistd.h>
44 #endif /* WIN32 || __EMX__ */
45
46 #if HAVE_AUTHORIZATION_H
47 # include <Security/Authorization.h>
48 # ifdef HAVE_SECBASEPRIV_H
49 # include <Security/SecBasePriv.h>
50 # else
51 extern const char *cssmErrorString(int error);
52 # endif /* HAVE_SECBASEPRIV_H */
53 #endif /* HAVE_AUTHORIZATION_H */
54
55 #if defined(SO_PEERCRED) && defined(AF_LOCAL)
56 # include <pwd.h>
57 #endif /* SO_PEERCRED && AF_LOCAL */
58
59
60 /*
61 * Local functions...
62 */
63
64 #ifdef HAVE_GSSAPI
65 static gss_name_t cups_get_gssname(http_t *http, const char *service_name);
66 # ifdef DEBUG
67 static void cups_gss_printf(OM_uint32 major_status, OM_uint32 minor_status,
68 const char *message);
69 # else
70 # define cups_gss_printf(major, minor, message)
71 # endif /* DEBUG */
72 #endif /* HAVE_GSSAPI */
73 static int cups_local_auth(http_t *http);
74
75
76 /*
77 * 'cupsDoAuthentication()' - Authenticate a request.
78 *
79 * This function should be called in response to a @code HTTP_UNAUTHORIZED@
80 * status, prior to resubmitting your request.
81 *
82 * @since CUPS 1.1.20/Mac OS X 10.4@
83 */
84
85 int /* O - 0 on success, -1 on error */
86 cupsDoAuthentication(
87 http_t *http, /* I - Connection to server or @code CUPS_HTTP_DEFAULT@ */
88 const char *method, /* I - Request method ("GET", "POST", "PUT") */
89 const char *resource) /* I - Resource path */
90 {
91 const char *password; /* Password string */
92 char prompt[1024], /* Prompt for user */
93 realm[HTTP_MAX_VALUE], /* realm="xyz" string */
94 nonce[HTTP_MAX_VALUE]; /* nonce="xyz" string */
95 int localauth; /* Local authentication result */
96 _cups_globals_t *cg; /* Global data */
97
98
99 DEBUG_printf(("cupsDoAuthentication(http=%p, method=\"%s\", resource=\"%s\")",
100 http, method, resource));
101 DEBUG_printf(("2cupsDoAuthentication: digest_tries=%d, userpass=\"%s\"",
102 http->digest_tries, http->userpass));
103 DEBUG_printf(("2cupsDoAuthentication: WWW-Authenticate=\"%s\"",
104 httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE)));
105
106 if (!http)
107 http = _cupsConnect();
108
109 if (!http || !method || !resource)
110 return (-1);
111
112 /*
113 * Clear the current authentication string...
114 */
115
116 httpSetAuthString(http, NULL, NULL);
117
118 /*
119 * See if we can do local authentication...
120 */
121
122 if (http->digest_tries < 3)
123 {
124 if ((localauth = cups_local_auth(http)) == 0)
125 {
126 DEBUG_printf(("2cupsDoAuthentication: authstring=\"%s\"",
127 http->authstring));
128
129 if (http->status == HTTP_UNAUTHORIZED)
130 http->digest_tries ++;
131
132 return (0);
133 }
134 else if (localauth == -1)
135 {
136 http->status = HTTP_AUTHORIZATION_CANCELED;
137 return (-1); /* Error or canceled */
138 }
139 }
140
141 /*
142 * Nope, see if we should retry the current username:password...
143 */
144
145 if ((http->digest_tries > 1 || !http->userpass[0]) &&
146 strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Negotiate", 9))
147 {
148 /*
149 * Nope - get a new password from the user...
150 */
151
152 cg = _cupsGlobals();
153
154 if (!cg->lang_default)
155 cg->lang_default = cupsLangDefault();
156
157 snprintf(prompt, sizeof(prompt),
158 _cupsLangString(cg->lang_default, _("Password for %s on %s? ")),
159 cupsUser(),
160 http->hostname[0] == '/' ? "localhost" : http->hostname);
161
162 http->digest_tries = strncasecmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE],
163 "Digest", 5) != 0;
164 http->userpass[0] = '\0';
165
166 if ((password = cupsGetPassword2(prompt, http, method, resource)) == NULL)
167 {
168 http->status = HTTP_AUTHORIZATION_CANCELED;
169 return (-1);
170 }
171
172 if (!password[0])
173 {
174 http->status = HTTP_AUTHORIZATION_CANCELED;
175 return (-1);
176 }
177
178 snprintf(http->userpass, sizeof(http->userpass), "%s:%s", cupsUser(),
179 password);
180 }
181 else if (http->status == HTTP_UNAUTHORIZED)
182 http->digest_tries ++;
183
184 /*
185 * Got a password; encode it for the server...
186 */
187
188 if (!strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Negotiate", 9))
189 {
190 #ifdef HAVE_GSSAPI
191 /*
192 * Kerberos authentication...
193 */
194
195 OM_uint32 minor_status, /* Minor status code */
196 major_status; /* Major status code */
197 gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER,
198 /* Output token */
199 input_token = GSS_C_EMPTY_BUFFER;
200 /* Input token */
201 char *gss_service_name;
202 /* GSS service name */
203 # ifdef USE_SPNEGO
204 const char *authorization;
205 /* Pointer into Authorization string */
206 # endif /* USE_SPNEGO */
207
208
209 # ifdef __APPLE__
210 /*
211 * If the weak-linked GSSAPI/Kerberos library is not present, don't try
212 * to use it...
213 */
214
215 if (gss_init_sec_context == NULL)
216 {
217 DEBUG_puts("1cupsDoAuthentication: Weak-linked GSSAPI/Kerberos framework "
218 "is not present");
219 http->status = HTTP_AUTHORIZATION_CANCELED;
220
221 return (-1);
222 }
223 # endif /* __APPLE__ */
224
225 if (http->status == HTTP_UNAUTHORIZED && http->digest_tries >= 3)
226 {
227 DEBUG_printf(("1cupsDoAuthentication: too many Negotiate tries (%d)",
228 http->digest_tries));
229 http->status = HTTP_AUTHORIZATION_CANCELED;
230
231 return (-1);
232 }
233
234 if (http->gssname == GSS_C_NO_NAME)
235 {
236 if ((gss_service_name = getenv("CUPS_GSSSERVICENAME")) == NULL)
237 gss_service_name = CUPS_DEFAULT_GSSSERVICENAME;
238 else
239 DEBUG_puts("2cupsDoAuthentication: GSS service name set via "
240 "environment variable");
241
242 http->gssname = cups_get_gssname(http, gss_service_name);
243 }
244
245 # ifdef USE_SPNEGO /* We don't implement SPNEGO just yet... */
246 /*
247 * Find the start of the Kerberos input token...
248 */
249
250 authorization = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
251
252 authorization += 9;
253 while (*authorization && isspace(*authorization & 255))
254 authorization ++;
255
256 if (*authorization)
257 {
258 /*
259 * Decode the authorization string to get the input token...
260 */
261
262 int len = strlen(authorization);
263
264 input_token.value = malloc(len);
265 input_token.value = httpDecode64_2(input_token.value, &len,
266 authorization);
267 input_token.length = len;
268
269 # ifdef DEBUG
270 {
271 char *ptr = (char *)input_token.value;
272 int left = len;
273
274 fputs("input_token=", stdout);
275 while (left > 0)
276 {
277 if (*ptr < ' ')
278 printf("\\%03o", *ptr & 255);
279 else
280 putchar(*ptr);
281 ptr ++;
282 left --;
283 }
284 putchar('\n');
285 }
286 # endif /* DEBUG */
287 }
288 # endif /* USE_SPNEGO */
289
290 if (http->gssctx != GSS_C_NO_CONTEXT)
291 {
292 gss_delete_sec_context(&minor_status, &http->gssctx, GSS_C_NO_BUFFER);
293 http->gssctx = GSS_C_NO_CONTEXT;
294 }
295
296 major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL,
297 &http->gssctx,
298 http->gssname, http->gssmech,
299 #ifdef GSS_C_DELEG_POLICY_FLAG
300 GSS_C_DELEG_POLICY_FLAG |
301 #endif /* GSS_C_DELEG_POLICY_FLAG */
302 GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG,
303 GSS_C_INDEFINITE,
304 GSS_C_NO_CHANNEL_BINDINGS,
305 &input_token, &http->gssmech,
306 &output_token, NULL, NULL);
307
308 if (input_token.value)
309 free(input_token.value);
310
311 if (GSS_ERROR(major_status))
312 {
313 cups_gss_printf(major_status, minor_status,
314 "cupsDoAuthentication: Unable to initialize security "
315 "context");
316 http->status = HTTP_AUTHORIZATION_CANCELED;
317
318 return (-1);
319 }
320
321 if (major_status == GSS_S_CONTINUE_NEEDED)
322 cups_gss_printf(major_status, minor_status,
323 "cupsDoAuthentication: Continuation needed!");
324
325 if (output_token.length > 0 && output_token.length <= 65536)
326 {
327 /*
328 * Allocate the authorization string since Windows KDCs can have
329 * arbitrarily large credentials...
330 */
331
332 int authsize = 10 + /* "Negotiate " */
333 output_token.length * 4 / 3 + 1 + /* Base64 */
334 1; /* nul */
335
336 httpSetAuthString(http, NULL, NULL);
337
338 if ((http->authstring = malloc(authsize)) == NULL)
339 {
340 http->authstring = http->_authstring;
341 authsize = sizeof(http->_authstring);
342 }
343
344 strcpy(http->authstring, "Negotiate ");
345 httpEncode64_2(http->authstring + 10, authsize - 10, output_token.value,
346 output_token.length);
347
348 gss_release_buffer(&minor_status, &output_token);
349 }
350 else
351 {
352 DEBUG_printf(("1cupsDoAuthentication: Kerberos credentials too large - "
353 "%d bytes!", (int)output_token.length));
354 http->status = HTTP_AUTHORIZATION_CANCELED;
355 gss_release_buffer(&minor_status, &output_token);
356
357 return (-1);
358 }
359 #endif /* HAVE_GSSAPI */
360 }
361 else if (strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Digest", 6))
362 {
363 /*
364 * Basic authentication...
365 */
366
367 char encode[256]; /* Base64 buffer */
368
369
370 httpEncode64_2(encode, sizeof(encode), http->userpass,
371 (int)strlen(http->userpass));
372 httpSetAuthString(http, "Basic", encode);
373 }
374 else
375 {
376 /*
377 * Digest authentication...
378 */
379
380 char encode[33], /* MD5 buffer */
381 digest[1024]; /* Digest auth data */
382
383
384 httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE, "realm", realm);
385 httpGetSubField(http, HTTP_FIELD_WWW_AUTHENTICATE, "nonce", nonce);
386
387 httpMD5(cupsUser(), realm, strchr(http->userpass, ':') + 1, encode);
388 httpMD5Final(nonce, method, resource, encode);
389 snprintf(digest, sizeof(digest),
390 "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", "
391 "response=\"%s\"", cupsUser(), realm, nonce, resource, encode);
392 httpSetAuthString(http, "Digest", digest);
393 }
394
395 DEBUG_printf(("1cupsDoAuthentication: authstring=\"%s\"", http->authstring));
396
397 return (0);
398 }
399
400
401 #ifdef HAVE_GSSAPI
402 /*
403 * 'cups_get_gssname()' - Get CUPS service credentials for authentication.
404 */
405
406 static gss_name_t /* O - Server name */
407 cups_get_gssname(
408 http_t *http, /* I - Connection to server */
409 const char *service_name) /* I - Service name */
410 {
411 gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
412 /* Service token */
413 OM_uint32 major_status, /* Major status code */
414 minor_status; /* Minor status code */
415 gss_name_t server_name; /* Server name */
416 char buf[1024], /* Name buffer */
417 fqdn[HTTP_MAX_URI]; /* Server name buffer */
418
419
420 DEBUG_printf(("7cups_get_gssname(http=%p, service_name=\"%s\")", http,
421 service_name));
422
423
424 /*
425 * Get the hostname...
426 */
427
428 httpGetHostname(http, fqdn, sizeof(fqdn));
429
430 if (!strcmp(fqdn, "localhost"))
431 httpGetHostname(NULL, fqdn, sizeof(fqdn));
432
433 /*
434 * Get a server name we can use for authentication purposes...
435 */
436
437 snprintf(buf, sizeof(buf), "%s@%s", service_name, fqdn);
438
439 DEBUG_printf(("9cups_get_gssname: Looking up %s...", buf));
440
441 token.value = buf;
442 token.length = strlen(buf);
443 server_name = GSS_C_NO_NAME;
444 major_status = gss_import_name(&minor_status, &token,
445 GSS_C_NT_HOSTBASED_SERVICE,
446 &server_name);
447
448 if (GSS_ERROR(major_status))
449 {
450 cups_gss_printf(major_status, minor_status,
451 "cups_get_gssname: gss_import_name() failed");
452 return (NULL);
453 }
454
455 return (server_name);
456 }
457
458
459 # ifdef DEBUG
460 /*
461 * 'cups_gss_printf()' - Show debug error messages from GSSAPI...
462 */
463
464 static void
465 cups_gss_printf(OM_uint32 major_status,/* I - Major status code */
466 OM_uint32 minor_status,/* I - Minor status code */
467 const char *message) /* I - Prefix for error message */
468 {
469 OM_uint32 err_major_status, /* Major status code for display */
470 err_minor_status; /* Minor status code for display */
471 OM_uint32 msg_ctx; /* Message context */
472 gss_buffer_desc major_status_string = GSS_C_EMPTY_BUFFER,
473 /* Major status message */
474 minor_status_string = GSS_C_EMPTY_BUFFER;
475 /* Minor status message */
476
477
478 msg_ctx = 0;
479 err_major_status = gss_display_status(&err_minor_status,
480 major_status,
481 GSS_C_GSS_CODE,
482 GSS_C_NO_OID,
483 &msg_ctx,
484 &major_status_string);
485
486 if (!GSS_ERROR(err_major_status))
487 gss_display_status(&err_minor_status, minor_status, GSS_C_MECH_CODE,
488 GSS_C_NULL_OID, &msg_ctx, &minor_status_string);
489
490 DEBUG_printf(("1%s: %s, %s", message, (char *)major_status_string.value,
491 (char *)minor_status_string.value));
492
493 gss_release_buffer(&err_minor_status, &major_status_string);
494 gss_release_buffer(&err_minor_status, &minor_status_string);
495 }
496 # endif /* DEBUG */
497 #endif /* HAVE_GSSAPI */
498
499
500 /*
501 * 'cups_local_auth()' - Get the local authorization certificate if
502 * available/applicable...
503 */
504
505 static int /* O - 0 if available */
506 /* 1 if not available */
507 /* -1 error */
508 cups_local_auth(http_t *http) /* I - HTTP connection to server */
509 {
510 #if defined(WIN32) || defined(__EMX__)
511 /*
512 * Currently WIN32 and OS-2 do not support the CUPS server...
513 */
514
515 return (1);
516 #else
517 int pid; /* Current process ID */
518 FILE *fp; /* Certificate file */
519 char trc[16], /* Try Root Certificate parameter */
520 filename[1024], /* Certificate filename */
521 certificate[33];/* Certificate string */
522 _cups_globals_t *cg = _cupsGlobals(); /* Global data */
523 # if defined(HAVE_AUTHORIZATION_H)
524 OSStatus status; /* Status */
525 AuthorizationItem auth_right; /* Authorization right */
526 AuthorizationRights auth_rights; /* Authorization rights */
527 AuthorizationFlags auth_flags; /* Authorization flags */
528 AuthorizationExternalForm auth_extrn; /* Authorization ref external */
529 char auth_key[1024]; /* Buffer */
530 char buffer[1024]; /* Buffer */
531 # endif /* HAVE_AUTHORIZATION_H */
532
533
534 DEBUG_printf(("7cups_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"",
535 http, httpAddrString(http->hostaddr, filename, sizeof(filename)), http->hostname));
536
537 /*
538 * See if we are accessing localhost...
539 */
540
541 if (!httpAddrLocalhost(http->hostaddr) &&
542 strcasecmp(http->hostname, "localhost") != 0)
543 {
544 DEBUG_puts("8cups_local_auth: Not a local connection!");
545 return (1);
546 }
547
548 # if defined(HAVE_AUTHORIZATION_H)
549 /*
550 * Delete any previous authorization reference...
551 */
552
553 if (http->auth_ref)
554 {
555 AuthorizationFree(http->auth_ref, kAuthorizationFlagDefaults);
556 http->auth_ref = NULL;
557 }
558
559 if (!getenv("GATEWAY_INTERFACE") &&
560 httpGetSubField2(http, HTTP_FIELD_WWW_AUTHENTICATE, "authkey",
561 auth_key, sizeof(auth_key)))
562 {
563 status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment,
564 kAuthorizationFlagDefaults, &http->auth_ref);
565 if (status != errAuthorizationSuccess)
566 {
567 DEBUG_printf(("8cups_local_auth: AuthorizationCreate() returned %d (%s)",
568 (int)status, cssmErrorString(status)));
569 return (-1);
570 }
571
572 auth_right.name = auth_key;
573 auth_right.valueLength = 0;
574 auth_right.value = NULL;
575 auth_right.flags = 0;
576
577 auth_rights.count = 1;
578 auth_rights.items = &auth_right;
579
580 auth_flags = kAuthorizationFlagDefaults |
581 kAuthorizationFlagPreAuthorize |
582 kAuthorizationFlagInteractionAllowed |
583 kAuthorizationFlagExtendRights;
584
585 status = AuthorizationCopyRights(http->auth_ref, &auth_rights,
586 kAuthorizationEmptyEnvironment,
587 auth_flags, NULL);
588 if (status == errAuthorizationSuccess)
589 status = AuthorizationMakeExternalForm(http->auth_ref, &auth_extrn);
590
591 if (status == errAuthorizationSuccess)
592 {
593 /*
594 * Set the authorization string and return...
595 */
596
597 httpEncode64_2(buffer, sizeof(buffer), (void *)&auth_extrn,
598 sizeof(auth_extrn));
599
600 httpSetAuthString(http, "AuthRef", buffer);
601
602 DEBUG_printf(("8cups_local_auth: Returning authstring=\"%s\"",
603 http->authstring));
604 return (0);
605 }
606 else if (status == errAuthorizationCanceled)
607 return (-1);
608
609 DEBUG_printf(("9cups_local_auth: AuthorizationCopyRights() returned %d (%s)",
610 (int)status, cssmErrorString(status)));
611
612 /*
613 * Fall through to try certificates...
614 */
615 }
616 # endif /* HAVE_AUTHORIZATION_H */
617
618 # if defined(SO_PEERCRED) && defined(AF_LOCAL)
619 /*
620 * See if we can authenticate using the peer credentials provided over a
621 * domain socket; if so, specify "PeerCred username" as the authentication
622 * information...
623 */
624
625 # ifdef HAVE_GSSAPI
626 if (strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Negotiate", 9) &&
627 # else
628 if (
629 # endif /* HAVE_GSSAPI */
630 http->hostaddr->addr.sa_family == AF_LOCAL &&
631 !getenv("GATEWAY_INTERFACE")) /* Not via CGI programs... */
632 {
633 /*
634 * Verify that the current cupsUser() matches the current UID...
635 */
636
637 struct passwd *pwd; /* Password information */
638 const char *username; /* Current username */
639
640 username = cupsUser();
641
642 if ((pwd = getpwnam(username)) != NULL && pwd->pw_uid == getuid())
643 {
644 httpSetAuthString(http, "PeerCred", username);
645
646 DEBUG_printf(("8cups_local_auth: Returning authstring=\"%s\"",
647 http->authstring));
648
649 return (0);
650 }
651 }
652 # endif /* SO_PEERCRED && AF_LOCAL */
653
654 /*
655 * Try opening a certificate file for this PID. If that fails,
656 * try the root certificate...
657 */
658
659 pid = getpid();
660 snprintf(filename, sizeof(filename), "%s/certs/%d", cg->cups_statedir, pid);
661 if ((fp = fopen(filename, "r")) == NULL && pid > 0)
662 {
663 /*
664 * No certificate for this PID; see if we can get the root certificate...
665 */
666
667 DEBUG_printf(("9cups_local_auth: Unable to open file %s: %s",
668 filename, strerror(errno)));
669
670 #ifdef HAVE_GSSAPI
671 if (!strncmp(http->fields[HTTP_FIELD_WWW_AUTHENTICATE], "Negotiate", 9))
672 {
673 /*
674 * Kerberos required, don't try the root certificate...
675 */
676
677 return (1);
678 }
679 #endif /* HAVE_GSSAPI */
680
681 if (!httpGetSubField2(http, HTTP_FIELD_WWW_AUTHENTICATE, "trc", trc,
682 sizeof(trc)))
683 {
684 /*
685 * Scheduler doesn't want us to use the root certificate...
686 */
687
688 return (1);
689 }
690
691 snprintf(filename, sizeof(filename), "%s/certs/0", cg->cups_statedir);
692 fp = fopen(filename, "r");
693 }
694
695 if (fp)
696 {
697 /*
698 * Read the certificate from the file...
699 */
700
701 fgets(certificate, sizeof(certificate), fp);
702 fclose(fp);
703
704 /*
705 * Set the authorization string and return...
706 */
707
708 httpSetAuthString(http, "Local", certificate);
709
710 DEBUG_printf(("8cups_local_auth: Returning authstring=\"%s\"",
711 http->authstring));
712
713 return (0);
714 }
715
716 return (1);
717 #endif /* WIN32 || __EMX__ */
718 }
719
720
721 /*
722 * End of "$Id: auth.c 7720 2008-07-11 22:46:21Z mike $".
723 */