Coverage Report

Created: 2025-11-22 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/cups/cups/auth.c
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