Coverage Report

Created: 2025-07-12 06:21

/src/libcups/cups/auth.c
Line
Count
Source (jump to first uncovered line)
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
  //
87
  // Nope, loop through the authentication schemes to find the first we support.
88
  //
89
90
0
  www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
91
92
0
  for (schemedata = cups_auth_scheme(www_auth, scheme, sizeof(scheme)); schemedata; schemedata = cups_auth_scheme(schemedata + strlen(scheme), scheme, sizeof(scheme)))
93
0
  {
94
    // Check the scheme name...
95
0
    DEBUG_printf("2cupsDoAuthentication: Trying scheme \"%s\"...", scheme);
96
97
0
    if (!_cups_strcasecmp(scheme, "Bearer"))
98
0
    {
99
      // OAuth 2.0 (Bearer) authentication...
100
0
      const char  *bearer = NULL; // Bearer token string, if any
101
102
0
      if (cg->oauth_cb)
103
0
      {
104
        // Try callback...
105
0
  char  scope[HTTP_MAX_VALUE];  // scope="xyz" string
106
107
0
  cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm));
108
109
0
  if (cups_auth_param(schemedata, "scope", scope, sizeof(scope)))
110
0
    bearer = (cg->oauth_cb)(http, http->realm, scope, resource, cg->oauth_data);
111
0
  else
112
0
    bearer = (cg->oauth_cb)(http, http->realm, NULL, resource, cg->oauth_data);
113
0
      }
114
115
0
      if (bearer)
116
0
      {
117
        // Use this access token...
118
0
        httpSetAuthString(http, "Bearer", bearer);
119
0
        break;
120
0
      }
121
0
      else
122
0
      {
123
        // No access token, try the next scheme...
124
0
        DEBUG_puts("2cupsDoAuthentication: No OAuth access token to provide.");
125
0
        continue;
126
0
      }
127
0
    }
128
0
    else if (_cups_strcasecmp(scheme, "Basic") && _cups_strcasecmp(scheme, "Digest") && _cups_strcasecmp(scheme, "Negotiate"))
129
0
    {
130
      // Other schemes not yet supported...
131
0
      DEBUG_printf("2cupsDoAuthentication: Scheme \"%s\" not yet supported.", scheme);
132
0
      continue;
133
0
    }
134
135
    // See if we should retry the current username:password...
136
0
    if (http->digest_tries > 1 || !http->userpass[0])
137
0
    {
138
      // Nope - get a new password from the user...
139
0
      char default_username[HTTP_MAX_VALUE];
140
          // Default username
141
142
0
      if (!cg->lang_default)
143
0
  cg->lang_default = cupsLangDefault();
144
145
0
      if (cups_auth_param(schemedata, "username", default_username, sizeof(default_username)))
146
0
  cupsSetUser(default_username);
147
148
0
      cupsLangFormatString(cg->lang_default, prompt, sizeof(prompt), _("Password for %s on %s? "), cupsGetUser(), http->hostname[0] == '/' ? "localhost" : http->hostname);
149
150
0
      http->digest_tries  = _cups_strncasecmp(scheme, "Digest", 6) != 0;
151
0
      http->userpass[0]   = '\0';
152
153
0
      if ((password = cupsGetPassword(prompt, http, method, resource)) == NULL)
154
0
      {
155
0
        DEBUG_puts("1cupsDoAuthentication: User canceled password request.");
156
0
  http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
157
0
  return (false);
158
0
      }
159
160
0
      snprintf(http->userpass, sizeof(http->userpass), "%s:%s", cupsGetUser(), password);
161
0
    }
162
0
    else if (http->status == HTTP_STATUS_UNAUTHORIZED)
163
0
    {
164
0
      http->digest_tries ++;
165
0
    }
166
167
0
    if (http->status == HTTP_STATUS_UNAUTHORIZED && http->digest_tries >= 3)
168
0
    {
169
0
      DEBUG_printf("1cupsDoAuthentication: Too many authentication tries (%d)", http->digest_tries);
170
171
0
      http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
172
0
      return (false);
173
0
    }
174
175
    // Got a password; encode it for the server...
176
0
    if (!_cups_strcasecmp(scheme, "Basic"))
177
0
    {
178
      // Basic authentication...
179
0
      char  encode[256];    // Base64 buffer
180
181
0
      DEBUG_puts("2cupsDoAuthentication: Using Basic.");
182
0
      httpEncode64(encode, sizeof(encode), http->userpass, strlen(http->userpass), false);
183
0
      httpSetAuthString(http, "Basic", encode);
184
0
      break;
185
0
    }
186
0
    else if (!_cups_strcasecmp(scheme, "Digest"))
187
0
    {
188
      // Digest authentication...
189
0
      char nonce[HTTP_MAX_VALUE]; // nonce="xyz" string
190
191
0
      cups_auth_param(schemedata, "algorithm", http->algorithm, sizeof(http->algorithm));
192
0
      cups_auth_param(schemedata, "nonce", nonce, sizeof(nonce));
193
0
      cups_auth_param(schemedata, "opaque", http->opaque, sizeof(http->opaque));
194
0
      cups_auth_param(schemedata, "qop", http->qop, sizeof(http->qop));
195
0
      cups_auth_param(schemedata, "realm", http->realm, sizeof(http->realm));
196
197
0
      if (_httpSetDigestAuthString(http, nonce, method, resource))
198
0
      {
199
0
  DEBUG_puts("2cupsDoAuthentication: Using Digest.");
200
0
        break;
201
0
      }
202
0
    }
203
0
  }
204
205
0
  if (http->authstring && http->authstring[0])
206
0
  {
207
0
    DEBUG_printf("1cupsDoAuthentication: authstring=\"%s\".", http->authstring);
208
209
0
    return (true);
210
0
  }
211
0
  else
212
0
  {
213
0
    DEBUG_puts("1cupsDoAuthentication: No supported schemes.");
214
0
    http->status = HTTP_STATUS_CUPS_AUTHORIZATION_CANCELED;
215
216
0
    return (false);
217
0
  }
218
0
}
219
220
221
//
222
// 'cups_auth_find()' - Find the named WWW-Authenticate scheme.
223
//
224
// The "www_authenticate" parameter points to the current position in the header.
225
//
226
// Returns `NULL` if the auth scheme is not present.
227
//
228
229
static const char *       // O - Start of matching scheme or `NULL` if not found
230
cups_auth_find(const char *www_authenticate,  // I - Pointer into WWW-Authenticate header
231
               const char *scheme)    // I - Authentication scheme
232
0
{
233
0
  size_t  schemelen = strlen(scheme); // Length of scheme
234
235
236
0
  DEBUG_printf("8cups_auth_find(www_authenticate=\"%s\", scheme=\"%s\"(%d))", www_authenticate, scheme, (int)schemelen);
237
238
0
  while (*www_authenticate)
239
0
  {
240
    // Skip leading whitespace and commas...
241
0
    DEBUG_printf("9cups_auth_find: Before whitespace: \"%s\"", www_authenticate);
242
0
    while (isspace(*www_authenticate & 255) || *www_authenticate == ',')
243
0
      www_authenticate ++;
244
0
    DEBUG_printf("9cups_auth_find: After whitespace: \"%s\"", www_authenticate);
245
246
    // See if this is "Scheme" followed by whitespace or the end of the string.
247
0
    if (!strncmp(www_authenticate, scheme, schemelen) && (isspace(www_authenticate[schemelen] & 255) || www_authenticate[schemelen] == ',' || !www_authenticate[schemelen]))
248
0
    {
249
      // Yes, this is the start of the scheme-specific information...
250
0
      DEBUG_printf("9cups_auth_find: Returning \"%s\".", www_authenticate);
251
252
0
      return (www_authenticate);
253
0
    }
254
255
    // Skip the scheme name or param="value" string...
256
0
    while (!isspace(*www_authenticate & 255) && *www_authenticate)
257
0
    {
258
0
      if (*www_authenticate == '\"')
259
0
      {
260
        // Skip quoted value...
261
0
        www_authenticate ++;
262
0
        while (*www_authenticate && *www_authenticate != '\"')
263
0
          www_authenticate ++;
264
265
0
        DEBUG_printf("9cups_auth_find: After quoted: \"%s\"", www_authenticate);
266
0
      }
267
268
0
      www_authenticate ++;
269
0
    }
270
271
0
    DEBUG_printf("9cups_auth_find: After skip: \"%s\"", www_authenticate);
272
0
  }
273
274
0
  DEBUG_puts("9cups_auth_find: Returning NULL.");
275
276
0
  return (NULL);
277
0
}
278
279
280
//
281
// 'cups_auth_param()' - Copy the value for the named authentication parameter,
282
//                       if present.
283
//
284
285
static const char *       // O - Parameter value or `NULL` if not present
286
cups_auth_param(const char *scheme,   // I - Pointer to auth data
287
                const char *name,   // I - Name of parameter
288
                char       *value,    // I - Value buffer
289
                size_t     valsize)   // I - Size of value buffer
290
0
{
291
0
  char    *valptr = value,    // Pointer into value buffer
292
0
          *valend = value + valsize - 1;  // Pointer to end of buffer
293
0
  size_t  namelen = strlen(name);   // Name length
294
0
  bool    param;        // Is this a parameter?
295
296
297
0
  DEBUG_printf("8cups_auth_param(scheme=\"%s\", name=\"%s\", value=%p, valsize=%d)", scheme, name, (void *)value, (int)valsize);
298
299
0
  while (!isspace(*scheme & 255) && *scheme)
300
0
    scheme ++;
301
302
0
  while (*scheme)
303
0
  {
304
0
    while (isspace(*scheme & 255) || *scheme == ',')
305
0
      scheme ++;
306
307
0
    if (!strncmp(scheme, name, namelen) && scheme[namelen] == '=')
308
0
    {
309
      // Found the parameter, copy the value...
310
0
      scheme += namelen + 1;
311
0
      if (*scheme == '\"')
312
0
      {
313
0
        scheme ++;
314
315
0
  while (*scheme && *scheme != '\"')
316
0
  {
317
0
    if (valptr < valend)
318
0
      *valptr++ = *scheme;
319
320
0
    scheme ++;
321
0
  }
322
0
      }
323
0
      else
324
0
      {
325
0
  while (*scheme && strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~+/=", *scheme))
326
0
  {
327
0
    if (valptr < valend)
328
0
      *valptr++ = *scheme;
329
330
0
    scheme ++;
331
0
  }
332
0
      }
333
334
0
      *valptr = '\0';
335
336
0
      DEBUG_printf("9cups_auth_param: Returning \"%s\".", value);
337
338
0
      return (value);
339
0
    }
340
341
    // Skip the param=value string...
342
0
    param = false;
343
344
0
    while (!isspace(*scheme & 255) && *scheme)
345
0
    {
346
0
      if (*scheme == '=')
347
0
      {
348
0
        param = true;
349
0
      }
350
0
      else if (*scheme == '\"')
351
0
      {
352
        // Skip quoted value...
353
0
        scheme ++;
354
0
        while (*scheme && *scheme != '\"')
355
0
          scheme ++;
356
0
      }
357
358
0
      scheme ++;
359
0
    }
360
361
    // If this wasn't a parameter, we are at the end of this scheme's 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" parameter 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=%d)", www_authenticate, (void *)scheme, (int)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
0
      }
421
0
    }
422
423
0
    if (sptr > scheme && !param)
424
0
    {
425
0
      *sptr = '\0';
426
427
0
      DEBUG_printf("9cups_auth_scheme: Returning \"%s\".", start);
428
429
0
      return (start);
430
0
    }
431
0
  }
432
433
0
  *scheme = '\0';
434
435
0
  DEBUG_puts("9cups_auth_scheme: Returning NULL.");
436
437
0
  return (NULL);
438
0
}
439
440
441
//
442
// 'cups_do_local_auth()' - Use local peer credentials if available/applicable.
443
//
444
445
static bool       // O - `true` if authorized, `false` otherwise
446
cups_do_local_auth(http_t *http)  // I - HTTP connection to server
447
0
{
448
#ifdef DEBUG
449
  char      hostaddr[256];  // Host address string
450
451
452
  DEBUG_printf("7cups_do_local_auth(http=%p) hostaddr=%s, hostname=\"%s\"", (void *)http, httpAddrGetString(http->hostaddr, hostaddr, sizeof(hostaddr)), http->hostname);
453
#endif // DEBUG
454
455
0
#if defined(SO_PEERCRED) && defined(AF_LOCAL)
456
0
  const char    *www_auth;  // WWW-Authenticate header
457
0
  struct passwd   pwd;    // Password information
458
0
  struct passwd   *result;  // Auxiliary pointer
459
0
  const char    *username;  // Current username
460
0
  _cups_globals_t *cg = _cupsGlobals(); // Global data
461
462
  // See if we can authenticate using the peer credentials provided over a
463
  // domain socket; if so, specify "PeerCred username" as the authentication
464
  // information...
465
0
  www_auth = httpGetField(http, HTTP_FIELD_WWW_AUTHENTICATE);
466
467
0
  if (http->hostaddr->addr.sa_family == AF_LOCAL && cups_auth_find(www_auth, "PeerCred"))
468
0
  {
469
    // Verify that the current cupsGetUser() matches the current UID...
470
0
    username = cupsGetUser();
471
472
0
    getpwnam_r(username, &pwd, cg->pw_buf, PW_BUF_SIZE, &result);
473
0
    if (result && pwd.pw_uid == getuid())
474
0
    {
475
0
      httpSetAuthString(http, "PeerCred", username);
476
477
0
      DEBUG_printf("8cups_do_local_auth: Returning true, authstring=\"%s\".", http->authstring);
478
479
0
      return (true);
480
0
    }
481
0
  }
482
0
#endif // SO_PEERCRED && AF_LOCAL
483
484
0
  DEBUG_puts("8cups_do_local_auth: Returning false.");
485
486
0
  return (false);
487
0
}