Coverage Report

Created: 2026-08-31 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libcups/cups/auth.c
Line
Count
Source
1
//
2
// Authentication functions for CUPS.
3
//
4
// Copyright © 2021-2026 by OpenPrinting.
5
// Copyright © 2007-2019 by Apple Inc.
6
// Copyright © 1997-2007 by Easy Software Products.
7
//
8
// Licensed under Apache License v2.0.  See the file "LICENSE" for more
9
// information.
10
//
11
12
#include "cups-private.h"
13
#include <fcntl.h>
14
#include <sys/stat.h>
15
#if defined(_WIN32) || defined(__EMX__)
16
#  include <io.h>
17
#else
18
#  include <unistd.h>
19
#endif // _WIN32 || __EMX__
20
#if defined(SO_PEERCRED) && defined(AF_LOCAL)
21
#  include <pwd.h>
22
#endif // SO_PEERCRED && AF_LOCAL
23
24
25
//
26
// Local functions...
27
//
28
29
static const char *cups_auth_find(const char *www_authenticate, const char *scheme);
30
static const char *cups_auth_param(const char *scheme, const char *name, char *value, size_t valsize);
31
static const char *cups_auth_scheme(const char *www_authenticate, char *scheme, size_t schemesize);
32
static bool   cups_do_local_auth(http_t *http);
33
34
35
//
36
// 'cupsDoAuthentication()' - Authenticate a request.
37
//
38
// This function performs authentication for a request.  It should be called in
39
// response to a `HTTP_STATUS_UNAUTHORIZED` status, prior to resubmitting your
40
// request.
41
//
42
43
bool          // O - `true` on success, `false` on failure/error
44
cupsDoAuthentication(
45
    http_t     *http,     // I - Connection to server or @code CUPS_HTTP_DEFAULT@
46
    const char *method,     // I - Request method ("GET", "POST", "PUT")
47
    const char *resource)   // I - Resource path
48
0
{
49
0
  const char  *password,    // Password string
50
0
    *www_auth,    // WWW-Authenticate header
51
0
    *schemedata;    // Scheme-specific data
52
0
  char    scheme[256],    // Scheme name
53
0
    prompt[1024];   // Prompt for user
54
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
55
56
57
0
  DEBUG_printf("cupsDoAuthentication(http=%p, method=\"%s\", resource=\"%s\")", (void *)http, method, resource);
58
59
  // Range check input...
60
0
  if (!http)
61
0
    http = _cupsConnect();
62
63
0
  if (!http || !method || !resource)
64
0
    return (false);
65
66
0
  DEBUG_printf("2cupsDoAuthentication: digest_tries=%d, userpass=\"%s\"", http->digest_tries, http->userpass);
67
0
  DEBUG_printf("2cupsDoAuthentication: WWW-Authenticate=\"%s\"", httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE));
68
69
  // Clear the current authentication string...
70
0
  httpSetAuthString(http, NULL, NULL);
71
72
  // See if we can do local authentication...
73
0
  if (http->digest_tries < 3)
74
0
  {
75
0
    if (cups_do_local_auth(http))
76
0
    {
77
0
      DEBUG_printf("2cupsDoAuthentication: authstring=\"%s\"", http->authstring);
78
79
0
      if (http->status == HTTP_STATUS_UNAUTHORIZED)
80
0
  http->digest_tries ++;
81
82
0
      return (true);
83
0
    }
84
0
  }
85
86
  // Nope, loop through the authentication schemes to find the first we support.
87
0
  www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
88
89
0
  for (schemedata = cups_auth_scheme(www_auth, scheme, sizeof(scheme)); schemedata; schemedata = cups_auth_scheme(schemedata + strlen(scheme), scheme, sizeof(scheme)))
90
0
  {
91
    // Check the scheme name...
92
0
    DEBUG_printf("2cupsDoAuthentication: Trying scheme \"%s\"...", scheme);
93
94
0
    if (!_cups_strcasecmp(scheme, "Bearer"))
95
0
    {
96
      // OAuth 2.0 (Bearer) authentication...
97
0
      const char  *bearer = NULL; // Bearer token string, if any
98
99
0
      if (cg->oauth_cb)
100
0
      {
101
        // Try callback...
102
0
  char  scope[_HTTP_MAX_VALUE]; // scope="xyz" string
103
104
0
  cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm));
105
106
0
  if (cups_auth_param(schemedata, "scope", scope, sizeof(scope)))
107
0
    bearer = (cg->oauth_cb)(http, http->realm, scope, resource, cg->oauth_data);
108
0
  else
109
0
    bearer = (cg->oauth_cb)(http, http->realm, NULL, resource, cg->oauth_data);
110
0
      }
111
112
0
      if (bearer)
113
0
      {
114
        // Use this access token...
115
0
        httpSetAuthString(http, "Bearer", bearer);
116
0
        break;
117
0
      }
118
0
      else
119
0
      {
120
        // No access token, try the next scheme...
121
0
        DEBUG_puts("2cupsDoAuthentication: No OAuth access token to provide.");
122
0
        continue;
123
0
      }
124
0
    }
125
0
    else if (_cups_strcasecmp(scheme, "Basic") && _cups_strcasecmp(scheme, "Digest") && _cups_strcasecmp(scheme, "Negotiate"))
126
0
    {
127
      // Other schemes not yet supported...
128
0
      DEBUG_printf("2cupsDoAuthentication: Scheme \"%s\" not yet supported.", scheme);
129
0
      continue;
130
0
    }
131
132
    // See if we should retry the current username:password...
133
0
    if (http->digest_tries > 1 || !http->userpass[0])
134
0
    {
135
      // Nope - get a new password from the user...
136
0
      char default_username[_HTTP_MAX_VALUE];
137
          // Default username
138
139
0
      if (!cg->lang_default)
140
0
  cg->lang_default = cupsLangDefault();
141
142
0
      if (cups_auth_param(schemedata, "username", default_username, sizeof(default_username)))
143
0
  cupsSetUser(default_username);
144
145
0
      cupsLangFormatString(cg->lang_default, prompt, sizeof(prompt), _("Password for %s on %s? "), cupsGetUser(), http->hostname[0] == '/' ? "localhost" : http->hostname);
146
147
0
      http->digest_tries = _cups_strncasecmp(scheme, "Digest", 6) != 0;
148
0
      http->userpass[0]  = '\0';
149
150
0
      if ((password = cupsGetPassword(prompt, http, method, resource)) == NULL)
151
0
      {
152
0
        DEBUG_puts("1cupsDoAuthentication: User canceled password request.");
153
0
  http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
154
0
  return (false);
155
0
      }
156
157
0
      snprintf(http->userpass, sizeof(http->userpass), "%s:%s", cupsGetUser(), password);
158
0
    }
159
0
    else if (http->status == HTTP_STATUS_UNAUTHORIZED)
160
0
    {
161
0
      http->digest_tries ++;
162
0
    }
163
164
0
    if (http->status == HTTP_STATUS_UNAUTHORIZED && http->digest_tries >= 3)
165
0
    {
166
0
      DEBUG_printf("1cupsDoAuthentication: Too many authentication tries (%d)", http->digest_tries);
167
168
0
      http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
169
0
      return (false);
170
0
    }
171
172
    // Got a password; encode it for the server...
173
0
    if (!_cups_strcasecmp(scheme, "Basic"))
174
0
    {
175
      // Basic authentication...
176
0
      char  encode[256];    // Base64 buffer
177
178
0
      DEBUG_puts("2cupsDoAuthentication: Using Basic.");
179
0
      httpEncode64(encode, sizeof(encode), http->userpass, strlen(http->userpass), false);
180
0
      httpSetAuthString(http, "Basic", encode);
181
0
      break;
182
0
    }
183
0
    else if (!_cups_strcasecmp(scheme, "Digest"))
184
0
    {
185
      // Digest authentication...
186
0
      char nonce[_HTTP_MAX_VALUE];  // nonce="xyz" string
187
188
0
      cups_auth_param(schemedata, "algorithm", http->algorithm, sizeof(http->algorithm));
189
0
      cups_auth_param(schemedata, "nonce", nonce, sizeof(nonce));
190
0
      cups_auth_param(schemedata, "opaque", http->opaque, sizeof(http->opaque));
191
0
      cups_auth_param(schemedata, "qop", http->qop, sizeof(http->qop));
192
0
      cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm));
193
194
0
      if (_httpSetDigestAuthString(http, nonce, method, resource))
195
0
      {
196
0
  DEBUG_puts("2cupsDoAuthentication: Using Digest.");
197
0
        break;
198
0
      }
199
0
    }
200
0
  }
201
202
0
  if (http->authstring && http->authstring[0])
203
0
  {
204
0
    DEBUG_printf("1cupsDoAuthentication: authstring=\"%s\".", http->authstring);
205
206
0
    return (true);
207
0
  }
208
0
  else
209
0
  {
210
0
    DEBUG_puts("1cupsDoAuthentication: No supported schemes.");
211
0
    http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
212
213
0
    return (false);
214
0
  }
215
0
}
216
217
218
//
219
// 'cups_auth_find()' - Find the named WWW-Authenticate scheme.
220
//
221
// The "www_authenticate" parameter points to the current position in the header.
222
//
223
// Returns `NULL` if the auth scheme is not present.
224
//
225
226
static const char *       // O - Start of matching scheme or `NULL` if not found
227
cups_auth_find(const char *www_authenticate,  // I - Pointer into WWW-Authenticate header
228
               const char *scheme)    // I - Authentication scheme
229
0
{
230
0
  size_t  schemelen = strlen(scheme); // Length of scheme
231
232
233
0
  DEBUG_printf("8cups_auth_find(www_authenticate=\"%s\", scheme=\"%s\"(%d))", www_authenticate, scheme, (int)schemelen);
234
235
0
  while (*www_authenticate)
236
0
  {
237
    // Skip leading whitespace and commas...
238
0
    DEBUG_printf("9cups_auth_find: Before whitespace: \"%s\"", www_authenticate);
239
0
    while (isspace(*www_authenticate & 255) || *www_authenticate == ',')
240
0
      www_authenticate ++;
241
0
    DEBUG_printf("9cups_auth_find: After whitespace: \"%s\"", www_authenticate);
242
243
    // See if this is "Scheme" followed by whitespace or the end of the string.
244
0
    if (!strncmp(www_authenticate, scheme, schemelen) && (isspace(www_authenticate[schemelen] & 255) || www_authenticate[schemelen] == ',' || !www_authenticate[schemelen]))
245
0
    {
246
      // Yes, this is the start of the scheme-specific information...
247
0
      DEBUG_printf("9cups_auth_find: Returning \"%s\".", www_authenticate);
248
249
0
      return (www_authenticate);
250
0
    }
251
252
    // Skip the scheme name or param="value" string...
253
0
    while (!isspace(*www_authenticate & 255) && *www_authenticate)
254
0
    {
255
0
      if (*www_authenticate == '\"')
256
0
      {
257
        // Skip quoted value...
258
0
        www_authenticate ++;
259
0
        while (*www_authenticate && *www_authenticate != '\"')
260
0
          www_authenticate ++;
261
262
0
        DEBUG_printf("9cups_auth_find: After quoted: \"%s\"", www_authenticate);
263
0
      }
264
265
0
      if (*www_authenticate)
266
0
        www_authenticate ++;
267
0
    }
268
269
0
    DEBUG_printf("9cups_auth_find: After skip: \"%s\"", www_authenticate);
270
0
  }
271
272
0
  DEBUG_puts("9cups_auth_find: Returning NULL.");
273
274
0
  return (NULL);
275
0
}
276
277
278
//
279
// 'cups_auth_param()' - Copy the value for the named authentication parameter,
280
//                       if present.
281
//
282
283
static const char *       // O - Parameter value or `NULL` if not present
284
cups_auth_param(const char *scheme,   // I - Pointer to auth data
285
                const char *name,   // I - Name of parameter
286
                char       *value,    // I - Value buffer
287
                size_t     valsize)   // I - Size of value buffer
288
0
{
289
0
  char    *valptr = value,    // Pointer into value buffer
290
0
          *valend = value + valsize - 1;  // Pointer to end of buffer
291
0
  size_t  namelen = strlen(name);   // Name length
292
0
  bool    param;        // Is this a parameter?
293
294
295
0
  DEBUG_printf("8cups_auth_param(scheme=\"%s\", name=\"%s\", value=%p, valsize=%d)", scheme, name, (void *)value, (int)valsize);
296
297
0
  while (!isspace(*scheme & 255) && *scheme)
298
0
    scheme ++;
299
300
0
  while (*scheme)
301
0
  {
302
0
    while (isspace(*scheme & 255) || *scheme == ',')
303
0
      scheme ++;
304
305
0
    if (!strncmp(scheme, name, namelen) && scheme[namelen] == '=')
306
0
    {
307
      // Found the parameter, copy the value...
308
0
      scheme += namelen + 1;
309
0
      if (*scheme == '\"')
310
0
      {
311
0
        scheme ++;
312
313
0
  while (*scheme && *scheme != '\"')
314
0
  {
315
0
    if (valptr < valend)
316
0
      *valptr++ = *scheme;
317
318
0
    scheme ++;
319
0
  }
320
0
      }
321
0
      else
322
0
      {
323
0
  while (*scheme && strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~+/=", *scheme))
324
0
  {
325
0
    if (valptr < valend)
326
0
      *valptr++ = *scheme;
327
328
0
    scheme ++;
329
0
  }
330
0
      }
331
332
0
      *valptr = '\0';
333
334
0
      DEBUG_printf("9cups_auth_param: Returning \"%s\".", value);
335
336
0
      return (value);
337
0
    }
338
339
    // Skip the param=value string...
340
0
    param = false;
341
342
0
    while (!isspace(*scheme & 255) && *scheme)
343
0
    {
344
0
      if (*scheme == '=')
345
0
      {
346
0
        param = true;
347
0
      }
348
0
      else if (*scheme == '\"')
349
0
      {
350
        // Skip quoted value...
351
0
        scheme ++;
352
0
        while (*scheme && *scheme != '\"')
353
0
          scheme ++;
354
0
      }
355
356
0
      if (*scheme)
357
0
        scheme ++;
358
0
    }
359
360
    // If this wasn't a parameter, we are at the end of this scheme's
361
    // parameters...
362
0
    if (!param)
363
0
      break;
364
0
  }
365
366
0
  *value = '\0';
367
368
0
  DEBUG_puts("9cups_auth_param: Returning NULL.");
369
370
0
  return (NULL);
371
0
}
372
373
374
//
375
// 'cups_auth_scheme()' - Get the (next) WWW-Authenticate scheme.
376
//
377
// The "www_authenticate" argument points to the current position in the header.
378
//
379
// Returns `NULL` if there are no (more) auth schemes present.
380
//
381
382
static const char *       // O - Start of scheme or `NULL` if not found
383
cups_auth_scheme(const char *www_authenticate,  // I - Pointer into WWW-Authenticate header
384
     char       *scheme,    // I - Scheme name buffer
385
     size_t     schemesize)   // I - Size of buffer
386
0
{
387
0
  const char  *start;       // Start of scheme data
388
0
  char    *sptr = scheme,     // Pointer into scheme buffer
389
0
    *send = scheme + schemesize - 1;// End of scheme buffer
390
0
  bool    param;        // Is this a parameter?
391
392
393
0
  DEBUG_printf("8cups_auth_scheme(www_authenticate=\"%s\", scheme=%p, schemesize=%u)", www_authenticate, (void *)scheme, (unsigned)schemesize);
394
395
0
  while (*www_authenticate)
396
0
  {
397
    // Skip leading whitespace and commas...
398
0
    while (isspace(*www_authenticate & 255) || *www_authenticate == ',')
399
0
      www_authenticate ++;
400
401
    // Parse the scheme name or param="value" string...
402
0
    for (sptr = scheme, start = www_authenticate, param = false; *www_authenticate && *www_authenticate != ',' && !isspace(*www_authenticate & 255); www_authenticate ++)
403
0
    {
404
0
      if (*www_authenticate == '=')
405
0
      {
406
0
        param = true;
407
0
      }
408
0
      else if (!param && sptr < send)
409
0
      {
410
0
        *sptr++ = *www_authenticate;
411
0
      }
412
0
      else if (*www_authenticate == '\"')
413
0
      {
414
        // Skip quoted value...
415
0
        do
416
0
        {
417
0
          www_authenticate ++;
418
0
        }
419
0
        while (*www_authenticate && *www_authenticate != '\"');
420
421
0
        if (!*www_authenticate)
422
0
          break;
423
0
      }
424
0
    }
425
426
0
    if (sptr > scheme && !param)
427
0
    {
428
0
      *sptr = '\0';
429
430
0
      DEBUG_printf("9cups_auth_scheme: Returning \"%s\".", start);
431
432
0
      return (start);
433
0
    }
434
0
  }
435
436
0
  *scheme = '\0';
437
438
0
  DEBUG_puts("9cups_auth_scheme: Returning NULL.");
439
440
0
  return (NULL);
441
0
}
442
443
444
//
445
// 'cups_do_local_auth()' - Use local credentials if available/applicable.
446
//
447
448
static bool       // O - `true` if authorized, `false` otherwise
449
cups_do_local_auth(http_t *http)  // I - HTTP connection to server
450
0
{
451
#ifdef DEBUG
452
  char      hostaddr[256];  // Host address string
453
454
455
  DEBUG_printf("7cups_do_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"", (void *)http, httpAddrGetString(http->hostaddr, hostaddr, sizeof(hostaddr)), http->hostname);
456
#endif // DEBUG
457
458
0
#if defined(SO_PEERCRED) && defined(AF_LOCAL)
459
0
  const char    *www_auth;  // WWW-Authenticate header
460
0
  struct passwd   pwd;    // Password information
461
0
  struct passwd   *result;  // Auxiliary pointer
462
0
  const char    *username;  // Current username
463
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
464
465
  // See if we can authenticate using the peer credentials provided over a
466
  // domain socket; if so, specify "PeerCred username" as the authentication
467
  // information...
468
0
  www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
469
470
0
  if (http->hostaddr->addr.sa_family == AF_LOCAL && cups_auth_find(www_auth, "PeerCred"))
471
0
  {
472
    // Verify that the current cupsGetUser() matches the current UID...
473
0
    username = cupsGetUser();
474
475
0
    getpwnam_r(username, &pwd, cg->pw_buf, PW_BUF_SIZE, &result);
476
0
    if (result && pwd.pw_uid == getuid())
477
0
    {
478
0
      httpSetAuthString(http, "PeerCred", username);
479
480
0
      DEBUG_printf("8cups_do_local_auth: Returning true, authstring=\"%s\".", http->authstring);
481
482
0
      return (true);
483
0
    }
484
0
  }
485
0
#endif // SO_PEERCRED && AF_LOCAL
486
487
0
  DEBUG_puts("8cups_do_local_auth: Returning false.");
488
489
  return (false);
490
0
}