Coverage Report

Created: 2025-11-24 06:32

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-2023 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
      www_authenticate ++;
266
0
    }
267
268
0
    DEBUG_printf("9cups_auth_find: After skip: \"%s\"", www_authenticate);
269
0
  }
270
271
0
  DEBUG_puts("9cups_auth_find: Returning NULL.");
272
273
0
  return (NULL);
274
0
}
275
276
277
//
278
// 'cups_auth_param()' - Copy the value for the named authentication parameter,
279
//                       if present.
280
//
281
282
static const char *       // O - Parameter value or `NULL` if not present
283
cups_auth_param(const char *scheme,   // I - Pointer to auth data
284
                const char *name,   // I - Name of parameter
285
                char       *value,    // I - Value buffer
286
                size_t     valsize)   // I - Size of value buffer
287
0
{
288
0
  char    *valptr = value,    // Pointer into value buffer
289
0
          *valend = value + valsize - 1;  // Pointer to end of buffer
290
0
  size_t  namelen = strlen(name);   // Name length
291
0
  bool    param;        // Is this a parameter?
292
293
294
0
  DEBUG_printf("8cups_auth_param(scheme=\"%s\", name=\"%s\", value=%p, valsize=%d)", scheme, name, (void *)value, (int)valsize);
295
296
0
  while (!isspace(*scheme & 255) && *scheme)
297
0
    scheme ++;
298
299
0
  while (*scheme)
300
0
  {
301
0
    while (isspace(*scheme & 255) || *scheme == ',')
302
0
      scheme ++;
303
304
0
    if (!strncmp(scheme, name, namelen) && scheme[namelen] == '=')
305
0
    {
306
      // Found the parameter, copy the value...
307
0
      scheme += namelen + 1;
308
0
      if (*scheme == '\"')
309
0
      {
310
0
        scheme ++;
311
312
0
  while (*scheme && *scheme != '\"')
313
0
  {
314
0
    if (valptr < valend)
315
0
      *valptr++ = *scheme;
316
317
0
    scheme ++;
318
0
  }
319
0
      }
320
0
      else
321
0
      {
322
0
  while (*scheme && strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~+/=", *scheme))
323
0
  {
324
0
    if (valptr < valend)
325
0
      *valptr++ = *scheme;
326
327
0
    scheme ++;
328
0
  }
329
0
      }
330
331
0
      *valptr = '\0';
332
333
0
      DEBUG_printf("9cups_auth_param: Returning \"%s\".", value);
334
335
0
      return (value);
336
0
    }
337
338
    // Skip the param=value string...
339
0
    param = false;
340
341
0
    while (!isspace(*scheme & 255) && *scheme)
342
0
    {
343
0
      if (*scheme == '=')
344
0
      {
345
0
        param = true;
346
0
      }
347
0
      else if (*scheme == '\"')
348
0
      {
349
        // Skip quoted value...
350
0
        scheme ++;
351
0
        while (*scheme && *scheme != '\"')
352
0
          scheme ++;
353
0
      }
354
355
0
      scheme ++;
356
0
    }
357
358
    // If this wasn't a parameter, we are at the end of this scheme's
359
    // parameters...
360
0
    if (!param)
361
0
      break;
362
0
  }
363
364
0
  *value = '\0';
365
366
0
  DEBUG_puts("9cups_auth_param: Returning NULL.");
367
368
0
  return (NULL);
369
0
}
370
371
372
//
373
// 'cups_auth_scheme()' - Get the (next) WWW-Authenticate scheme.
374
//
375
// The "www_authenticate" argument points to the current position in the header.
376
//
377
// Returns `NULL` if there are no (more) auth schemes present.
378
//
379
380
static const char *       // O - Start of scheme or `NULL` if not found
381
cups_auth_scheme(const char *www_authenticate,  // I - Pointer into WWW-Authenticate header
382
     char       *scheme,    // I - Scheme name buffer
383
     size_t     schemesize)   // I - Size of buffer
384
0
{
385
0
  const char  *start;       // Start of scheme data
386
0
  char    *sptr = scheme,     // Pointer into scheme buffer
387
0
    *send = scheme + schemesize - 1;// End of scheme buffer
388
0
  bool    param;        // Is this a parameter?
389
390
391
0
  DEBUG_printf("8cups_auth_scheme(www_authenticate=\"%s\", scheme=%p, schemesize=%u)", www_authenticate, (void *)scheme, (unsigned)schemesize);
392
393
0
  while (*www_authenticate)
394
0
  {
395
    // Skip leading whitespace and commas...
396
0
    while (isspace(*www_authenticate & 255) || *www_authenticate == ',')
397
0
      www_authenticate ++;
398
399
    // Parse the scheme name or param="value" string...
400
0
    for (sptr = scheme, start = www_authenticate, param = false; *www_authenticate && *www_authenticate != ',' && !isspace(*www_authenticate & 255); www_authenticate ++)
401
0
    {
402
0
      if (*www_authenticate == '=')
403
0
      {
404
0
        param = true;
405
0
      }
406
0
      else if (!param && sptr < send)
407
0
      {
408
0
        *sptr++ = *www_authenticate;
409
0
      }
410
0
      else if (*www_authenticate == '\"')
411
0
      {
412
        // Skip quoted value...
413
0
        do
414
0
        {
415
0
          www_authenticate ++;
416
0
        }
417
0
        while (*www_authenticate && *www_authenticate != '\"');
418
0
      }
419
0
    }
420
421
0
    if (sptr > scheme && !param)
422
0
    {
423
0
      *sptr = '\0';
424
425
0
      DEBUG_printf("9cups_auth_scheme: Returning \"%s\".", start);
426
427
0
      return (start);
428
0
    }
429
0
  }
430
431
0
  *scheme = '\0';
432
433
0
  DEBUG_puts("9cups_auth_scheme: Returning NULL.");
434
435
0
  return (NULL);
436
0
}
437
438
439
//
440
// 'cups_do_local_auth()' - Use local credentials if available/applicable.
441
//
442
443
static bool       // O - `true` if authorized, `false` otherwise
444
cups_do_local_auth(http_t *http)  // I - HTTP connection to server
445
0
{
446
#ifdef DEBUG
447
  char      hostaddr[256];  // Host address string
448
449
450
  DEBUG_printf("7cups_do_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"", (void *)http, httpAddrGetString(http->hostaddr, hostaddr, sizeof(hostaddr)), http->hostname);
451
#endif // DEBUG
452
453
0
#if defined(SO_PEERCRED) && defined(AF_LOCAL)
454
0
  const char    *www_auth;  // WWW-Authenticate header
455
0
  struct passwd   pwd;    // Password information
456
0
  struct passwd   *result;  // Auxiliary pointer
457
0
  const char    *username;  // Current username
458
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
459
460
  // See if we can authenticate using the peer credentials provided over a
461
  // domain socket; if so, specify "PeerCred username" as the authentication
462
  // information...
463
0
  www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
464
465
0
  if (http->hostaddr->addr.sa_family == AF_LOCAL && cups_auth_find(www_auth, "PeerCred"))
466
0
  {
467
    // Verify that the current cupsGetUser() matches the current UID...
468
0
    username = cupsGetUser();
469
470
0
    getpwnam_r(username, &pwd, cg->pw_buf, PW_BUF_SIZE, &result);
471
0
    if (result && pwd.pw_uid == getuid())
472
0
    {
473
0
      httpSetAuthString(http, "PeerCred", username);
474
475
0
      DEBUG_printf("8cups_do_local_auth: Returning true, authstring=\"%s\".", http->authstring);
476
477
0
      return (true);
478
0
    }
479
0
  }
480
0
#endif // SO_PEERCRED && AF_LOCAL
481
482
0
  DEBUG_puts("8cups_do_local_auth: Returning false.");
483
484
  return (false);
485
0
}