Line | Count | Source |
1 | | // |
2 | | // Authentication functions for CUPS. |
3 | | // |
4 | | // Copyright © 2020-2025 by OpenPrinting. |
5 | | // Copyright © 2007-2019 by Apple Inc. |
6 | | // Copyright © 1997-2007 by Easy Software Products. |
7 | | // |
8 | | // This file contains Kerberos support code, copyright 2006 by |
9 | | // Jelmer Vernooij. |
10 | | // |
11 | | // Licensed under Apache License v2.0. See the file "LICENSE" for more |
12 | | // information. |
13 | | // |
14 | | |
15 | | #include "cups-private.h" |
16 | | #include "debug-internal.h" |
17 | | #include <fcntl.h> |
18 | | #include <sys/stat.h> |
19 | | #if defined(_WIN32) || defined(__EMX__) |
20 | | # include <io.h> |
21 | | #else |
22 | | # include <unistd.h> |
23 | | #endif // _WIN32 || __EMX__ |
24 | | |
25 | | #if HAVE_AUTHORIZATION_H |
26 | | # include <Security/Authorization.h> |
27 | | #endif // HAVE_AUTHORIZATION_H |
28 | | |
29 | | #if defined(SO_PEERCRED) && defined(AF_LOCAL) |
30 | | # include <pwd.h> |
31 | | #endif // SO_PEERCRED && AF_LOCAL |
32 | | |
33 | | |
34 | | // |
35 | | // Local functions... |
36 | | // |
37 | | |
38 | | static const char *cups_auth_find(const char *www_authenticate, const char *scheme); |
39 | | static const char *cups_auth_param(const char *scheme, const char *name, char *value, size_t valsize); |
40 | | static const char *cups_auth_scheme(const char *www_authenticate, char *scheme, size_t schemesize); |
41 | | static bool cups_do_local_auth(http_t *http); |
42 | | |
43 | | #ifdef HAVE_GSSAPI |
44 | | # define CUPS_GSS_OK 0 // Successfully set credentials |
45 | | # define CUPS_GSS_NONE -1 // No credentials |
46 | | # define CUPS_GSS_FAIL -2 // Failed credentials/authentication |
47 | | # ifdef HAVE_GSS_ACQUIRE_CRED_EX_F |
48 | | # ifdef HAVE_GSS_GSSAPI_SPI_H |
49 | | # include <GSS/gssapi_spi.h> |
50 | | # else |
51 | | # define GSS_AUTH_IDENTITY_TYPE_1 1 |
52 | | # define gss_acquire_cred_ex_f __ApplePrivate_gss_acquire_cred_ex_f |
53 | | typedef struct gss_auth_identity // @private@ |
54 | | { |
55 | | uint32_t type; |
56 | | uint32_t flags; |
57 | | char *username; |
58 | | char *realm; |
59 | | char *password; |
60 | | gss_buffer_t *credentialsRef; |
61 | | } gss_auth_identity_desc; |
62 | | extern OM_uint32 gss_acquire_cred_ex_f(gss_status_id_t, const gss_name_t, |
63 | | OM_uint32, OM_uint32, const gss_OID, |
64 | | gss_cred_usage_t, gss_auth_identity_t, |
65 | | void *, void (*)(void *, OM_uint32, |
66 | | gss_status_id_t, |
67 | | gss_cred_id_t, |
68 | | gss_OID_set, |
69 | | OM_uint32)); |
70 | | # endif // HAVE_GSS_GSSAPI_SPI_H |
71 | | # include <dispatch/dispatch.h> |
72 | | typedef struct _cups_gss_acquire_s // Acquire callback data |
73 | | { |
74 | | dispatch_semaphore_t sem; // Synchronization semaphore |
75 | | OM_uint32 major; // Returned status code |
76 | | gss_cred_id_t creds; // Returned credentials |
77 | | } _cups_gss_acquire_t; |
78 | | |
79 | | static void cups_gss_acquire(void *ctx, OM_uint32 major, |
80 | | gss_status_id_t status, |
81 | | gss_cred_id_t creds, gss_OID_set oids, |
82 | | OM_uint32 time_rec); |
83 | | # endif // HAVE_GSS_ACQUIRE_CRED_EX_F |
84 | | static gss_name_t cups_gss_getname(http_t *http, const char *service_name); |
85 | | # ifdef DEBUG |
86 | | static void cups_gss_printf(OM_uint32 major_status, OM_uint32 minor_status, |
87 | | const char *message); |
88 | | # else |
89 | | # define cups_gss_printf(major, minor, message) |
90 | | # endif // DEBUG |
91 | | #endif // HAVE_GSSAPI |
92 | | |
93 | | |
94 | | // |
95 | | // 'cupsDoAuthentication()' - Authenticate a request. |
96 | | // |
97 | | // This function performs authentication for a request. It should be called in |
98 | | // response to a `HTTP_STATUS_UNAUTHORIZED` status, prior to resubmitting your |
99 | | // request. |
100 | | // |
101 | | // @since CUPS 1.1.20@ |
102 | | // |
103 | | |
104 | | int // O - `0` on success, `-1` on error |
105 | | cupsDoAuthentication( |
106 | | http_t *http, // I - Connection to server or `CUPS_HTTP_DEFAULT` |
107 | | const char *method, // I - Request method ("GET", "POST", "PUT") |
108 | | const char *resource) // I - Resource path |
109 | 0 | { |
110 | 0 | const char *password, // Password string |
111 | 0 | *www_auth, // WWW-Authenticate header |
112 | 0 | *schemedata; // Scheme-specific data |
113 | 0 | char scheme[256], // Scheme name |
114 | 0 | prompt[1024]; // Prompt for user |
115 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Global data |
116 | | |
117 | |
|
118 | 0 | DEBUG_printf("cupsDoAuthentication(http=%p, method=\"%s\", resource=\"%s\")", (void *)http, method, resource); |
119 | | |
120 | | // Range check input... |
121 | 0 | if (!http) |
122 | 0 | http = _cupsConnect(); |
123 | |
|
124 | 0 | if (!http || !method || !resource) |
125 | 0 | return (-1); |
126 | | |
127 | 0 | DEBUG_printf("2cupsDoAuthentication: digest_tries=%d, userpass=\"%s\"", http->digest_tries, http->userpass); |
128 | 0 | DEBUG_printf("2cupsDoAuthentication: WWW-Authenticate=\"%s\"", httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE)); |
129 | | |
130 | | // Clear the current authentication string... |
131 | 0 | httpSetAuthString(http, NULL, NULL); |
132 | | |
133 | | // See if we can do local authentication... |
134 | 0 | if (http->digest_tries < 3) |
135 | 0 | { |
136 | 0 | if (cups_do_local_auth(http)) |
137 | 0 | { |
138 | 0 | DEBUG_printf("2cupsDoAuthentication: authstring=\"%s\"", http->authstring); |
139 | |
|
140 | 0 | if (http->status == HTTP_STATUS_UNAUTHORIZED) |
141 | 0 | http->digest_tries ++; |
142 | |
|
143 | 0 | return (0); |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | | // Nope, loop through the authentication schemes to find the first we support. |
148 | 0 | www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE); |
149 | |
|
150 | 0 | for (schemedata = cups_auth_scheme(www_auth, scheme, sizeof(scheme)); schemedata; schemedata = cups_auth_scheme(schemedata + strlen(scheme), scheme, sizeof(scheme))) |
151 | 0 | { |
152 | | // Check the scheme name... |
153 | 0 | DEBUG_printf("2cupsDoAuthentication: Trying scheme \"%s\"...", scheme); |
154 | |
|
155 | | #ifdef HAVE_GSSAPI |
156 | | if (!_cups_strcasecmp(scheme, "Negotiate") && !httpAddrIsLocalhost(httpGetAddress(http))) |
157 | | { |
158 | | // Kerberos authentication to remote server... |
159 | | int gss_status; // Auth status |
160 | | |
161 | | if ((gss_status = _cupsSetNegotiateAuthString(http, method, resource)) == CUPS_GSS_FAIL) |
162 | | { |
163 | | DEBUG_puts("1cupsDoAuthentication: Negotiate failed."); |
164 | | http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; |
165 | | return (-1); |
166 | | } |
167 | | else if (gss_status == CUPS_GSS_NONE) |
168 | | { |
169 | | DEBUG_puts("2cupsDoAuthentication: No credentials for Negotiate."); |
170 | | continue; |
171 | | } |
172 | | else |
173 | | { |
174 | | DEBUG_puts("2cupsDoAuthentication: Using Negotiate."); |
175 | | break; |
176 | | } |
177 | | } |
178 | | else |
179 | | #endif // HAVE_GSSAPI |
180 | 0 | if (!_cups_strcasecmp(scheme, "Bearer")) |
181 | 0 | { |
182 | | // OAuth 2.0 (Bearer) authentication... |
183 | 0 | const char *bearer = NULL; // Bearer token string, if any |
184 | |
|
185 | 0 | if (cg->oauth_cb) |
186 | 0 | { |
187 | | // Try callback... |
188 | 0 | char scope[HTTP_MAX_VALUE]; // scope="xyz" string |
189 | |
|
190 | 0 | cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm)); |
191 | |
|
192 | 0 | if (cups_auth_param(schemedata, "scope", scope, sizeof(scope))) |
193 | 0 | bearer = (cg->oauth_cb)(http, http->realm, scope, resource, cg->oauth_data); |
194 | 0 | else |
195 | 0 | bearer = (cg->oauth_cb)(http, http->realm, NULL, resource, cg->oauth_data); |
196 | 0 | } |
197 | |
|
198 | 0 | if (bearer) |
199 | 0 | { |
200 | | // Use this access token... |
201 | 0 | httpSetAuthString(http, "Bearer", bearer); |
202 | 0 | break; |
203 | 0 | } |
204 | 0 | else |
205 | 0 | { |
206 | | // No access token, try the next scheme... |
207 | 0 | DEBUG_puts("2cupsDoAuthentication: No OAuth access token to provide."); |
208 | 0 | continue; |
209 | 0 | } |
210 | 0 | } |
211 | 0 | else if (_cups_strcasecmp(scheme, "Basic") && _cups_strcasecmp(scheme, "Digest") && _cups_strcasecmp(scheme, "Negotiate")) |
212 | 0 | { |
213 | | // Other schemes not yet supported... |
214 | 0 | DEBUG_printf("2cupsDoAuthentication: Scheme \"%s\" not yet supported.", scheme); |
215 | 0 | continue; |
216 | 0 | } |
217 | | |
218 | | // See if we should retry the current username:password... |
219 | 0 | if (http->digest_tries > 1 || !http->userpass[0]) |
220 | 0 | { |
221 | | // Nope - get a new password from the user... |
222 | 0 | char default_username[HTTP_MAX_VALUE]; |
223 | | // Default username |
224 | |
|
225 | 0 | if (!cg->lang_default) |
226 | 0 | cg->lang_default = cupsLangDefault(); |
227 | |
|
228 | 0 | if (cups_auth_param(schemedata, "username", default_username, sizeof(default_username))) |
229 | 0 | cupsSetUser(default_username); |
230 | |
|
231 | 0 | snprintf(prompt, sizeof(prompt), _cupsLangString(cg->lang_default, _("Password for %s on %s? ")), cupsGetUser(), http->hostname[0] == '/' ? "localhost" : http->hostname); |
232 | |
|
233 | 0 | http->digest_tries = _cups_strncasecmp(scheme, "Digest", 6) != 0; |
234 | 0 | http->userpass[0] = '\0'; |
235 | |
|
236 | 0 | if ((password = cupsGetPassword2(prompt, http, method, resource)) == NULL) |
237 | 0 | { |
238 | 0 | DEBUG_puts("1cupsDoAuthentication: User canceled password request."); |
239 | 0 | http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; |
240 | 0 | return (-1); |
241 | 0 | } |
242 | | |
243 | 0 | snprintf(http->userpass, sizeof(http->userpass), "%s:%s", cupsGetUser(), password); |
244 | 0 | } |
245 | 0 | else if (http->status == HTTP_STATUS_UNAUTHORIZED) |
246 | 0 | { |
247 | 0 | http->digest_tries ++; |
248 | 0 | } |
249 | | |
250 | 0 | if (http->status == HTTP_STATUS_UNAUTHORIZED && http->digest_tries >= 3) |
251 | 0 | { |
252 | 0 | DEBUG_printf("1cupsDoAuthentication: Too many authentication tries (%d)", http->digest_tries); |
253 | |
|
254 | 0 | http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; |
255 | 0 | return (-1); |
256 | 0 | } |
257 | | |
258 | | // Got a password; encode it for the server... |
259 | 0 | if (!_cups_strcasecmp(scheme, "Basic")) |
260 | 0 | { |
261 | | // Basic authentication... |
262 | 0 | char encode[256]; // Base64 buffer |
263 | |
|
264 | 0 | DEBUG_puts("2cupsDoAuthentication: Using Basic."); |
265 | 0 | httpEncode64_3(encode, sizeof(encode), http->userpass, strlen(http->userpass), false); |
266 | 0 | httpSetAuthString(http, "Basic", encode); |
267 | 0 | break; |
268 | 0 | } |
269 | 0 | else if (!_cups_strcasecmp(scheme, "Digest")) |
270 | 0 | { |
271 | | // Digest authentication... |
272 | 0 | char nonce[HTTP_MAX_VALUE]; // nonce="xyz" string |
273 | |
|
274 | 0 | cups_auth_param(schemedata, "algorithm", http->algorithm, sizeof(http->algorithm)); |
275 | 0 | cups_auth_param(schemedata, "nonce", nonce, sizeof(nonce)); |
276 | 0 | cups_auth_param(schemedata, "opaque", http->opaque, sizeof(http->opaque)); |
277 | 0 | cups_auth_param(schemedata, "qop", http->qop, sizeof(http->qop)); |
278 | 0 | cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm)); |
279 | |
|
280 | 0 | if (_httpSetDigestAuthString(http, nonce, method, resource)) |
281 | 0 | { |
282 | 0 | DEBUG_puts("2cupsDoAuthentication: Using Digest."); |
283 | 0 | break; |
284 | 0 | } |
285 | 0 | } |
286 | 0 | } |
287 | | |
288 | 0 | if (http->authstring && http->authstring[0]) |
289 | 0 | { |
290 | 0 | DEBUG_printf("1cupsDoAuthentication: authstring=\"%s\".", http->authstring); |
291 | |
|
292 | 0 | return (0); |
293 | 0 | } |
294 | 0 | else |
295 | 0 | { |
296 | 0 | DEBUG_puts("1cupsDoAuthentication: No supported schemes."); |
297 | 0 | http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED; |
298 | |
|
299 | 0 | return (-1); |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | | |
304 | | #ifdef HAVE_GSSAPI |
305 | | // |
306 | | // '_cupsSetNegotiateAuthString()' - Set the Kerberos authentication string. |
307 | | // |
308 | | |
309 | | int // O - 0 on success, negative on error |
310 | | _cupsSetNegotiateAuthString( |
311 | | http_t *http, // I - Connection to server |
312 | | const char *method, // I - Request method ("GET", "POST", "PUT") |
313 | | const char *resource) // I - Resource path |
314 | | { |
315 | | OM_uint32 minor_status, // Minor status code |
316 | | major_status; // Major status code |
317 | | gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; |
318 | | // Output token |
319 | | |
320 | | # ifdef __APPLE__ |
321 | | // If the weak-linked GSSAPI/Kerberos library is not present, don't try |
322 | | // to use it... |
323 | | if (&gss_init_sec_context == NULL) |
324 | | { |
325 | | DEBUG_puts("1_cupsSetNegotiateAuthString: Weak-linked GSSAPI/Kerberos " |
326 | | "framework is not present"); |
327 | | return (CUPS_GSS_NONE); |
328 | | } |
329 | | # endif // __APPLE__ |
330 | | |
331 | | if (!strcmp(http->hostname, "localhost") || http->hostname[0] == '/' || isdigit(http->hostname[0] & 255) || !strchr(http->hostname, '.')) |
332 | | { |
333 | | DEBUG_printf("1_cupsSetNegotiateAuthString: Kerberos not available for host \"%s\".", http->hostname); |
334 | | return (CUPS_GSS_NONE); |
335 | | } |
336 | | |
337 | | if (http->gssname == GSS_C_NO_NAME) |
338 | | { |
339 | | http->gssname = cups_gss_getname(http, _cupsGSSServiceName()); |
340 | | } |
341 | | |
342 | | if (http->gssctx != GSS_C_NO_CONTEXT) |
343 | | { |
344 | | gss_delete_sec_context(&minor_status, &http->gssctx, GSS_C_NO_BUFFER); |
345 | | http->gssctx = GSS_C_NO_CONTEXT; |
346 | | } |
347 | | |
348 | | major_status = gss_init_sec_context(&minor_status, GSS_C_NO_CREDENTIAL, |
349 | | &http->gssctx, |
350 | | http->gssname, http->gssmech, |
351 | | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG, |
352 | | GSS_C_INDEFINITE, |
353 | | GSS_C_NO_CHANNEL_BINDINGS, |
354 | | GSS_C_NO_BUFFER, &http->gssmech, |
355 | | &output_token, NULL, NULL); |
356 | | |
357 | | # ifdef HAVE_GSS_ACQUIRE_CRED_EX_F |
358 | | if (major_status == GSS_S_NO_CRED) |
359 | | { |
360 | | // Ask the user for credentials... |
361 | | char prompt[1024], // Prompt for user |
362 | | userbuf[256]; // Kerberos username |
363 | | const char *username, // Username string |
364 | | *password; // Password string |
365 | | _cups_gss_acquire_t data; // Callback data |
366 | | gss_auth_identity_desc identity; // Kerberos user identity |
367 | | _cups_globals_t *cg = _cupsGlobals(); |
368 | | // Per-thread global data |
369 | | |
370 | | if (!cg->lang_default) |
371 | | cg->lang_default = cupsLangDefault(); |
372 | | |
373 | | snprintf(prompt, sizeof(prompt), |
374 | | _cupsLangString(cg->lang_default, _("Password for %s on %s? ")), |
375 | | cupsGetUser(), http->gsshost); |
376 | | |
377 | | if ((password = cupsGetPassword2(prompt, http, method, resource)) == NULL) |
378 | | return (CUPS_GSS_FAIL); |
379 | | |
380 | | // Try to acquire credentials... |
381 | | username = cupsGetUser(); |
382 | | if (!strchr(username, '@')) |
383 | | { |
384 | | snprintf(userbuf, sizeof(userbuf), "%s@%s", username, http->gsshost); |
385 | | username = userbuf; |
386 | | } |
387 | | |
388 | | identity.type = GSS_AUTH_IDENTITY_TYPE_1; |
389 | | identity.flags = 0; |
390 | | identity.username = (char *)username; |
391 | | identity.realm = (char *)""; |
392 | | identity.password = (char *)password; |
393 | | identity.credentialsRef = NULL; |
394 | | |
395 | | data.sem = dispatch_semaphore_create(0); |
396 | | data.major = 0; |
397 | | data.creds = NULL; |
398 | | |
399 | | if (data.sem) |
400 | | { |
401 | | major_status = gss_acquire_cred_ex_f(NULL, GSS_C_NO_NAME, 0, GSS_C_INDEFINITE, GSS_KRB5_MECHANISM, GSS_C_INITIATE, (gss_auth_identity_t)&identity, &data, cups_gss_acquire); |
402 | | |
403 | | if (major_status == GSS_S_COMPLETE) |
404 | | { |
405 | | dispatch_semaphore_wait(data.sem, DISPATCH_TIME_FOREVER); |
406 | | major_status = data.major; |
407 | | } |
408 | | |
409 | | dispatch_release(data.sem); |
410 | | |
411 | | if (major_status == GSS_S_COMPLETE) |
412 | | { |
413 | | OM_uint32 release_minor; // Minor status from releasing creds |
414 | | |
415 | | major_status = gss_init_sec_context(&minor_status, data.creds, |
416 | | &http->gssctx, |
417 | | http->gssname, http->gssmech, |
418 | | GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG, |
419 | | GSS_C_INDEFINITE, |
420 | | GSS_C_NO_CHANNEL_BINDINGS, |
421 | | GSS_C_NO_BUFFER, &http->gssmech, |
422 | | &output_token, NULL, NULL); |
423 | | gss_release_cred(&release_minor, &data.creds); |
424 | | } |
425 | | } |
426 | | } |
427 | | # else |
428 | | (void)method; |
429 | | (void)resource; |
430 | | # endif // HAVE_GSS_ACQUIRED_CRED_EX_F |
431 | | |
432 | | if (major_status == GSS_S_NO_CRED) |
433 | | { |
434 | | cups_gss_printf(major_status, minor_status, "_cupsSetNegotiateAuthString: No credentials"); |
435 | | return (CUPS_GSS_NONE); |
436 | | } |
437 | | else if (GSS_ERROR(major_status)) |
438 | | { |
439 | | cups_gss_printf(major_status, minor_status, "_cupsSetNegotiateAuthString: Unable to initialize security context"); |
440 | | return (CUPS_GSS_FAIL); |
441 | | } |
442 | | |
443 | | # ifdef DEBUG |
444 | | else if (major_status == GSS_S_CONTINUE_NEEDED) |
445 | | cups_gss_printf(major_status, minor_status, "_cupsSetNegotiateAuthString: Continuation needed"); |
446 | | # endif // DEBUG |
447 | | |
448 | | if (output_token.length > 0 && output_token.length <= 65536) |
449 | | { |
450 | | // Allocate the authorization string since Windows KDCs can have |
451 | | // arbitrarily large credentials... |
452 | | size_t authsize = (size_t)(10 + // "Negotiate " |
453 | | ((output_token.length * 4 / 3 + 3) & ~3U) + 1); |
454 | | // Base64 + nul |
455 | | |
456 | | httpSetAuthString(http, NULL, NULL); |
457 | | |
458 | | if ((http->authstring = malloc((size_t)authsize)) == NULL) |
459 | | { |
460 | | http->authstring = http->_authstring; |
461 | | authsize = sizeof(http->_authstring); |
462 | | } |
463 | | |
464 | | cupsCopyString(http->authstring, "Negotiate ", (size_t)authsize); |
465 | | httpEncode64_3(http->authstring + 10, authsize - 10, output_token.value, output_token.length, false); |
466 | | |
467 | | gss_release_buffer(&minor_status, &output_token); |
468 | | } |
469 | | else |
470 | | { |
471 | | DEBUG_printf("1_cupsSetNegotiateAuthString: Kerberos credentials too large - %d bytes!", (int)output_token.length); |
472 | | gss_release_buffer(&minor_status, &output_token); |
473 | | |
474 | | return (CUPS_GSS_FAIL); |
475 | | } |
476 | | |
477 | | return (CUPS_GSS_OK); |
478 | | } |
479 | | #endif // HAVE_GSSAPI |
480 | | |
481 | | |
482 | | // |
483 | | // 'cups_auth_find()' - Find the named WWW-Authenticate scheme. |
484 | | // |
485 | | // The "www_authenticate" argument points to the current position in the header. |
486 | | // |
487 | | // Returns `NULL` if the auth scheme is not present. |
488 | | // |
489 | | |
490 | | static const char * // O - Start of matching scheme or `NULL` if not found |
491 | | cups_auth_find(const char *www_authenticate, // I - Pointer into WWW-Authenticate header |
492 | | const char *scheme) // I - Authentication scheme |
493 | 0 | { |
494 | 0 | size_t schemelen = strlen(scheme); // Length of scheme |
495 | | |
496 | |
|
497 | 0 | DEBUG_printf("8cups_auth_find(www_authenticate=\"%s\", scheme=\"%s\"(%d))", www_authenticate, scheme, (int)schemelen); |
498 | |
|
499 | 0 | while (*www_authenticate) |
500 | 0 | { |
501 | | // Skip leading whitespace and commas... |
502 | 0 | DEBUG_printf("9cups_auth_find: Before whitespace: \"%s\"", www_authenticate); |
503 | 0 | while (isspace(*www_authenticate & 255) || *www_authenticate == ',') |
504 | 0 | www_authenticate ++; |
505 | 0 | DEBUG_printf("9cups_auth_find: After whitespace: \"%s\"", www_authenticate); |
506 | | |
507 | | // See if this is "Scheme" followed by whitespace or the end of the string. |
508 | 0 | if (!strncmp(www_authenticate, scheme, schemelen) && (isspace(www_authenticate[schemelen] & 255) || www_authenticate[schemelen] == ',' || !www_authenticate[schemelen])) |
509 | 0 | { |
510 | | // Yes, this is the start of the scheme-specific information... |
511 | 0 | DEBUG_printf("9cups_auth_find: Returning \"%s\".", www_authenticate); |
512 | |
|
513 | 0 | return (www_authenticate); |
514 | 0 | } |
515 | | |
516 | | // Skip the scheme name or param="value" string... |
517 | 0 | while (!isspace(*www_authenticate & 255) && *www_authenticate) |
518 | 0 | { |
519 | 0 | if (*www_authenticate == '\"') |
520 | 0 | { |
521 | | // Skip quoted value... |
522 | 0 | www_authenticate ++; |
523 | 0 | while (*www_authenticate && *www_authenticate != '\"') |
524 | 0 | www_authenticate ++; |
525 | |
|
526 | 0 | DEBUG_printf("9cups_auth_find: After quoted: \"%s\"", www_authenticate); |
527 | 0 | } |
528 | |
|
529 | 0 | www_authenticate ++; |
530 | 0 | } |
531 | |
|
532 | 0 | DEBUG_printf("9cups_auth_find: After skip: \"%s\"", www_authenticate); |
533 | 0 | } |
534 | | |
535 | 0 | DEBUG_puts("9cups_auth_find: Returning NULL."); |
536 | |
|
537 | 0 | return (NULL); |
538 | 0 | } |
539 | | |
540 | | |
541 | | // |
542 | | // 'cups_auth_param()' - Copy the value for the named authentication parameter, |
543 | | // if present. |
544 | | // |
545 | | |
546 | | static const char * // O - Parameter value or `NULL` if not present |
547 | | cups_auth_param(const char *scheme, // I - Pointer to auth data |
548 | | const char *name, // I - Name of parameter |
549 | | char *value, // I - Value buffer |
550 | | size_t valsize) // I - Size of value buffer |
551 | 0 | { |
552 | 0 | char *valptr = value, // Pointer into value buffer |
553 | 0 | *valend = value + valsize - 1; // Pointer to end of buffer |
554 | 0 | size_t namelen = strlen(name); // Name length |
555 | 0 | bool param; // Is this a parameter? |
556 | | |
557 | |
|
558 | 0 | DEBUG_printf("8cups_auth_param(scheme=\"%s\", name=\"%s\", value=%p, valsize=%d)", scheme, name, (void *)value, (int)valsize); |
559 | |
|
560 | 0 | while (!isspace(*scheme & 255) && *scheme) |
561 | 0 | scheme ++; |
562 | |
|
563 | 0 | while (*scheme) |
564 | 0 | { |
565 | 0 | while (isspace(*scheme & 255) || *scheme == ',') |
566 | 0 | scheme ++; |
567 | |
|
568 | 0 | if (!strncmp(scheme, name, namelen) && scheme[namelen] == '=') |
569 | 0 | { |
570 | | // Found the parameter, copy the value... |
571 | 0 | scheme += namelen + 1; |
572 | 0 | if (*scheme == '\"') |
573 | 0 | { |
574 | 0 | scheme ++; |
575 | |
|
576 | 0 | while (*scheme && *scheme != '\"') |
577 | 0 | { |
578 | 0 | if (valptr < valend) |
579 | 0 | *valptr++ = *scheme; |
580 | |
|
581 | 0 | scheme ++; |
582 | 0 | } |
583 | 0 | } |
584 | 0 | else |
585 | 0 | { |
586 | 0 | while (*scheme && strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~+/=", *scheme)) |
587 | 0 | { |
588 | 0 | if (valptr < valend) |
589 | 0 | *valptr++ = *scheme; |
590 | |
|
591 | 0 | scheme ++; |
592 | 0 | } |
593 | 0 | } |
594 | |
|
595 | 0 | *valptr = '\0'; |
596 | |
|
597 | 0 | DEBUG_printf("9cups_auth_param: Returning \"%s\".", value); |
598 | |
|
599 | 0 | return (value); |
600 | 0 | } |
601 | | |
602 | | // Skip the param=value string... |
603 | 0 | param = false; |
604 | |
|
605 | 0 | while (!isspace(*scheme & 255) && *scheme) |
606 | 0 | { |
607 | 0 | if (*scheme == '=') |
608 | 0 | { |
609 | 0 | param = true; |
610 | 0 | } |
611 | 0 | else if (*scheme == '\"') |
612 | 0 | { |
613 | | // Skip quoted value... |
614 | 0 | scheme ++; |
615 | 0 | while (*scheme && *scheme != '\"') |
616 | 0 | scheme ++; |
617 | 0 | } |
618 | |
|
619 | 0 | scheme ++; |
620 | 0 | } |
621 | | |
622 | | // If this wasn't a parameter, we are at the end of this scheme's |
623 | | // parameters... |
624 | 0 | if (!param) |
625 | 0 | break; |
626 | 0 | } |
627 | | |
628 | 0 | *value = '\0'; |
629 | |
|
630 | 0 | DEBUG_puts("9cups_auth_param: Returning NULL."); |
631 | |
|
632 | 0 | return (NULL); |
633 | 0 | } |
634 | | |
635 | | |
636 | | // |
637 | | // 'cups_auth_scheme()' - Get the (next) WWW-Authenticate scheme. |
638 | | // |
639 | | // The "www_authenticate" argument points to the current position in the header. |
640 | | // |
641 | | // Returns `NULL` if there are no (more) auth schemes present. |
642 | | // |
643 | | |
644 | | static const char * // O - Start of scheme or `NULL` if not found |
645 | | cups_auth_scheme(const char *www_authenticate, // I - Pointer into WWW-Authenticate header |
646 | | char *scheme, // I - Scheme name buffer |
647 | | size_t schemesize) // I - Size of buffer |
648 | 0 | { |
649 | 0 | const char *start; // Start of scheme data |
650 | 0 | char *sptr = scheme, // Pointer into scheme buffer |
651 | 0 | *send = scheme + schemesize - 1;// End of scheme buffer |
652 | 0 | bool param; // Is this a parameter? |
653 | | |
654 | |
|
655 | 0 | DEBUG_printf("8cups_auth_scheme(www_authenticate=\"%s\", scheme=%p, schemesize=%u)", www_authenticate, (void *)scheme, (unsigned)schemesize); |
656 | |
|
657 | 0 | while (*www_authenticate) |
658 | 0 | { |
659 | | // Skip leading whitespace and commas... |
660 | 0 | while (isspace(*www_authenticate & 255) || *www_authenticate == ',') |
661 | 0 | www_authenticate ++; |
662 | | |
663 | | // Parse the scheme name or param="value" string... |
664 | 0 | for (sptr = scheme, start = www_authenticate, param = false; *www_authenticate && *www_authenticate != ',' && !isspace(*www_authenticate & 255); www_authenticate ++) |
665 | 0 | { |
666 | 0 | if (*www_authenticate == '=') |
667 | 0 | { |
668 | 0 | param = true; |
669 | 0 | } |
670 | 0 | else if (!param && sptr < send) |
671 | 0 | { |
672 | 0 | *sptr++ = *www_authenticate; |
673 | 0 | } |
674 | 0 | else if (*www_authenticate == '\"') |
675 | 0 | { |
676 | | // Skip quoted value... |
677 | 0 | do |
678 | 0 | { |
679 | 0 | www_authenticate ++; |
680 | 0 | } |
681 | 0 | while (*www_authenticate && *www_authenticate != '\"'); |
682 | 0 | } |
683 | 0 | } |
684 | |
|
685 | 0 | if (sptr > scheme && !param) |
686 | 0 | { |
687 | 0 | *sptr = '\0'; |
688 | |
|
689 | 0 | DEBUG_printf("9cups_auth_scheme: Returning \"%s\".", start); |
690 | |
|
691 | 0 | return (start); |
692 | 0 | } |
693 | 0 | } |
694 | | |
695 | 0 | *scheme = '\0'; |
696 | |
|
697 | 0 | DEBUG_puts("9cups_auth_scheme: Returning NULL."); |
698 | |
|
699 | 0 | return (NULL); |
700 | 0 | } |
701 | | |
702 | | |
703 | | // |
704 | | // 'cups_do_local_auth()' - Use local credentials if available/applicable. |
705 | | // |
706 | | |
707 | | static bool // O - `true` if authorized, `false` otherwise |
708 | | cups_do_local_auth(http_t *http) // I - HTTP connection to server |
709 | 0 | { |
710 | 0 | #if !_WIN32 && !__EMX__ |
711 | 0 | int pid; // Current process ID |
712 | 0 | FILE *fp; // Certificate file |
713 | 0 | char trc[16], // Try Root Certificate parameter |
714 | 0 | filename[1024]; // Certificate filename |
715 | 0 | const char *www_auth, // WWW-Authenticate header |
716 | 0 | *schemedata; // Data for the named auth scheme |
717 | 0 | _cups_globals_t *cg = _cupsGlobals(); // Global data |
718 | | # if defined(HAVE_AUTHORIZATION_H) |
719 | | OSStatus status; // Status |
720 | | AuthorizationItem auth_right; // Authorization right |
721 | | AuthorizationRights auth_rights; // Authorization rights |
722 | | AuthorizationFlags auth_flags; // Authorization flags |
723 | | AuthorizationExternalForm auth_extrn; // Authorization ref external |
724 | | char auth_key[1024]; // Buffer |
725 | | char buffer[1024]; // Buffer |
726 | | # endif // HAVE_AUTHORIZATION_H |
727 | | |
728 | |
|
729 | 0 | DEBUG_printf("7cups_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"", (void *)http, httpAddrString(http->hostaddr, filename, sizeof(filename)), http->hostname); |
730 | | |
731 | | // See if we are accessing localhost... |
732 | 0 | if (!httpAddrIsLocalhost(httpGetAddress(http))) |
733 | 0 | { |
734 | 0 | DEBUG_puts("8cups_local_auth: Not a local connection, returning false."); |
735 | 0 | return (false); |
736 | 0 | } |
737 | | |
738 | 0 | www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE); |
739 | |
|
740 | | # if defined(HAVE_AUTHORIZATION_H) |
741 | | // Delete any previous authorization reference... |
742 | | if (http->auth_ref) |
743 | | { |
744 | | AuthorizationFree(http->auth_ref, kAuthorizationFlagDefaults); |
745 | | http->auth_ref = NULL; |
746 | | } |
747 | | |
748 | | if (!getenv("GATEWAY_INTERFACE") && (schemedata = cups_auth_find(www_auth, "AuthRef")) != NULL && cups_auth_param(schemedata, "key", auth_key, sizeof(auth_key))) |
749 | | { |
750 | | status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &http->auth_ref); |
751 | | if (status != errAuthorizationSuccess) |
752 | | { |
753 | | DEBUG_printf("8cups_local_auth: AuthorizationCreate() returned %d, returning false.", (int)status); |
754 | | return (false); |
755 | | } |
756 | | |
757 | | auth_right.name = auth_key; |
758 | | auth_right.valueLength = 0; |
759 | | auth_right.value = NULL; |
760 | | auth_right.flags = 0; |
761 | | |
762 | | auth_rights.count = 1; |
763 | | auth_rights.items = &auth_right; |
764 | | |
765 | | auth_flags = kAuthorizationFlagDefaults | |
766 | | kAuthorizationFlagPreAuthorize | |
767 | | kAuthorizationFlagInteractionAllowed | |
768 | | kAuthorizationFlagExtendRights; |
769 | | |
770 | | status = AuthorizationCopyRights(http->auth_ref, &auth_rights, |
771 | | kAuthorizationEmptyEnvironment, |
772 | | auth_flags, NULL); |
773 | | if (status == errAuthorizationSuccess) |
774 | | status = AuthorizationMakeExternalForm(http->auth_ref, &auth_extrn); |
775 | | |
776 | | if (status == errAuthorizationSuccess) |
777 | | { |
778 | | // Set the authorization string and return... |
779 | | httpEncode64_3(buffer, sizeof(buffer), (void *)&auth_extrn, sizeof(auth_extrn), false); |
780 | | |
781 | | httpSetAuthString(http, "AuthRef", buffer); |
782 | | |
783 | | DEBUG_printf("8cups_local_auth: Returning authstring=\"%s\", returning true.", http->authstring); |
784 | | return (true); |
785 | | } |
786 | | else if (status == errAuthorizationCanceled) |
787 | | { |
788 | | return (false); |
789 | | } |
790 | | |
791 | | DEBUG_printf("9cups_local_auth: AuthorizationCopyRights() returned %d.", (int)status); |
792 | | |
793 | | // Fall through to try certificates... |
794 | | } |
795 | | # endif // HAVE_AUTHORIZATION_H |
796 | |
|
797 | 0 | # if defined(SO_PEERCRED) && defined(AF_LOCAL) |
798 | | // See if we can authenticate using the peer credentials provided over a |
799 | | // domain socket; if so, specify "PeerCred username" as the authentication |
800 | | // information... |
801 | 0 | if (http->hostaddr->addr.sa_family == AF_LOCAL && |
802 | 0 | !getenv("GATEWAY_INTERFACE") && // Not via CGI programs... |
803 | 0 | cups_auth_find(www_auth, "PeerCred")) |
804 | 0 | { |
805 | | // Verify that the current cupsGetUser() matches the current UID... |
806 | 0 | struct passwd pwd; // Password information |
807 | 0 | struct passwd *result; // Auxiliary pointer |
808 | 0 | const char *username; // Current username |
809 | |
|
810 | 0 | username = cupsGetUser(); |
811 | |
|
812 | 0 | getpwnam_r(username, &pwd, cg->pw_buf, PW_BUF_SIZE, &result); |
813 | 0 | if (result && pwd.pw_uid == getuid()) |
814 | 0 | { |
815 | 0 | httpSetAuthString(http, "PeerCred", username); |
816 | |
|
817 | 0 | DEBUG_printf("8cups_local_auth: Returning authstring=\"%s\", returning true.", http->authstring); |
818 | |
|
819 | 0 | return (true); |
820 | 0 | } |
821 | 0 | } |
822 | 0 | # endif // SO_PEERCRED && AF_LOCAL |
823 | | |
824 | 0 | if ((schemedata = cups_auth_find(www_auth, "Local")) == NULL) |
825 | 0 | return (false); |
826 | | |
827 | | // Try opening a certificate file for this PID. If that fails, |
828 | | // try the root certificate... |
829 | 0 | pid = getpid(); |
830 | 0 | snprintf(filename, sizeof(filename), "%s/certs/%d", cg->cups_statedir, pid); |
831 | 0 | if ((fp = fopen(filename, "r")) == NULL && pid > 0) |
832 | 0 | { |
833 | | // No certificate for this PID; see if we can get the root certificate... |
834 | 0 | DEBUG_printf("9cups_local_auth: Unable to open file \"%s\": %s", filename, strerror(errno)); |
835 | |
|
836 | 0 | if (!cups_auth_param(schemedata, "trc", trc, sizeof(trc))) |
837 | 0 | { |
838 | | // Scheduler doesn't want us to use the root certificate... |
839 | 0 | return (false); |
840 | 0 | } |
841 | | |
842 | 0 | snprintf(filename, sizeof(filename), "%s/certs/0", cg->cups_statedir); |
843 | 0 | if ((fp = fopen(filename, "r")) == NULL) |
844 | 0 | DEBUG_printf("9cups_local_auth: Unable to open file \"%s\": %s", filename, strerror(errno)); |
845 | 0 | } |
846 | | |
847 | 0 | if (fp) |
848 | 0 | { |
849 | | // Read the certificate from the file... |
850 | 0 | char certificate[33], // Certificate string |
851 | 0 | *certptr; // Pointer to certificate string |
852 | |
|
853 | 0 | certptr = fgets(certificate, sizeof(certificate), fp); |
854 | 0 | fclose(fp); |
855 | |
|
856 | 0 | if (certptr) |
857 | 0 | { |
858 | | // Set the authorization string and return... |
859 | 0 | httpSetAuthString(http, "Local", certificate); |
860 | |
|
861 | 0 | DEBUG_printf("8cups_local_auth: Returning authstring=\"%s\", returning true.", http->authstring); |
862 | |
|
863 | 0 | return (true); |
864 | 0 | } |
865 | 0 | } |
866 | 0 | #endif // !_WIN32 && !__EMX__ |
867 | | |
868 | 0 | DEBUG_puts("8cups_do_local_auth: Returning false."); |
869 | |
|
870 | | return (false); |
871 | 0 | } |
872 | | |
873 | | |
874 | | #ifdef HAVE_GSSAPI |
875 | | # ifdef HAVE_GSS_ACQUIRE_CRED_EX_F |
876 | | // |
877 | | // 'cups_gss_acquire()' - Kerberos credentials callback. |
878 | | // |
879 | | static void |
880 | | cups_gss_acquire( |
881 | | void *ctx, // I - Caller context |
882 | | OM_uint32 major, // I - Major error code |
883 | | gss_status_id_t status, // I - Status (unused) |
884 | | gss_cred_id_t creds, // I - Credentials (if any) |
885 | | gss_OID_set oids, // I - Mechanism OIDs (unused) |
886 | | OM_uint32 time_rec) // I - Timestamp (unused) |
887 | | { |
888 | | uint32_t min; // Minor error code |
889 | | _cups_gss_acquire_t *data; // Callback data |
890 | | |
891 | | |
892 | | (void)status; |
893 | | (void)time_rec; |
894 | | |
895 | | data = (_cups_gss_acquire_t *)ctx; |
896 | | data->major = major; |
897 | | data->creds = creds; |
898 | | |
899 | | gss_release_oid_set(&min, &oids); |
900 | | dispatch_semaphore_signal(data->sem); |
901 | | } |
902 | | # endif // HAVE_GSS_ACQUIRE_CRED_EX_F |
903 | | |
904 | | |
905 | | // |
906 | | // 'cups_gss_getname()' - Get CUPS service credentials for authentication. |
907 | | // |
908 | | |
909 | | static gss_name_t // O - Server name |
910 | | cups_gss_getname( |
911 | | http_t *http, // I - Connection to server |
912 | | const char *service_name) // I - Service name |
913 | | { |
914 | | gss_buffer_desc token = GSS_C_EMPTY_BUFFER; |
915 | | // Service token |
916 | | OM_uint32 major_status, // Major status code |
917 | | minor_status; // Minor status code |
918 | | gss_name_t server_name; // Server name |
919 | | char buf[1024]; // Name buffer |
920 | | |
921 | | |
922 | | DEBUG_printf("7cups_gss_getname(http=%p, service_name=\"%s\")", (void *)http, service_name); |
923 | | |
924 | | // Get the hostname... |
925 | | if (!http->gsshost[0]) |
926 | | { |
927 | | httpGetHostname(http, http->gsshost, sizeof(http->gsshost)); |
928 | | |
929 | | if (!strcmp(http->gsshost, "localhost")) |
930 | | { |
931 | | if (gethostname(http->gsshost, sizeof(http->gsshost)) < 0) |
932 | | { |
933 | | DEBUG_printf("1cups_gss_getname: gethostname() failed: %s", strerror(errno)); |
934 | | http->gsshost[0] = '\0'; |
935 | | return (NULL); |
936 | | } |
937 | | |
938 | | if (!strchr(http->gsshost, '.')) |
939 | | { |
940 | | // The hostname is not a FQDN, so look it up... |
941 | | struct hostent *host; // Host entry to get FQDN |
942 | | |
943 | | if ((host = gethostbyname(http->gsshost)) != NULL && host->h_name) |
944 | | { |
945 | | // Use the resolved hostname... |
946 | | cupsCopyString(http->gsshost, host->h_name, sizeof(http->gsshost)); |
947 | | } |
948 | | else |
949 | | { |
950 | | DEBUG_printf("1cups_gss_getname: gethostbyname(\"%s\") failed.", http->gsshost); |
951 | | http->gsshost[0] = '\0'; |
952 | | return (NULL); |
953 | | } |
954 | | } |
955 | | } |
956 | | } |
957 | | |
958 | | // Get a service name we can use for authentication purposes... |
959 | | snprintf(buf, sizeof(buf), "%s@%s", service_name, http->gsshost); |
960 | | |
961 | | DEBUG_printf("8cups_gss_getname: Looking up \"%s\".", buf); |
962 | | |
963 | | token.value = buf; |
964 | | token.length = strlen(buf); |
965 | | server_name = GSS_C_NO_NAME; |
966 | | major_status = gss_import_name(&minor_status, &token, |
967 | | GSS_C_NT_HOSTBASED_SERVICE, |
968 | | &server_name); |
969 | | |
970 | | if (GSS_ERROR(major_status)) |
971 | | { |
972 | | cups_gss_printf(major_status, minor_status, |
973 | | "cups_gss_getname: gss_import_name() failed"); |
974 | | return (NULL); |
975 | | } |
976 | | |
977 | | return (server_name); |
978 | | } |
979 | | |
980 | | |
981 | | # ifdef DEBUG |
982 | | // |
983 | | // 'cups_gss_printf()' - Show debug error messages from GSSAPI. |
984 | | // |
985 | | |
986 | | static void |
987 | | cups_gss_printf(OM_uint32 major_status,// I - Major status code |
988 | | OM_uint32 minor_status,// I - Minor status code |
989 | | const char *message) // I - Prefix for error message |
990 | | { |
991 | | OM_uint32 err_major_status, // Major status code for display |
992 | | err_minor_status; // Minor status code for display |
993 | | OM_uint32 msg_ctx; // Message context |
994 | | gss_buffer_desc major_status_string = GSS_C_EMPTY_BUFFER, |
995 | | // Major status message |
996 | | minor_status_string = GSS_C_EMPTY_BUFFER; |
997 | | // Minor status message |
998 | | |
999 | | |
1000 | | msg_ctx = 0; |
1001 | | err_major_status = gss_display_status(&err_minor_status, |
1002 | | major_status, |
1003 | | GSS_C_GSS_CODE, |
1004 | | GSS_C_NO_OID, |
1005 | | &msg_ctx, |
1006 | | &major_status_string); |
1007 | | |
1008 | | if (!GSS_ERROR(err_major_status)) |
1009 | | gss_display_status(&err_minor_status, minor_status, GSS_C_MECH_CODE, |
1010 | | GSS_C_NULL_OID, &msg_ctx, &minor_status_string); |
1011 | | |
1012 | | DEBUG_printf("1%s: %s, %s", message, (char *)major_status_string.value, (char *)minor_status_string.value); |
1013 | | |
1014 | | gss_release_buffer(&err_minor_status, &major_status_string); |
1015 | | gss_release_buffer(&err_minor_status, &minor_status_string); |
1016 | | } |
1017 | | # endif // DEBUG |
1018 | | #endif // HAVE_GSSAPI |