Coverage Report

Created: 2026-09-01 06:51

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-2026 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") && httpAddrGetFamily(httpGetAddress(http)) != AF_LOCAL)
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
      if (*www_authenticate)
530
0
        www_authenticate ++;
531
0
    }
532
533
0
    DEBUG_printf("9cups_auth_find: After skip: \"%s\"", www_authenticate);
534
0
  }
535
536
0
  DEBUG_puts("9cups_auth_find: Returning NULL.");
537
538
0
  return (NULL);
539
0
}
540
541
542
//
543
// 'cups_auth_param()' - Copy the value for the named authentication parameter,
544
//                       if present.
545
//
546
547
static const char *       // O - Parameter value or `NULL` if not present
548
cups_auth_param(const char *scheme,   // I - Pointer to auth data
549
                const char *name,   // I - Name of parameter
550
                char       *value,    // I - Value buffer
551
                size_t     valsize)   // I - Size of value buffer
552
0
{
553
0
  char    *valptr = value,    // Pointer into value buffer
554
0
          *valend = value + valsize - 1;  // Pointer to end of buffer
555
0
  size_t  namelen = strlen(name);   // Name length
556
0
  bool    param;        // Is this a parameter?
557
558
559
0
  DEBUG_printf("8cups_auth_param(scheme=\"%s\", name=\"%s\", value=%p, valsize=%d)", scheme, name, (void *)value, (int)valsize);
560
561
0
  while (!isspace(*scheme & 255) && *scheme)
562
0
    scheme ++;
563
564
0
  while (*scheme)
565
0
  {
566
0
    while (isspace(*scheme & 255) || *scheme == ',')
567
0
      scheme ++;
568
569
0
    if (!strncmp(scheme, name, namelen) && scheme[namelen] == '=')
570
0
    {
571
      // Found the parameter, copy the value...
572
0
      scheme += namelen + 1;
573
0
      if (*scheme == '\"')
574
0
      {
575
0
        scheme ++;
576
577
0
  while (*scheme && *scheme != '\"')
578
0
  {
579
0
    if (valptr < valend)
580
0
      *valptr++ = *scheme;
581
582
0
    scheme ++;
583
0
  }
584
0
      }
585
0
      else
586
0
      {
587
0
  while (*scheme && strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~+/=", *scheme))
588
0
  {
589
0
    if (valptr < valend)
590
0
      *valptr++ = *scheme;
591
592
0
    scheme ++;
593
0
  }
594
0
      }
595
596
0
      *valptr = '\0';
597
598
0
      DEBUG_printf("9cups_auth_param: Returning \"%s\".", value);
599
600
0
      return (value);
601
0
    }
602
603
    // Skip the param=value string...
604
0
    param = false;
605
606
0
    while (!isspace(*scheme & 255) && *scheme)
607
0
    {
608
0
      if (*scheme == '=')
609
0
      {
610
0
        param = true;
611
0
      }
612
0
      else if (*scheme == '\"')
613
0
      {
614
        // Skip quoted value...
615
0
        scheme ++;
616
0
        while (*scheme && *scheme != '\"')
617
0
          scheme ++;
618
0
      }
619
620
0
      if (*scheme)
621
0
        scheme ++;
622
0
    }
623
624
    // If this wasn't a parameter, we are at the end of this scheme's
625
    // parameters...
626
0
    if (!param)
627
0
      break;
628
0
  }
629
630
0
  *value = '\0';
631
632
0
  DEBUG_puts("9cups_auth_param: Returning NULL.");
633
634
0
  return (NULL);
635
0
}
636
637
638
//
639
// 'cups_auth_scheme()' - Get the (next) WWW-Authenticate scheme.
640
//
641
// The "www_authenticate" argument points to the current position in the header.
642
//
643
// Returns `NULL` if there are no (more) auth schemes present.
644
//
645
646
static const char *       // O - Start of scheme or `NULL` if not found
647
cups_auth_scheme(const char *www_authenticate,  // I - Pointer into WWW-Authenticate header
648
     char       *scheme,    // I - Scheme name buffer
649
     size_t     schemesize)   // I - Size of buffer
650
0
{
651
0
  const char  *start;       // Start of scheme data
652
0
  char    *sptr = scheme,     // Pointer into scheme buffer
653
0
    *send = scheme + schemesize - 1;// End of scheme buffer
654
0
  bool    param;        // Is this a parameter?
655
656
657
0
  DEBUG_printf("8cups_auth_scheme(www_authenticate=\"%s\", scheme=%p, schemesize=%u)", www_authenticate, (void *)scheme, (unsigned)schemesize);
658
659
0
  while (*www_authenticate)
660
0
  {
661
    // Skip leading whitespace and commas...
662
0
    while (isspace(*www_authenticate & 255) || *www_authenticate == ',')
663
0
      www_authenticate ++;
664
665
    // Parse the scheme name or param="value" string...
666
0
    for (sptr = scheme, start = www_authenticate, param = false; *www_authenticate && *www_authenticate != ',' && !isspace(*www_authenticate & 255); www_authenticate ++)
667
0
    {
668
0
      if (*www_authenticate == '=')
669
0
      {
670
0
        param = true;
671
0
      }
672
0
      else if (!param && sptr < send)
673
0
      {
674
0
        *sptr++ = *www_authenticate;
675
0
      }
676
0
      else if (*www_authenticate == '\"')
677
0
      {
678
        // Skip quoted value...
679
0
        do
680
0
        {
681
0
          www_authenticate ++;
682
0
        }
683
0
        while (*www_authenticate && *www_authenticate != '\"');
684
685
0
        if (!*www_authenticate)
686
0
          break;
687
0
      }
688
0
    }
689
690
0
    if (sptr > scheme && !param)
691
0
    {
692
0
      *sptr = '\0';
693
694
0
      DEBUG_printf("9cups_auth_scheme: Returning \"%s\".", start);
695
696
0
      return (start);
697
0
    }
698
0
  }
699
700
0
  *scheme = '\0';
701
702
0
  DEBUG_puts("9cups_auth_scheme: Returning NULL.");
703
704
0
  return (NULL);
705
0
}
706
707
708
//
709
// 'cups_do_local_auth()' - Use local credentials if available/applicable.
710
//
711
712
static bool       // O - `true` if authorized, `false` otherwise
713
cups_do_local_auth(http_t *http)  // I - HTTP connection to server
714
0
{
715
0
#if !_WIN32 && !__EMX__ && defined(AF_LOCAL)
716
0
  int     pid;    // Current process ID
717
0
  FILE      *fp;    // Certificate file
718
0
  char      trc[16],  // Try Root Certificate parameter
719
0
      filename[1024]; // Certificate filename
720
0
  const char    *www_auth,  // WWW-Authenticate header
721
0
      *schemedata;  // Data for the named auth scheme
722
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
723
#  if defined(HAVE_AUTHORIZATION_H)
724
  OSStatus    status;   // Status
725
  AuthorizationItem auth_right; // Authorization right
726
  AuthorizationRights auth_rights;  // Authorization rights
727
  AuthorizationFlags  auth_flags; // Authorization flags
728
  AuthorizationExternalForm auth_extrn; // Authorization ref external
729
  char      auth_key[1024]; // Buffer
730
  char      buffer[1024]; // Buffer
731
#  endif // HAVE_AUTHORIZATION_H
732
733
734
0
  DEBUG_printf("7cups_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"", (void *)http, httpAddrString(http->hostaddr, filename, sizeof(filename)), http->hostname);
735
736
  // See if we are accessing localhost...
737
0
  if (httpAddrGetFamily(httpGetAddress(http)) != AF_LOCAL)
738
0
  {
739
0
    DEBUG_puts("8cups_local_auth: Not a local connection, returning false.");
740
0
    return (false);
741
0
  }
742
743
0
  www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
744
745
#  if defined(HAVE_AUTHORIZATION_H)
746
  // Delete any previous authorization reference...
747
  if (http->auth_ref)
748
  {
749
    AuthorizationFree(http->auth_ref, kAuthorizationFlagDefaults);
750
    http->auth_ref = NULL;
751
  }
752
753
  if (!getenv("GATEWAY_INTERFACE") && (schemedata = cups_auth_find(www_auth, "AuthRef")) != NULL && cups_auth_param(schemedata, "key", auth_key, sizeof(auth_key)))
754
  {
755
    status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &http->auth_ref);
756
    if (status != errAuthorizationSuccess)
757
    {
758
      DEBUG_printf("8cups_local_auth: AuthorizationCreate() returned %d, returning false.", (int)status);
759
      return (false);
760
    }
761
762
    auth_right.name        = auth_key;
763
    auth_right.valueLength = 0;
764
    auth_right.value       = NULL;
765
    auth_right.flags       = 0;
766
767
    auth_rights.count = 1;
768
    auth_rights.items = &auth_right;
769
770
    auth_flags = kAuthorizationFlagDefaults |
771
     kAuthorizationFlagPreAuthorize |
772
     kAuthorizationFlagInteractionAllowed |
773
     kAuthorizationFlagExtendRights;
774
775
    status = AuthorizationCopyRights(http->auth_ref, &auth_rights,
776
             kAuthorizationEmptyEnvironment,
777
             auth_flags, NULL);
778
    if (status == errAuthorizationSuccess)
779
      status = AuthorizationMakeExternalForm(http->auth_ref, &auth_extrn);
780
781
    if (status == errAuthorizationSuccess)
782
    {
783
      // Set the authorization string and return...
784
      httpEncode64_3(buffer, sizeof(buffer), (void *)&auth_extrn, sizeof(auth_extrn), false);
785
786
      httpSetAuthString(http, "AuthRef", buffer);
787
788
      DEBUG_printf("8cups_local_auth: Returning authstring=\"%s\", returning true.", http->authstring);
789
      return (true);
790
    }
791
    else if (status == errAuthorizationCanceled)
792
    {
793
      return (false);
794
    }
795
796
    DEBUG_printf("9cups_local_auth: AuthorizationCopyRights() returned %d.", (int)status);
797
798
   // Fall through to try certificates...
799
  }
800
#  endif // HAVE_AUTHORIZATION_H
801
802
0
#  if defined(SO_PEERCRED)
803
  // See if we can authenticate using the peer credentials provided over a
804
  // domain socket; if so, specify "PeerCred username" as the authentication
805
  // information...
806
0
  if (!getenv("GATEWAY_INTERFACE") && // Not via CGI programs...
807
0
      cups_auth_find(www_auth, "PeerCred"))
808
0
  {
809
    // Verify that the current cupsGetUser() matches the current UID...
810
0
    struct passwd pwd;    // Password information
811
0
    struct passwd *result;  // Auxiliary pointer
812
0
    const char    *username;  // Current username
813
814
0
    username = cupsGetUser();
815
816
0
    getpwnam_r(username, &pwd, cg->pw_buf, PW_BUF_SIZE, &result);
817
0
    if (result && pwd.pw_uid == getuid())
818
0
    {
819
0
      httpSetAuthString(http, "PeerCred", username);
820
821
0
      DEBUG_printf("8cups_local_auth: Returning authstring=\"%s\", returning true.", http->authstring);
822
823
0
      return (true);
824
0
    }
825
0
  }
826
0
#  endif // SO_PEERCRED && AF_LOCAL
827
828
0
  if ((schemedata = cups_auth_find(www_auth, "Local")) == NULL)
829
0
    return (false);
830
831
  // Try opening a certificate file for this PID.  If that fails,
832
  // try the root certificate...
833
0
  pid = getpid();
834
0
  snprintf(filename, sizeof(filename), "%s/certs/%d", cg->cups_statedir, pid);
835
0
  if ((fp = fopen(filename, "r")) == NULL && pid > 0)
836
0
  {
837
    // No certificate for this PID; see if we can get the root certificate...
838
0
    DEBUG_printf("9cups_local_auth: Unable to open file \"%s\": %s", filename, strerror(errno));
839
840
0
    if (!cups_auth_param(schemedata, "trc", trc, sizeof(trc)))
841
0
    {
842
      // Scheduler doesn't want us to use the root certificate...
843
0
      return (false);
844
0
    }
845
846
0
    snprintf(filename, sizeof(filename), "%s/certs/0", cg->cups_statedir);
847
0
    if ((fp = fopen(filename, "r")) == NULL)
848
0
      DEBUG_printf("9cups_local_auth: Unable to open file \"%s\": %s", filename, strerror(errno));
849
0
  }
850
851
0
  if (fp)
852
0
  {
853
    // Read the certificate from the file...
854
0
    char  certificate[33],  // Certificate string
855
0
    *certptr;   // Pointer to certificate string
856
857
0
    certptr = fgets(certificate, sizeof(certificate), fp);
858
0
    fclose(fp);
859
860
0
    if (certptr)
861
0
    {
862
      // Set the authorization string and return...
863
0
      httpSetAuthString(http, "Local", certificate);
864
865
0
      DEBUG_printf("8cups_local_auth: Returning authstring=\"%s\", returning true.", http->authstring);
866
867
0
      return (true);
868
0
    }
869
0
  }
870
0
#endif // !_WIN32 && !__EMX__ && defined(AF_LOCAL)
871
872
0
  DEBUG_puts("8cups_do_local_auth: Returning false.");
873
874
  return (false);
875
0
}
876
877
878
#ifdef HAVE_GSSAPI
879
#  ifdef HAVE_GSS_ACQUIRE_CRED_EX_F
880
//
881
// 'cups_gss_acquire()' - Kerberos credentials callback.
882
//
883
static void
884
cups_gss_acquire(
885
    void            *ctx,   // I - Caller context
886
    OM_uint32       major,    // I - Major error code
887
    gss_status_id_t status,   // I - Status (unused)
888
    gss_cred_id_t   creds,    // I - Credentials (if any)
889
    gss_OID_set     oids,   // I - Mechanism OIDs (unused)
890
    OM_uint32       time_rec)   // I - Timestamp (unused)
891
{
892
  uint32_t    min;    // Minor error code
893
  _cups_gss_acquire_t *data;    // Callback data
894
895
896
  (void)status;
897
  (void)time_rec;
898
899
  data        = (_cups_gss_acquire_t *)ctx;
900
  data->major = major;
901
  data->creds = creds;
902
903
  gss_release_oid_set(&min, &oids);
904
  dispatch_semaphore_signal(data->sem);
905
}
906
#  endif // HAVE_GSS_ACQUIRE_CRED_EX_F
907
908
909
//
910
// 'cups_gss_getname()' - Get CUPS service credentials for authentication.
911
//
912
913
static gss_name_t     // O - Server name
914
cups_gss_getname(
915
    http_t     *http,     // I - Connection to server
916
    const char *service_name)   // I - Service name
917
{
918
  gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
919
          // Service token
920
  OM_uint32   major_status,   // Major status code
921
      minor_status;   // Minor status code
922
  gss_name_t    server_name;    // Server name
923
  char      buf[1024];    // Name buffer
924
925
926
  DEBUG_printf("7cups_gss_getname(http=%p, service_name=\"%s\")", (void *)http, service_name);
927
928
  // Get the hostname...
929
  if (!http->gsshost[0])
930
  {
931
    httpGetHostname(http, http->gsshost, sizeof(http->gsshost));
932
933
    if (!strcmp(http->gsshost, "localhost"))
934
    {
935
      if (gethostname(http->gsshost, sizeof(http->gsshost)) < 0)
936
      {
937
  DEBUG_printf("1cups_gss_getname: gethostname() failed: %s", strerror(errno));
938
  http->gsshost[0] = '\0';
939
  return (NULL);
940
      }
941
942
      if (!strchr(http->gsshost, '.'))
943
      {
944
        // The hostname is not a FQDN, so look it up...
945
  struct hostent  *host;    // Host entry to get FQDN
946
947
  if ((host = gethostbyname(http->gsshost)) != NULL && host->h_name)
948
  {
949
    // Use the resolved hostname...
950
    cupsCopyString(http->gsshost, host->h_name, sizeof(http->gsshost));
951
  }
952
  else
953
  {
954
    DEBUG_printf("1cups_gss_getname: gethostbyname(\"%s\") failed.", http->gsshost);
955
    http->gsshost[0] = '\0';
956
    return (NULL);
957
  }
958
      }
959
    }
960
  }
961
962
  // Get a service name we can use for authentication purposes...
963
  snprintf(buf, sizeof(buf), "%s@%s", service_name, http->gsshost);
964
965
  DEBUG_printf("8cups_gss_getname: Looking up \"%s\".", buf);
966
967
  token.value  = buf;
968
  token.length = strlen(buf);
969
  server_name  = GSS_C_NO_NAME;
970
  major_status = gss_import_name(&minor_status, &token,
971
         GSS_C_NT_HOSTBASED_SERVICE,
972
         &server_name);
973
974
  if (GSS_ERROR(major_status))
975
  {
976
    cups_gss_printf(major_status, minor_status,
977
                    "cups_gss_getname: gss_import_name() failed");
978
    return (NULL);
979
  }
980
981
  return (server_name);
982
}
983
984
985
#  ifdef DEBUG
986
//
987
// 'cups_gss_printf()' - Show debug error messages from GSSAPI.
988
//
989
990
static void
991
cups_gss_printf(OM_uint32  major_status,// I - Major status code
992
    OM_uint32  minor_status,// I - Minor status code
993
    const char *message)  // I - Prefix for error message
994
{
995
  OM_uint32 err_major_status, // Major status code for display
996
    err_minor_status; // Minor status code for display
997
  OM_uint32 msg_ctx;    // Message context
998
  gss_buffer_desc major_status_string = GSS_C_EMPTY_BUFFER,
999
          // Major status message
1000
    minor_status_string = GSS_C_EMPTY_BUFFER;
1001
          // Minor status message
1002
1003
1004
  msg_ctx          = 0;
1005
  err_major_status = gss_display_status(&err_minor_status,
1006
                            major_status,
1007
          GSS_C_GSS_CODE,
1008
          GSS_C_NO_OID,
1009
          &msg_ctx,
1010
          &major_status_string);
1011
1012
  if (!GSS_ERROR(err_major_status))
1013
    gss_display_status(&err_minor_status, minor_status, GSS_C_MECH_CODE,
1014
           GSS_C_NULL_OID, &msg_ctx, &minor_status_string);
1015
1016
  DEBUG_printf("1%s: %s, %s", message, (char *)major_status_string.value, (char *)minor_status_string.value);
1017
1018
  gss_release_buffer(&err_minor_status, &major_status_string);
1019
  gss_release_buffer(&err_minor_status, &minor_status_string);
1020
}
1021
#  endif // DEBUG
1022
#endif // HAVE_GSSAPI