Coverage Report

Created: 2026-06-15 07:03

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Utilities/cmcurl/lib/netrc.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#ifndef CURL_DISABLE_NETRC
27
28
#ifdef HAVE_PWD_H
29
#ifdef __AMIGA__
30
#undef __NO_NET_API /* required for AmigaOS to declare getpwuid() */
31
#endif
32
#include <pwd.h>
33
#ifdef __AMIGA__
34
#define __NO_NET_API
35
#endif
36
#endif
37
38
#include "netrc.h"
39
#include "strcase.h"
40
#include "curl_get_line.h"
41
#include "curlx/fopen.h"
42
#include "curlx/strparse.h"
43
44
/* Get user and password from .netrc when given a machine name */
45
46
enum host_lookup_state {
47
  NOTHING,
48
  HOSTFOUND,    /* the 'machine' keyword was found */
49
  HOSTVALID,    /* this is "our" machine! */
50
  MACDEF
51
};
52
53
enum found_state {
54
  NONE,
55
  LOGIN,
56
  PASSWORD
57
};
58
59
0
#define FOUND_LOGIN    1
60
0
#define FOUND_PASSWORD 2
61
62
0
#define MAX_NETRC_LINE  16384
63
0
#define MAX_NETRC_FILE  (128 * 1024)
64
0
#define MAX_NETRC_TOKEN 4096
65
66
/* convert a dynbuf call CURLcode error to a NETRCcode error */
67
#define curl2netrc(result)                     \
68
0
  (((result) == CURLE_OUT_OF_MEMORY) ?         \
69
0
   NETRC_OUT_OF_MEMORY : NETRC_SYNTAX_ERROR)
70
71
static NETRCcode file2memory(const char *filename, struct dynbuf *filebuf)
72
0
{
73
0
  NETRCcode ret = NETRC_FILE_MISSING; /* if it cannot open the file */
74
0
  FILE *file = curlx_fopen(filename, FOPEN_READTEXT);
75
76
0
  if(file) {
77
0
    curlx_struct_stat stat;
78
0
    if((curlx_fstat(fileno(file), &stat) == -1) || !S_ISDIR(stat.st_mode)) {
79
0
      CURLcode result = CURLE_OK;
80
0
      bool eof;
81
0
      struct dynbuf linebuf;
82
0
      curlx_dyn_init(&linebuf, MAX_NETRC_LINE);
83
0
      ret = NETRC_OK;
84
0
      do {
85
0
        const char *line;
86
        /* Curl_get_line always returns lines ending with a newline */
87
0
        result = Curl_get_line(&linebuf, file, &eof);
88
0
        if(!result) {
89
0
          line = curlx_dyn_ptr(&linebuf);
90
          /* skip comments on load */
91
0
          curlx_str_passblanks(&line);
92
0
          if(*line == '#')
93
0
            continue;
94
0
          result = curlx_dyn_add(filebuf, line);
95
0
        }
96
0
        if(result) {
97
0
          curlx_dyn_free(filebuf);
98
0
          ret = curl2netrc(result);
99
0
          break;
100
0
        }
101
0
      } while(!eof);
102
0
      curlx_dyn_free(&linebuf);
103
0
    }
104
0
    curlx_fclose(file);
105
0
  }
106
0
  return ret;
107
0
}
108
109
/* bundled parser state to keep function signatures compact */
110
struct netrc_state {
111
  char *login;
112
  char *password;
113
  enum host_lookup_state state;
114
  enum found_state keyword;
115
  NETRCcode retcode;
116
  unsigned char found; /* FOUND_LOGIN | FOUND_PASSWORD bits */
117
  bool our_login;
118
  bool done;
119
  bool specific_login;
120
};
121
122
/*
123
 * Parse a quoted token starting after the opening '"'. Handles \n, \r, \t
124
 * escape sequences. Advances *tok_endp past the closing '"'.
125
 *
126
 * Returns NETRC_OK or error.
127
 */
128
static NETRCcode netrc_quoted_token(const char **tok_endp,
129
                                    struct dynbuf *token)
130
0
{
131
0
  bool escape = FALSE;
132
0
  NETRCcode rc = NETRC_SYNTAX_ERROR;
133
0
  const char *tok_end = *tok_endp;
134
0
  tok_end++; /* pass the leading quote */
135
0
  while(*tok_end) {
136
0
    CURLcode result;
137
0
    char s = *tok_end;
138
0
    if(escape) {
139
0
      escape = FALSE;
140
0
      switch(s) {
141
0
      case 'n':
142
0
        s = '\n';
143
0
        break;
144
0
      case 'r':
145
0
        s = '\r';
146
0
        break;
147
0
      case 't':
148
0
        s = '\t';
149
0
        break;
150
0
      }
151
0
    }
152
0
    else if(s == '\\') {
153
0
      escape = TRUE;
154
0
      tok_end++;
155
0
      continue;
156
0
    }
157
0
    else if(s == '\"') {
158
0
      tok_end++; /* pass the ending quote */
159
0
      rc = NETRC_OK;
160
0
      break;
161
0
    }
162
0
    result = curlx_dyn_addn(token, &s, 1);
163
0
    if(result) {
164
0
      *tok_endp = tok_end;
165
0
      return curl2netrc(result);
166
0
    }
167
0
    tok_end++;
168
0
  }
169
0
  *tok_endp = tok_end;
170
0
  return rc;
171
0
}
172
173
/*
174
 * Gets the next token from the netrc buffer at *tokp. Writes the token into
175
 * the 'token' dynbuf. Advances *tok_endp past the consumed token in the input
176
 * buffer. Updates *statep for MACDEF newline handling. Sets *lineend = TRUE
177
 * when the line is exhausted.
178
 *
179
 * Returns NETRC_OK or an error code.
180
 */
181
static NETRCcode netrc_get_token(const char **tokp,
182
                                 const char **tok_endp,
183
                                 struct dynbuf *token,
184
                                 enum host_lookup_state *statep,
185
                                 bool *lineend)
186
0
{
187
0
  const char *tok = *tokp;
188
0
  const char *tok_end;
189
190
0
  *lineend = FALSE;
191
0
  curlx_dyn_reset(token);
192
0
  curlx_str_passblanks(&tok);
193
194
  /* tok is first non-space letter */
195
0
  if(*statep == MACDEF) {
196
0
    if((*tok == '\n') || (*tok == '\r'))
197
0
      *statep = NOTHING; /* end of macro definition */
198
0
    *lineend = TRUE;
199
0
    *tokp = tok;
200
0
    return NETRC_OK;
201
0
  }
202
203
0
  if(!*tok || (*tok == '\n')) {
204
    /* end of line */
205
0
    *lineend = TRUE;
206
0
    *tokp = tok;
207
0
    return NETRC_OK;
208
0
  }
209
210
0
  tok_end = tok;
211
0
  if(*tok == '\"') {
212
    /* quoted string */
213
0
    NETRCcode ret = netrc_quoted_token(&tok_end, token);
214
0
    if(ret)
215
0
      return ret;
216
0
  }
217
0
  else {
218
    /* unquoted token */
219
0
    size_t len = 0;
220
0
    CURLcode result;
221
0
    while(*tok_end > ' ') {
222
0
      tok_end++;
223
0
      len++;
224
0
    }
225
0
    if(!len)
226
0
      return NETRC_SYNTAX_ERROR;
227
0
    result = curlx_dyn_addn(token, tok, len);
228
0
    if(result)
229
0
      return curl2netrc(result);
230
0
  }
231
232
0
  *tok_endp = tok_end;
233
234
0
  if(curlx_dyn_len(token))
235
0
    *tokp = curlx_dyn_ptr(token);
236
0
  else
237
    /* set it to blank to avoid NULL */
238
0
    *tokp = "";
239
240
0
  return NETRC_OK;
241
0
}
242
243
/*
244
 * Reset parser for a new machine entry. Frees password and optionally login
245
 * if it was not user-specified.
246
 */
247
static void netrc_new_machine(struct netrc_state *ns)
248
0
{
249
0
  ns->keyword = NONE;
250
0
  ns->found = 0;
251
0
  ns->our_login = FALSE;
252
0
  curlx_safefree(ns->password);
253
0
  if(!ns->specific_login)
254
0
    curlx_safefree(ns->login);
255
0
}
256
257
/*
258
 * Process a parsed token through the HOSTVALID state machine branch. This
259
 * handles login/password values and keyword transitions for the matched host.
260
 *
261
 * Returns NETRC_OK or an error code.
262
 */
263
static NETRCcode netrc_hostvalid(struct netrc_state *ns, const char *tok)
264
0
{
265
0
  if(ns->keyword == LOGIN) {
266
0
    if(ns->specific_login)
267
0
      ns->our_login = !Curl_timestrcmp(ns->login, tok);
268
0
    else {
269
0
      ns->our_login = TRUE;
270
0
      curlx_free(ns->login);
271
0
      ns->login = curlx_strdup(tok);
272
0
      if(!ns->login)
273
0
        return NETRC_OUT_OF_MEMORY;
274
0
    }
275
0
    ns->found |= FOUND_LOGIN;
276
0
    ns->keyword = NONE;
277
0
  }
278
0
  else if(ns->keyword == PASSWORD) {
279
0
    curlx_free(ns->password);
280
0
    ns->password = curlx_strdup(tok);
281
0
    if(!ns->password)
282
0
      return NETRC_OUT_OF_MEMORY;
283
0
    ns->found |= FOUND_PASSWORD;
284
0
    ns->keyword = NONE;
285
0
  }
286
0
  else if(curl_strequal("login", tok))
287
0
    ns->keyword = LOGIN;
288
0
  else if(curl_strequal("password", tok))
289
0
    ns->keyword = PASSWORD;
290
0
  else if(curl_strequal("machine", tok)) {
291
    /* a new machine here */
292
293
0
    if(ns->found & FOUND_PASSWORD &&
294
      /* a password was provided for this host */
295
296
0
       ((!ns->specific_login || ns->our_login) ||
297
        /* either there was no specific login to search for, or this
298
           is the specific one we wanted */
299
0
        (ns->specific_login && !(ns->found & FOUND_LOGIN)))) {
300
      /* or we look for a specific login, but that was not specified */
301
302
0
      ns->done = TRUE;
303
0
      return NETRC_OK;
304
0
    }
305
306
0
    ns->state = HOSTFOUND;
307
0
    netrc_new_machine(ns);
308
0
  }
309
0
  else if(curl_strequal("default", tok)) {
310
0
    ns->state = HOSTVALID;
311
0
    ns->retcode = NETRC_OK;
312
0
    netrc_new_machine(ns);
313
0
  }
314
0
  if((ns->found == (FOUND_PASSWORD | FOUND_LOGIN)) && ns->our_login)
315
0
    ns->done = TRUE;
316
0
  return NETRC_OK;
317
0
}
318
319
/*
320
 * Process one parsed token through the netrc state
321
 * machine. Updates the parser state in *ns.
322
 * Returns NETRC_OK or an error code.
323
 */
324
static NETRCcode netrc_handle_token(struct netrc_state *ns,
325
                                    const char *tok,
326
                                    const char *host)
327
0
{
328
0
  switch(ns->state) {
329
0
  case NOTHING:
330
0
    if(curl_strequal("macdef", tok))
331
0
      ns->state = MACDEF;
332
0
    else if(curl_strequal("machine", tok)) {
333
0
      ns->state = HOSTFOUND;
334
0
      netrc_new_machine(ns);
335
0
    }
336
0
    else if(curl_strequal("default", tok)) {
337
0
      ns->state = HOSTVALID;
338
0
      ns->retcode = NETRC_OK;
339
0
    }
340
0
    break;
341
0
  case MACDEF:
342
0
    if(!*tok)
343
0
      ns->state = NOTHING;
344
0
    break;
345
0
  case HOSTFOUND:
346
0
    if(curl_strequal(host, tok)) {
347
0
      ns->state = HOSTVALID;
348
0
      ns->retcode = NETRC_OK;
349
0
    }
350
0
    else
351
0
      ns->state = NOTHING;
352
0
    break;
353
0
  case HOSTVALID:
354
0
    return netrc_hostvalid(ns, tok);
355
0
  }
356
0
  return NETRC_OK;
357
0
}
358
359
/*
360
 * Finalize the parse result: fill in defaults and free
361
 * resources on error.
362
 */
363
static NETRCcode netrc_finalize(struct netrc_state *ns,
364
                                char **loginp,
365
                                char **passwordp,
366
                                struct store_netrc *store)
367
0
{
368
0
  NETRCcode retcode = ns->retcode;
369
0
  if(!retcode) {
370
0
    if(!ns->password && ns->our_login) {
371
      /* success without a password, set a blank one */
372
0
      ns->password = curlx_strdup("");
373
0
      if(!ns->password)
374
0
        retcode = NETRC_OUT_OF_MEMORY;
375
0
    }
376
0
    else if(!ns->login && !ns->password)
377
      /* a default with no credentials */
378
0
      retcode = NETRC_NO_MATCH;
379
0
  }
380
0
  if(!retcode) {
381
    /* success */
382
0
    if(!ns->specific_login)
383
0
      *loginp = ns->login;
384
385
    /* netrc_finalize() can return a password even when specific_login is set
386
       but our_login is false (e.g., host matched but the requested login
387
       never matched). See test 685. */
388
0
    *passwordp = ns->password;
389
0
  }
390
0
  else {
391
0
    curlx_dyn_free(&store->filebuf);
392
0
    store->loaded = FALSE;
393
0
    if(!ns->specific_login)
394
0
      curlx_free(ns->login);
395
0
    curlx_free(ns->password);
396
0
  }
397
0
  return retcode;
398
0
}
399
400
/*
401
 * Returns zero on success.
402
 */
403
static NETRCcode parsenetrc(struct store_netrc *store,
404
                            const char *host,
405
                            char **loginp,
406
                            char **passwordp,
407
                            const char *netrcfile)
408
0
{
409
0
  const char *netrcbuffer;
410
0
  struct dynbuf token;
411
0
  struct dynbuf *filebuf = &store->filebuf;
412
0
  struct netrc_state ns;
413
414
0
  memset(&ns, 0, sizeof(ns));
415
0
  ns.retcode = NETRC_NO_MATCH;
416
0
  ns.login = *loginp;
417
0
  ns.specific_login = !!ns.login;
418
419
0
  DEBUGASSERT(!*passwordp);
420
0
  curlx_dyn_init(&token, MAX_NETRC_TOKEN);
421
422
0
  if(!store->loaded) {
423
0
    NETRCcode ret = file2memory(netrcfile, filebuf);
424
0
    if(ret)
425
0
      return ret;
426
0
    store->loaded = TRUE;
427
0
  }
428
429
0
  netrcbuffer = curlx_dyn_ptr(filebuf);
430
431
0
  while(!ns.done) {
432
0
    const char *tok = netrcbuffer;
433
0
    while(tok && !ns.done) {
434
0
      const char *tok_end;
435
0
      bool lineend;
436
0
      NETRCcode ret;
437
438
0
      ret = netrc_get_token(&tok, &tok_end, &token, &ns.state, &lineend);
439
0
      if(ret) {
440
0
        ns.retcode = ret;
441
0
        goto out;
442
0
      }
443
0
      if(lineend)
444
0
        break;
445
446
0
      ret = netrc_handle_token(&ns, tok, host);
447
0
      if(ret) {
448
0
        ns.retcode = ret;
449
0
        goto out;
450
0
      }
451
      /* tok_end cannot point to a null byte here since lines are always
452
         newline terminated */
453
0
      DEBUGASSERT(*tok_end);
454
0
      tok = ++tok_end;
455
0
    }
456
0
    if(!ns.done) {
457
0
      const char *nl = NULL;
458
0
      if(tok)
459
0
        nl = strchr(tok, '\n');
460
0
      if(!nl)
461
0
        break;
462
      /* point to next line */
463
0
      netrcbuffer = &nl[1];
464
0
    }
465
0
  } /* while !done */
466
467
0
out:
468
0
  curlx_dyn_free(&token);
469
0
  return netrc_finalize(&ns, loginp, passwordp, store);
470
0
}
471
472
const char *Curl_netrc_strerror(NETRCcode ret)
473
0
{
474
0
  switch(ret) {
475
0
  default:
476
0
    return ""; /* not a legit error */
477
0
  case NETRC_FILE_MISSING:
478
0
    return "no such file";
479
0
  case NETRC_NO_MATCH:
480
0
    return "no matching entry";
481
0
  case NETRC_OUT_OF_MEMORY:
482
0
    return "out of memory";
483
0
  case NETRC_SYNTAX_ERROR:
484
0
    return "syntax error";
485
0
  }
486
  /* never reached */
487
0
}
488
489
/*
490
 * @unittest: 1304
491
 *
492
 * *loginp and *passwordp MUST be allocated if they are not NULL when passed
493
 * in.
494
 */
495
NETRCcode Curl_parsenetrc(struct store_netrc *store, const char *host,
496
                          char **loginp, char **passwordp,
497
                          const char *netrcfile)
498
0
{
499
0
  NETRCcode retcode = NETRC_OK;
500
0
  char *filealloc = NULL;
501
502
0
  if(!netrcfile) {
503
0
    char *home = NULL;
504
0
    char *homea = NULL;
505
0
#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
506
0
    char pwbuf[1024];
507
0
#endif
508
0
    filealloc = curl_getenv("NETRC");
509
0
    if(!filealloc) {
510
0
      homea = curl_getenv("HOME"); /* portable environment reader */
511
0
      if(homea) {
512
0
        home = homea;
513
0
#if defined(HAVE_GETPWUID_R) && defined(HAVE_GETEUID)
514
0
      }
515
0
      else {
516
0
        struct passwd pw, *pw_res;
517
0
        if(!getpwuid_r(geteuid(), &pw, pwbuf, sizeof(pwbuf), &pw_res) &&
518
0
           pw_res) {
519
0
          home = pw.pw_dir;
520
0
        }
521
#elif defined(HAVE_GETPWUID) && defined(HAVE_GETEUID)
522
      }
523
      else {
524
        struct passwd *pw;
525
        pw = getpwuid(geteuid());
526
        if(pw) {
527
          home = pw->pw_dir;
528
        }
529
#elif defined(_WIN32)
530
      }
531
      else {
532
        homea = curl_getenv("USERPROFILE");
533
        if(homea) {
534
          home = homea;
535
        }
536
#endif
537
0
      }
538
539
0
      if(!home)
540
0
        return NETRC_FILE_MISSING; /* no home directory found (or possibly out
541
                                      of memory) */
542
543
0
      filealloc = curl_maprintf("%s%s.netrc", home, DIR_CHAR);
544
0
      if(!filealloc) {
545
0
        curlx_free(homea);
546
0
        return NETRC_OUT_OF_MEMORY;
547
0
      }
548
0
    }
549
0
    retcode = parsenetrc(store, host, loginp, passwordp, filealloc);
550
0
    curlx_free(filealloc);
551
#ifdef _WIN32
552
    if(retcode == NETRC_FILE_MISSING) {
553
      /* fallback to the old-style "_netrc" file */
554
      filealloc = curl_maprintf("%s%s_netrc", home, DIR_CHAR);
555
      if(!filealloc) {
556
        curlx_free(homea);
557
        return NETRC_OUT_OF_MEMORY;
558
      }
559
      retcode = parsenetrc(store, host, loginp, passwordp, filealloc);
560
      curlx_free(filealloc);
561
    }
562
#endif
563
0
    curlx_free(homea);
564
0
  }
565
0
  else
566
0
    retcode = parsenetrc(store, host, loginp, passwordp, netrcfile);
567
0
  return retcode;
568
0
}
569
570
void Curl_netrc_init(struct store_netrc *store)
571
0
{
572
0
  curlx_dyn_init(&store->filebuf, MAX_NETRC_FILE);
573
0
  store->loaded = FALSE;
574
0
}
575
void Curl_netrc_cleanup(struct store_netrc *store)
576
0
{
577
0
  curlx_dyn_free(&store->filebuf);
578
  store->loaded = FALSE;
579
0
}
580
#endif