• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

proftpd / proftpd / 26127302613

19 May 2026 09:51PM UTC coverage: 93.024% (+0.4%) from 92.635%
26127302613

push

github

51329 of 55178 relevant lines covered (93.02%)

215.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.84
/src/auth.c
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 1997, 1998 Public Flood Software
4
 * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net>
5
 * Copyright (c) 2001-2026 The ProFTPD Project team
6
 *
7
 * This program is free software; you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 2 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
19
 *
20
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
21
 * and other respective copyright holders give permission to link this program
22
 * with OpenSSL, and distribute the resulting executable, without including
23
 * the source code for OpenSSL in the source distribution.
24
 */
25

26
/* Authentication front-end for ProFTPD. */
27

28
#include "conf.h"
29
#include "privs.h"
30
#include "error.h"
31
#include "openbsd-blowfish.h"
32

33
static pool *auth_pool = NULL;
34
static size_t auth_max_passwd_len = PR_TUNABLE_PASSWORD_MAX;
35
static pr_table_t *auth_tab = NULL, *uid_tab = NULL, *user_tab = NULL,
36
  *gid_tab = NULL, *group_tab = NULL;
37
static xaset_t *auth_module_list = NULL;
38

39
struct auth_module_elt {
40
  struct auth_module_elt *next, *prev;
41
  const char *name;
42
};
43

44
static const char *trace_channel = "auth";
45

46
/* Caching of ID-to-name lookups, for both UIDs and GIDs, is enabled by
47
 * default.
48
 */
49
static unsigned int auth_caching = PR_AUTH_CACHE_FL_DEFAULT;
50

51
/* Key comparison callback for the uidcache and gidcache. */
52
static int uid_keycmp_cb(const void *key1, size_t keysz1,
53
    const void *key2, size_t keysz2) {
3✔
54

55
  /* Return zero to indicate a match, non-zero otherwise. */
56
  return (*((uid_t *) key1) == *((uid_t *) key2) ? 0 : 1);
57
}
3✔
58

59
static int gid_keycmp_cb(const void *key1, size_t keysz1,
60
    const void *key2, size_t keysz2) {
2✔
61

62
  /* Return zero to indicate a match, non-zero otherwise. */
63
  return (*((gid_t *) key1) == *((gid_t *) key2) ? 0 : 1);
64
}
2✔
65

66
/* Key "hash" callback for the uidcache and gidcache. */
67
static unsigned int uid_hash_cb(const void *key, size_t keysz) {
68
  uid_t u;
7✔
69
  unsigned int res;
7✔
70

7✔
71
  memcpy(&u, key, keysz);
72
  res = (unsigned int) (u << 8);
7✔
73

7✔
74
  return res;
75
}
7✔
76

77
static unsigned int gid_hash_cb(const void *key, size_t keysz) {
78
  gid_t g;
6✔
79
  unsigned int res;
6✔
80

6✔
81
  memcpy(&g, key, keysz);
82
  res = (unsigned int) (g << 8);
6✔
83

6✔
84
  return res;
85
}
6✔
86

87
static void uidcache_create(void) {
88
  if (uid_tab == NULL &&
11✔
89
      auth_pool != NULL) {
11✔
90
    uid_tab = pr_table_alloc(auth_pool, 0);
4✔
91

4✔
92
    (void) pr_table_ctl(uid_tab, PR_TABLE_CTL_SET_KEY_CMP,
93
      (void *) uid_keycmp_cb);
4✔
94
    (void) pr_table_ctl(uid_tab, PR_TABLE_CTL_SET_KEY_HASH,
95
      (void *) uid_hash_cb);
4✔
96
  }
97
}
98

11✔
99
static void uidcache_add(uid_t uid, const char *name) {
100
  uidcache_create();
5✔
101

5✔
102
  if (uid_tab != NULL) {
103
    int count;
5✔
104

5✔
105
    (void) pr_table_rewind(uid_tab);
106
    count = pr_table_kexists(uid_tab, (const void *) &uid, sizeof(uid_t));
5✔
107
    if (count <= 0) {
5✔
108
      uid_t *cache_uid;
5✔
109
      size_t namelen;
4✔
110

4✔
111
      /* Allocate memory for a UID out of the ID cache pool, so that this
112
       * UID can be used as a key.
113
       */
114
      cache_uid = palloc(auth_pool, sizeof(uid_t));
115
      *cache_uid = uid;
4✔
116

4✔
117
      namelen = strlen(name);
118

4✔
119
      if (pr_table_kadd(uid_tab, (const void *) cache_uid, sizeof(uid_t),
120
          pstrndup(auth_pool, name, namelen), namelen + 1) < 0 &&
4✔
121
          errno != EEXIST) {
4✔
122
        pr_trace_msg(trace_channel, 3,
×
123
          "error adding name '%s' for UID %s to the uidcache: %s", name,
×
124
          pr_uid2str(NULL, uid), strerror(errno));
125

126
      } else {
127
        pr_trace_msg(trace_channel, 5,
128
          "stashed name '%s' for UID %s in the uidcache", name,
4✔
129
          pr_uid2str(NULL, uid));
130
      }
131
    }
132
  }
133
}
134

5✔
135
static int uidcache_get(uid_t uid, char *name, size_t namesz) {
136
  if (uid_tab != NULL) {
6✔
137
    const void *v = NULL;
6✔
138

6✔
139
    v = pr_table_kget(uid_tab, (const void *) &uid, sizeof(uid_t), NULL);
140
    if (v != NULL) {
6✔
141
      memset(name, '\0', namesz);
6✔
142
      sstrncpy(name, v, namesz);
2✔
143

2✔
144
      pr_trace_msg(trace_channel, 8,
145
        "using name '%s' from uidcache for UID %s", name,
2✔
146
        pr_uid2str(NULL, uid));
147
      return 0;
148
    }
2✔
149

150
   pr_trace_msg(trace_channel, 9,
151
      "no value found in uidcache for UID %s: %s", pr_uid2str(NULL, uid),
4✔
152
      strerror(errno));
153
  }
4✔
154

155
  errno = ENOENT;
156
  return -1;
4✔
157
}
4✔
158

159
static void gidcache_create(void) {
160
  if (gid_tab == NULL&&
10✔
161
      auth_pool != NULL) {
10✔
162
    gid_tab = pr_table_alloc(auth_pool, 0);
4✔
163

4✔
164
    (void) pr_table_ctl(gid_tab, PR_TABLE_CTL_SET_KEY_CMP,
165
      (void *) gid_keycmp_cb);
4✔
166
    (void) pr_table_ctl(gid_tab, PR_TABLE_CTL_SET_KEY_HASH,
167
      (void *) gid_hash_cb);
4✔
168
  }
169
}
170

10✔
171
static void gidcache_add(gid_t gid, const char *name) {
172
  gidcache_create();
4✔
173

4✔
174
  if (gid_tab != NULL) {
175
    int count;
4✔
176

4✔
177
    (void) pr_table_rewind(gid_tab);
178
    count = pr_table_kexists(gid_tab, (const void *) &gid, sizeof(gid_t));
4✔
179
    if (count <= 0) {
4✔
180
      gid_t *cache_gid;
4✔
181
      size_t namelen;
4✔
182

4✔
183
      /* Allocate memory for a GID out of the ID cache pool, so that this
184
       * GID can be used as a key.
185
       */
186
      cache_gid = palloc(auth_pool, sizeof(gid_t));
187
      *cache_gid = gid;
4✔
188

4✔
189
      namelen = strlen(name);
190

4✔
191
      if (pr_table_kadd(gid_tab, (const void *) cache_gid, sizeof(gid_t),
192
          pstrndup(auth_pool, name, namelen), namelen + 1) < 0 &&
4✔
193
          errno != EEXIST) {
4✔
194
        pr_trace_msg(trace_channel, 3,
×
195
          "error adding name '%s' for GID %s to the gidcache: %s", name,
×
196
          pr_gid2str(NULL, gid), strerror(errno));
197

198
      } else {
199
        pr_trace_msg(trace_channel, 5,
200
          "stashed name '%s' for GID %s in the gidcache", name,
4✔
201
          pr_gid2str(NULL, gid));
202
      }
203
    }
204
  }
205
}
206

4✔
207
static int gidcache_get(gid_t gid, char *name, size_t namesz) {
208
  if (gid_tab != NULL) {
6✔
209
    const void *v = NULL;
6✔
210

6✔
211
    v = pr_table_kget(gid_tab, (const void *) &gid, sizeof(gid_t), NULL);
212
    if (v != NULL) {
6✔
213
      memset(name, '\0', namesz);
6✔
214
      sstrncpy(name, v, namesz);
2✔
215

2✔
216
      pr_trace_msg(trace_channel, 8,
217
        "using name '%s' from gidcache for GID %s", name,
2✔
218
        pr_gid2str(NULL, gid));
219
      return 0;
220
    }
2✔
221

222
   pr_trace_msg(trace_channel, 9,
223
      "no value found in gidcache for GID %s: %s", pr_gid2str(NULL, gid),
4✔
224
      strerror(errno));
225
  }
4✔
226

227
  errno = ENOENT;
228
  return -1;
4✔
229
}
4✔
230

231
static void usercache_create(void) {
232
  if (user_tab == NULL &&
43✔
233
      auth_pool != NULL) {
43✔
234
    user_tab = pr_table_alloc(auth_pool, 0);
37✔
235
  }
3✔
236
}
237

43✔
238
static void usercache_add(const char *name, uid_t uid) {
239
  usercache_create();
21✔
240

21✔
241
  if (user_tab != NULL) {
242
    int count;
21✔
243

4✔
244
    (void) pr_table_rewind(user_tab);
245
    count = pr_table_exists(user_tab, name);
4✔
246
    if (count <= 0) {
4✔
247
      const char *cache_name;
4✔
248
      uid_t *cache_key;
3✔
249

3✔
250
      /* Allocate memory for a key out of the ID cache pool, so that this
251
       * name can be used as a key.
252
       */
253
      cache_name = pstrdup(auth_pool, name);
254
      cache_key = palloc(auth_pool, sizeof(uid_t));
3✔
255
      *cache_key = uid;
3✔
256

3✔
257
      if (pr_table_add(user_tab, cache_name, cache_key, sizeof(uid_t)) < 0 &&
258
          errno != EEXIST) {
3✔
259
        pr_trace_msg(trace_channel, 3,
×
260
          "error adding UID %s for user '%s' to the usercache: %s",
×
261
          pr_uid2str(NULL, uid), name, strerror(errno));
262

263
      } else {
264
        pr_trace_msg(trace_channel, 5,
265
          "stashed UID %s for user '%s' in the usercache",
3✔
266
          pr_uid2str(NULL, uid), name);
267
      }
268
    }
269
  }
270
}
271

21✔
272
static int usercache_get(const char *name, uid_t *uid) {
273
  if (user_tab != NULL) {
22✔
274
    const void *v = NULL;
22✔
275

5✔
276
    v = pr_table_get(user_tab, name, NULL);
277
    if (v != NULL) {
5✔
278
      *uid = *((uid_t *) v);
5✔
279

2✔
280
      pr_trace_msg(trace_channel, 8,
281
        "using UID %s for user '%s' from usercache", pr_uid2str(NULL, *uid),
2✔
282
        name);
283
      return 0;
284
    }
2✔
285

286
   pr_trace_msg(trace_channel, 9,
287
      "no value found in usercache for user '%s': %s", name, strerror(errno));
3✔
288
  }
3✔
289

290
  errno = ENOENT;
291
  return -1;
20✔
292
}
20✔
293

294
static void groupcache_create(void) {
295
  if (group_tab == NULL &&
32✔
296
      auth_pool != NULL) {
32✔
297
    group_tab = pr_table_alloc(auth_pool, 0);
26✔
298
  }
4✔
299
}
300

32✔
301
static void groupcache_add(const char *name, gid_t gid) {
302
  groupcache_create();
15✔
303

15✔
304
  if (group_tab != NULL) {
305
    int count;
15✔
306

4✔
307
    (void) pr_table_rewind(group_tab);
308
    count = pr_table_exists(group_tab, name);
4✔
309
    if (count <= 0) {
4✔
310
      const char *cache_name;
4✔
311
      gid_t *cache_key;
4✔
312

4✔
313
      /* Allocate memory for a key out of the ID cache pool, so that this
314
       * name can be used as a key.
315
       */
316
      cache_name = pstrdup(auth_pool, name);
317
      cache_key = palloc(auth_pool, sizeof(gid_t));
4✔
318
      *cache_key = gid;
4✔
319

4✔
320
      if (pr_table_add(group_tab, cache_name, cache_key, sizeof(gid_t)) < 0 &&
321
          errno != EEXIST) {
4✔
322
        pr_trace_msg(trace_channel, 3,
×
323
          "error adding GID %s for group '%s' to the groupcache: %s",
×
324
          pr_gid2str(NULL, gid), name, strerror(errno));
325

326
      } else {
327
        pr_trace_msg(trace_channel, 5,
328
          "stashed GID %s for group '%s' in the groupcache",
4✔
329
          pr_gid2str(NULL, gid), name);
330
      }
331
    }
332
  }
333
}
334

15✔
335
static int groupcache_get(const char *name, gid_t *gid) {
336
  if (group_tab != NULL) {
17✔
337
    const void *v = NULL;
17✔
338

6✔
339
    v = pr_table_get(group_tab, name, NULL);
340
    if (v != NULL) {
6✔
341
      *gid = *((gid_t *) v);
6✔
342

2✔
343
      pr_trace_msg(trace_channel, 8,
344
        "using GID %s for group '%s' from groupcache", pr_gid2str(NULL, *gid),
2✔
345
        name);
346
      return 0;
347
    }
2✔
348

349
   pr_trace_msg(trace_channel, 9,
350
      "no value found in groupcache for group '%s': %s", name, strerror(errno));
4✔
351
  }
4✔
352

353
  errno = ENOENT;
354
  return -1;
15✔
355
}
15✔
356

357
/* The difference between this function, and pr_cmd_alloc(), is that this
358
 * allocates the cmd_rec directly from the given pool, whereas pr_cmd_alloc()
359
 * will allocate a subpool from the given pool, and allocate its cmd_rec
360
 * from the subpool.  This means that pr_cmd_alloc()'s cmd_rec's can be
361
 * subsequently destroyed easily; this function's cmd_rec's will be destroyed
362
 * when the given pool is destroyed.
363
 */
364
static cmd_rec *make_cmd(pool *cp, unsigned int argc, ...) {
365
  va_list args;
122✔
366
  cmd_rec *c;
122✔
367
  pool *sub_pool, *tmp_pool;
122✔
368

122✔
369
  c = pcalloc(cp, sizeof(cmd_rec));
370
  c->argc = argc;
122✔
371
  c->stash_index = -1;
122✔
372
  c->stash_hash = 0;
122✔
373

122✔
374
  if (argc > 0) {
375
    register unsigned int i;
122✔
376

104✔
377
    c->argv = pcalloc(cp, sizeof(void *) * (argc + 1));
378

104✔
379
    va_start(args, argc);
380

104✔
381
    for (i = 0; i < argc; i++) {
382
      c->argv[i] = (void *) va_arg(args, char *);
261✔
383
    }
157✔
384

385
    va_end(args);
386

104✔
387
    c->argv[argc] = NULL;
388
  }
104✔
389

390
  /* Make sure we provide pool and tmp_pool for the consumers. */
391
  sub_pool = make_sub_pool(cp);
392
  pr_pool_tag(sub_pool, "auth cmd subpool");
122✔
393
  c->pool = sub_pool;
122✔
394

122✔
395
  tmp_pool = make_sub_pool(c->pool);
396
  pr_pool_tag(tmp_pool, "auth cmd tmp pool");
122✔
397
  c->tmp_pool = tmp_pool;
122✔
398

122✔
399
  return c;
400
}
122✔
401

402
static modret_t *dispatch_auth(cmd_rec *cmd, char *match, module **m) {
403
  authtable *start_tab = NULL, *iter_tab = NULL;
123✔
404
  modret_t *mr = NULL;
123✔
405

123✔
406
  start_tab = pr_stash_get_symbol2(PR_SYM_AUTH, match, NULL,
407
    &cmd->stash_index, &cmd->stash_hash);
123✔
408
  if (start_tab == NULL) {
409
    int xerrno = errno;
123✔
410

53✔
411
    pr_trace_msg(trace_channel, 1, "error finding start symbol for '%s': %s",
412
      match, strerror(xerrno));
53✔
413
    return PR_ERROR_MSG(cmd, NULL, strerror(xerrno));
414
  }
53✔
415

416
  iter_tab = start_tab;
417

418
  while (iter_tab) {
419
    pr_signals_handle();
85✔
420

70✔
421
    if (m && *m && *m != iter_tab->m) {
422
      goto next;
70✔
423
    }
×
424

425
    pr_trace_msg(trace_channel, 6,
426
      "dispatching auth request \"%s\" to module mod_%s",
70✔
427
      match, iter_tab->m->name);
428

70✔
429
    mr = pr_module_call(iter_tab->m, iter_tab->handler, cmd);
430

70✔
431
    /* Return a pointer, if requested, to the module which answered the
432
     * auth request.  This is used, for example, by auth_getpwnam() for
433
     * associating the answering auth module with the data looked up.
434
     */
435

436
    if (iter_tab->auth_flags & PR_AUTH_FL_REQUIRED) {
437
      pr_trace_msg(trace_channel, 6,
70✔
438
        "\"%s\" response from module mod_%s is authoritative", match,
1✔
439
        iter_tab->m->name);
440

1✔
441
      if (m) {
442
        *m = iter_tab->m;
1✔
443
      }
×
444

445
      break;
446
    }
447

448
    if (MODRET_ISHANDLED(mr) ||
449
        MODRET_ISERROR(mr)) {
69✔
450

451
      if (m) {
452
        *m = iter_tab->m;
54✔
453
      }
20✔
454

455
      break;
456
    }
457

458
  next:
459
    iter_tab = pr_stash_get_symbol2(PR_SYM_AUTH, match, iter_tab,
15✔
460
      &cmd->stash_index, &cmd->stash_hash);
15✔
461

462
    if (iter_tab == start_tab) {
463
      /* We have looped back to the start.  Break out now and do not loop
15✔
464
       * around again (and again, and again...)
465
       */
466
      pr_trace_msg(trace_channel, 15, "reached end of symbols for '%s'", match);
467
      mr = PR_DECLINED(cmd);
×
468
      break;
×
469
    }
×
470
  }
471

472
  return mr;
473
}
474

475
void pr_auth_setpwent(pool *p) {
476
  cmd_rec *cmd = NULL;
2✔
477

2✔
478
  cmd = make_cmd(p, 0);
479
  (void) dispatch_auth(cmd, "setpwent", NULL);
2✔
480

2✔
481
  if (cmd->tmp_pool != NULL) {
482
    destroy_pool(cmd->tmp_pool);
2✔
483
    cmd->tmp_pool = NULL;
2✔
484
  }
2✔
485
}
486

2✔
487
void pr_auth_endpwent(pool *p) {
488
  cmd_rec *cmd = NULL;
4✔
489

4✔
490
  cmd = make_cmd(p, 0);
491
  (void) dispatch_auth(cmd, "endpwent", NULL);
4✔
492

4✔
493
  if (cmd->tmp_pool != NULL) {
494
    destroy_pool(cmd->tmp_pool);
4✔
495
    cmd->tmp_pool = NULL;
4✔
496
  }
4✔
497

498
  if (auth_tab != NULL) {
499
    int item_count;
4✔
500

1✔
501
    item_count = pr_table_count(auth_tab);
502
    pr_trace_msg(trace_channel, 5, "emptying authcache (%d %s)", item_count,
1✔
503
      item_count != 1 ? "items" : "item");
2✔
504

505
    (void) pr_table_empty(auth_tab);
506
    (void) pr_table_free(auth_tab);
1✔
507
    auth_tab = NULL;
1✔
508
  }
1✔
509
}
510

4✔
511
void pr_auth_setgrent(pool *p) {
512
  cmd_rec *cmd = NULL;
2✔
513

2✔
514
  cmd = make_cmd(p, 0);
515
  (void) dispatch_auth(cmd, "setgrent", NULL);
2✔
516

2✔
517
  if (cmd->tmp_pool != NULL) {
518
    destroy_pool(cmd->tmp_pool);
2✔
519
    cmd->tmp_pool = NULL;
2✔
520
  }
2✔
521
}
522

2✔
523
void pr_auth_endgrent(pool *p) {
524
  cmd_rec *cmd = NULL;
3✔
525

3✔
526
  cmd = make_cmd(p, 0);
527
  (void) dispatch_auth(cmd, "endgrent", NULL);
3✔
528

3✔
529
  if (cmd->tmp_pool != NULL) {
530
    destroy_pool(cmd->tmp_pool);
3✔
531
    cmd->tmp_pool = NULL;
3✔
532
  }
3✔
533
}
534

3✔
535
struct passwd *pr_auth_getpwent(pool *p) {
536
  cmd_rec *cmd = NULL;
5✔
537
  modret_t *mr = NULL;
5✔
538
  struct passwd *res = NULL;
5✔
539

5✔
540
  if (p == NULL) {
541
    errno = EINVAL;
5✔
542
    return NULL;
1✔
543
  }
1✔
544

545
  cmd = make_cmd(p, 0);
546
  mr = dispatch_auth(cmd, "getpwent", NULL);
4✔
547

4✔
548
  if (MODRET_ISHANDLED(mr) &&
549
      MODRET_HASDATA(mr)) {
4✔
550
    res = mr->data;
3✔
551
  }
3✔
552

553
  if (cmd->tmp_pool != NULL) {
554
    destroy_pool(cmd->tmp_pool);
4✔
555
    cmd->tmp_pool = NULL;
4✔
556
  }
4✔
557

558
  /* Sanity check */
559
  if (res == NULL) {
560
    return NULL;
4✔
561
  }
562

563
  /* Make sure the UID and GID are not -1 */
564
  if (res->pw_uid == (uid_t) -1) {
565
    pr_log_pri(PR_LOG_WARNING, "error: UID of -1 not allowed");
3✔
566
    errno = ENOENT;
1✔
567
    return NULL;
1✔
568
  }
1✔
569

570
  if (res->pw_gid == (gid_t) -1) {
571
    pr_log_pri(PR_LOG_WARNING, "error: GID of -1 not allowed");
2✔
572
    errno = ENOENT;
1✔
573
    return NULL;
1✔
574
  }
1✔
575

576
  return res;
577
}
578

579
struct group *pr_auth_getgrent(pool *p) {
580
  cmd_rec *cmd = NULL;
4✔
581
  modret_t *mr = NULL;
4✔
582
  struct group *res = NULL;
4✔
583

4✔
584
  if (p == NULL) {
585
    errno = EINVAL;
4✔
586
    return NULL;
1✔
587
  }
1✔
588

589
  cmd = make_cmd(p, 0);
590
  mr = dispatch_auth(cmd, "getgrent", NULL);
3✔
591

3✔
592
  if (MODRET_ISHANDLED(mr) &&
593
      MODRET_HASDATA(mr)) {
3✔
594
    res = mr->data;
2✔
595
  }
2✔
596

597
  if (cmd->tmp_pool != NULL) {
598
    destroy_pool(cmd->tmp_pool);
3✔
599
    cmd->tmp_pool = NULL;
3✔
600
  }
3✔
601

602
  /* Sanity check */
603
  if (res == NULL) {
604
    return NULL;
3✔
605
  }
606

607
  /* Make sure the GID is not -1 */
608
  if (res->gr_gid == (gid_t) -1) {
609
    pr_log_pri(PR_LOG_WARNING, "error: GID of -1 not allowed");
2✔
610
    errno = ENOENT;
1✔
611
    return NULL;
1✔
612
  }
1✔
613

614
  return res;
615
}
616

617
struct passwd *pr_auth_getpwnam(pool *p, const char *name) {
618
  cmd_rec *cmd = NULL;
11✔
619
  modret_t *mr = NULL;
11✔
620
  struct passwd *res = NULL;
11✔
621
  module *m = NULL;
11✔
622

11✔
623
  if (p == NULL ||
624
      name == NULL) {
11✔
625
    errno = EINVAL;
11✔
626
    return NULL;
1✔
627
  }
1✔
628

629
  cmd = make_cmd(p, 1, name);
630
  mr = dispatch_auth(cmd, "getpwnam", &m);
10✔
631

10✔
632
  if (MODRET_ISHANDLED(mr) &&
633
      MODRET_HASDATA(mr)) {
10✔
634
    res = mr->data;
4✔
635
  }
4✔
636

637
  if (cmd->tmp_pool) {
638
    destroy_pool(cmd->tmp_pool);
10✔
639
    cmd->tmp_pool = NULL;
10✔
640
  }
10✔
641

642
  /* Sanity check */
643
  if (res == NULL) {
644
    errno = ENOENT;
10✔
645
    return NULL;
6✔
646
  }
6✔
647

648
  /* Make sure the UID and GID are not -1 */
649
  if (res->pw_uid == (uid_t) -1) {
650
    pr_log_pri(PR_LOG_WARNING, "error: UID of -1 not allowed");
4✔
651
    errno = ENOENT;
1✔
652
    return NULL;
1✔
653
  }
1✔
654

655
  if (res->pw_gid == (gid_t) -1) {
656
    pr_log_pri(PR_LOG_WARNING, "error: GID of -1 not allowed");
3✔
657
    errno = ENOENT;
1✔
658
    return NULL;
1✔
659
  }
1✔
660

661
  if ((auth_caching & PR_AUTH_CACHE_FL_AUTH_MODULE) &&
662
      !auth_tab &&
2✔
663
      auth_pool) {
2✔
664
    auth_tab = pr_table_alloc(auth_pool, 0);
665
  }
1✔
666

667
  if (m &&
668
      auth_tab) {
2✔
669
    int count = 0;
670
    void *value = NULL;
2✔
671

2✔
672
    value = palloc(auth_pool, sizeof(module *));
673
    *((module **) value) = m;
2✔
674

2✔
675
    count = pr_table_exists(auth_tab, name);
676
    if (count <= 0) {
2✔
677
      if (pr_table_add(auth_tab, pstrdup(auth_pool, name), value,
2✔
678
          sizeof(module *)) < 0) {
1✔
679
        pr_trace_msg(trace_channel, 3,
680
          "error adding module 'mod_%s.c' for user '%s' to the authcache: %s",
×
681
          m->name, name, strerror(errno));
682

×
683
      } else {
684
        pr_trace_msg(trace_channel, 5,
685
          "stashed module 'mod_%s.c' for user '%s' in the authcache",
1✔
686
          m->name, name);
687
      }
688

689
    } else {
690
      if (pr_table_set(auth_tab, pstrdup(auth_pool, name), value,
691
          sizeof(module *)) < 0) {
1✔
692
        pr_trace_msg(trace_channel, 3,
693
          "error setting module 'mod_%s.c' for user '%s' in the authcache: %s",
×
694
          m->name, name, strerror(errno));
695

×
696
      } else {
697
        pr_trace_msg(trace_channel, 5,
698
          "stashed module 'mod_%s.c' for user '%s' in the authcache",
1✔
699
          m->name, name);
700
      }
701
    }
702
  }
703

704
  if (auth_caching & PR_AUTH_CACHE_FL_UID2NAME) {
705
    uidcache_add(res->pw_uid, res->pw_name);
2✔
706
  }
2✔
707

708
  if (auth_caching & PR_AUTH_CACHE_FL_NAME2UID) {
709
    usercache_add(res->pw_name, res->pw_uid);
2✔
710
  }
2✔
711

712
  /* Get the (possibly rewritten) home directory. */
713
  res->pw_dir = (char *) pr_auth_get_home(p, res->pw_dir);
714

2✔
715
  pr_log_debug(DEBUG10, "retrieved UID %s for user '%s'",
716
    pr_uid2str(NULL, res->pw_uid), name);
2✔
717
  return res;
718
}
2✔
719

720
struct passwd *pr_auth_getpwuid(pool *p, uid_t uid) {
721
  cmd_rec *cmd = NULL;
6✔
722
  modret_t *mr = NULL;
6✔
723
  struct passwd *res = NULL;
6✔
724

6✔
725
  if (p == NULL) {
726
    errno = EINVAL;
6✔
727
    return NULL;
1✔
728
  }
1✔
729

730
  cmd = make_cmd(p, 1, (void *) &uid);
731
  mr = dispatch_auth(cmd, "getpwuid", NULL);
5✔
732

5✔
733
  if (MODRET_ISHANDLED(mr) &&
734
      MODRET_HASDATA(mr)) {
5✔
735
    res = mr->data;
3✔
736
  }
3✔
737

738
  if (cmd->tmp_pool) {
739
    destroy_pool(cmd->tmp_pool);
5✔
740
    cmd->tmp_pool = NULL;
5✔
741
  }
5✔
742

743
  /* Sanity check */
744
  if (res == NULL) {
745
    errno = ENOENT;
5✔
746
    return NULL;
2✔
747
  }
2✔
748

749
  /* Make sure the UID and GID are not -1 */
750
  if (res->pw_uid == (uid_t) -1) {
751
    pr_log_pri(PR_LOG_WARNING, "error: UID of -1 not allowed");
3✔
752
    errno = ENOENT;
1✔
753
    return NULL;
1✔
754
  }
1✔
755

756
  if (res->pw_gid == (gid_t) -1) {
757
    pr_log_pri(PR_LOG_WARNING, "error: GID of -1 not allowed");
2✔
758
    errno = ENOENT;
1✔
759
    return NULL;
1✔
760
  }
1✔
761

762
  pr_log_debug(DEBUG10, "retrieved user '%s' for UID %s",
763
    res->pw_name, pr_uid2str(NULL, uid));
1✔
764
  return res;
765
}
1✔
766

767
struct group *pr_auth_getgrnam(pool *p, const char *name) {
768
  cmd_rec *cmd = NULL;
5✔
769
  modret_t *mr = NULL;
5✔
770
  struct group *res = NULL;
5✔
771

5✔
772
  if (p == NULL ||
773
      name == NULL) {
5✔
774
    errno = EINVAL;
5✔
775
    return NULL;
1✔
776
  }
1✔
777

778
  cmd = make_cmd(p, 1, name);
779
  mr = dispatch_auth(cmd, "getgrnam", NULL);
4✔
780

4✔
781
  if (MODRET_ISHANDLED(mr) &&
782
      MODRET_HASDATA(mr)) {
4✔
783
    res = mr->data;
2✔
784
  }
2✔
785

786
  if (cmd->tmp_pool) {
787
    destroy_pool(cmd->tmp_pool);
4✔
788
    cmd->tmp_pool = NULL;
4✔
789
  }
4✔
790

791
  /* Sanity check */
792
  if (res == NULL) {
793
    errno = ENOENT;
4✔
794
    return NULL;
2✔
795
  }
2✔
796

797
  /* Make sure the GID is not -1 */
798
  if (res->gr_gid == (gid_t) -1) {
799
    pr_log_pri(PR_LOG_WARNING, "error: GID of -1 not allowed");
2✔
800
    errno = ENOENT;
1✔
801
    return NULL;
1✔
802
  }
1✔
803

804
  if (auth_caching & PR_AUTH_CACHE_FL_GID2NAME) {
805
    gidcache_add(res->gr_gid, name);
1✔
806
  }
1✔
807

808
  if (auth_caching & PR_AUTH_CACHE_FL_NAME2GID) {
809
    groupcache_add(name, res->gr_gid);
1✔
810
  }
1✔
811

812
  pr_log_debug(DEBUG10, "retrieved GID %s for group '%s'",
813
    pr_gid2str(NULL, res->gr_gid), name);
1✔
814
  return res;
815
}
1✔
816

817
struct group *pr_auth_getgrgid(pool *p, gid_t gid) {
818
  cmd_rec *cmd = NULL;
5✔
819
  modret_t *mr = NULL;
5✔
820
  struct group *res = NULL;
5✔
821

5✔
822
  if (p == NULL) {
823
    errno = EINVAL;
5✔
824
    return NULL;
1✔
825
  }
1✔
826

827
  cmd = make_cmd(p, 1, (void *) &gid);
828
  mr = dispatch_auth(cmd, "getgrgid", NULL);
4✔
829

4✔
830
  if (MODRET_ISHANDLED(mr) &&
831
      MODRET_HASDATA(mr)) {
4✔
832
    res = mr->data;
2✔
833
  }
2✔
834

835
  if (cmd->tmp_pool) {
836
    destroy_pool(cmd->tmp_pool);
4✔
837
    cmd->tmp_pool = NULL;
4✔
838
  }
4✔
839

840
  /* Sanity check */
841
  if (res == NULL) {
842
    errno = ENOENT;
4✔
843
    return NULL;
2✔
844
  }
2✔
845

846
  /* Make sure the GID is not -1 */
847
  if (res->gr_gid == (gid_t) -1) {
848
    pr_log_pri(PR_LOG_WARNING, "error: GID of -1 not allowed");
2✔
849
    errno = ENOENT;
1✔
850
    return NULL;
1✔
851
  }
1✔
852

853
  pr_log_debug(DEBUG10, "retrieved group '%s' for GID %lu",
854
    res->gr_name, (unsigned long) gid);
1✔
855
  return res;
856
}
1✔
857

858
static const char *get_authcode_str(int auth_code) {
859
  const char *name = "(unknown)";
24✔
860

24✔
861
  switch (auth_code) {
862
    case PR_AUTH_OK_NO_PASS:
24✔
863
      name = "OK_NO_PASS";
1✔
864
      break;
1✔
865

1✔
866
    case PR_AUTH_RFC2228_OK:
867
      name = "RFC2228_OK";
3✔
868
      break;
3✔
869

3✔
870
    case PR_AUTH_OK:
871
      name = "OK";
4✔
872
      break;
4✔
873

4✔
874
    case PR_AUTH_ERROR:
875
      name = "ERROR";
1✔
876
      break;
1✔
877

1✔
878
    case PR_AUTH_NOPWD:
879
      name = "NOPWD";
2✔
880
      break;
2✔
881

2✔
882
    case PR_AUTH_BADPWD:
883
      name = "BADPWD";
3✔
884
      break;
3✔
885

3✔
886
    case PR_AUTH_AGEPWD:
887
      name = "AGEPWD";
1✔
888
      break;
1✔
889

1✔
890
    case PR_AUTH_DISABLEDPWD:
891
      name = "DISABLEDPWD";
1✔
892
      break;
1✔
893

1✔
894
    case PR_AUTH_CRED_INSUFFICIENT:
895
      name = "CRED_INSUFFICIENT";
1✔
896
      break;
1✔
897

1✔
898
    case PR_AUTH_CRED_UNAVAIL:
899
      name = "CRED_UNAVAIL";
1✔
900
      break;
1✔
901

1✔
902
    case PR_AUTH_CRED_ERROR:
903
      name = "CRED_ERROR";
1✔
904
      break;
1✔
905

1✔
906
    case PR_AUTH_INFO_UNAVAIL:
907
      name = "INFO_UNAVAIL";
1✔
908
      break;
1✔
909

1✔
910
    case PR_AUTH_MAX_ATTEMPTS_EXCEEDED:
911
      name = "MAX_ATTEMPTS_EXCEEDED";
1✔
912
      break;
1✔
913

1✔
914
    case PR_AUTH_INIT_ERROR:
915
      name = "INIT_ERROR";
1✔
916
      break;
1✔
917

1✔
918
    case PR_AUTH_NEW_TOKEN_REQUIRED:
919
      name = "NEW_TOKEN_REQUIRED";
1✔
920
      break;
1✔
921

1✔
922
    default:
923
      break;
924
  }
925

926
  return name;
927
}
24✔
928

929
int pr_auth_authenticate(pool *p, const char *name, const char *pw) {
930
  cmd_rec *cmd = NULL;
12✔
931
  modret_t *mr = NULL;
12✔
932
  module *m = NULL;
12✔
933
  int res = PR_AUTH_NOPWD;
12✔
934

12✔
935
  if (p == NULL ||
936
      name == NULL ||
12✔
937
      pw == NULL) {
12✔
938
    errno = EINVAL;
939
    return -1;
3✔
940
  }
3✔
941

942
  cmd = make_cmd(p, 2, name, pw);
943

9✔
944
  /* First, check for any of the modules in the "authenticating only" list
945
   * of modules.  This is usually only mod_auth_pam, but other modules
946
   * might also add themselves (e.g. mod_radius under certain conditions).
947
   */
948
  if (auth_module_list != NULL) {
949
    struct auth_module_elt *elt;
9✔
950

4✔
951
    for (elt = (struct auth_module_elt *) auth_module_list->xas_list; elt;
952
        elt = elt->next) {
9✔
953
      pr_signals_handle();
5✔
954

8✔
955
      pr_trace_msg(trace_channel, 7, "checking with auth-only module '%s'",
956
        elt->name);
8✔
957

958
      m = pr_module_get(elt->name);
959
      if (m != NULL) {
8✔
960
        mr = dispatch_auth(cmd, "auth", &m);
8✔
961

4✔
962
        if (MODRET_ISHANDLED(mr)) {
963
          pr_trace_msg(trace_channel, 4,
4✔
964
            "module '%s' used for authenticating user '%s'", elt->name, name);
2✔
965

966
          res = MODRET_HASDATA(mr) ? PR_AUTH_RFC2228_OK : PR_AUTH_OK;
967

2✔
968
          if (cmd->tmp_pool) {
969
            destroy_pool(cmd->tmp_pool);
2✔
970
            cmd->tmp_pool = NULL;
2✔
971
          }
2✔
972

973
          pr_trace_msg(trace_channel, 9,
974
            "module '%s' returned HANDLED (%s) for authenticating user '%s'",
2✔
975
            elt->name, get_authcode_str(res), name);
976
          return res;
977
        }
2✔
978

979
        if (MODRET_ISERROR(mr)) {
980
          pr_trace_msg(trace_channel, 4,
2✔
981
            "module '%s' used for authenticating user '%s'", elt->name, name);
1✔
982

983
          res = MODRET_ERROR(mr);
984

1✔
985
          if (cmd->tmp_pool) {
986
            destroy_pool(cmd->tmp_pool);
1✔
987
            cmd->tmp_pool = NULL;
1✔
988
          }
1✔
989

990
          pr_trace_msg(trace_channel, 9,
991
            "module '%s' returned ERROR (%s) for authenticating user '%s'",
1✔
992
            elt->name, get_authcode_str(res), name);
993
          return res;
994
        }
1✔
995

996
        m = NULL;
997
      }
1✔
998
    }
999
  }
1000

1001
  if (auth_tab != NULL) {
1002
    const void *v;
6✔
1003

×
1004
    /* Fetch the specific module to be used for authenticating this user. */
1005
    v = pr_table_get(auth_tab, name, NULL);
1006
    if (v != NULL) {
×
1007
      m = *((module **) v);
×
1008

×
1009
      pr_trace_msg(trace_channel, 4,
1010
        "using module 'mod_%s.c' from authcache to authenticate user '%s'",
×
1011
        m->name, name);
1012
    }
1013
  }
1014

1015
  mr = dispatch_auth(cmd, "auth", m ? &m : NULL);
1016

12✔
1017
  if (MODRET_ISHANDLED(mr)) {
1018
    res = MODRET_HASDATA(mr) ? PR_AUTH_RFC2228_OK : PR_AUTH_OK;
6✔
1019
    pr_trace_msg(trace_channel, 9,
3✔
1020
      "obtained HANDLED (%s) for authenticating user '%s'",
3✔
1021
      get_authcode_str(res), name);
1022

1023
  } else if (MODRET_ISERROR(mr)) {
1024
    res = MODRET_ERROR(mr);
3✔
1025
    pr_trace_msg(trace_channel, 9,
1✔
1026
      "obtained ERROR (%s) for authenticating user '%s'", get_authcode_str(res),
1✔
1027
      name);
1028
  }
1029

1030
  if (cmd->tmp_pool) {
1031
    destroy_pool(cmd->tmp_pool);
6✔
1032
    cmd->tmp_pool = NULL;
6✔
1033
  }
6✔
1034

1035
  return res;
1036
}
1037

1038
int pr_auth_authorize(pool *p, const char *name) {
1039
  cmd_rec *cmd = NULL;
6✔
1040
  modret_t *mr = NULL;
6✔
1041
  module *m = NULL;
6✔
1042
  int res = PR_AUTH_OK;
6✔
1043

6✔
1044
  if (p == NULL ||
1045
      name == NULL) {
6✔
1046
    errno = EINVAL;
6✔
1047
    return -1;
2✔
1048
  }
2✔
1049

1050
  cmd = make_cmd(p, 1, name);
1051

4✔
1052
  if (auth_tab != NULL) {
1053
    const void *v;
4✔
1054

×
1055
    /* Fetch the specific module to be used for authenticating this user. */
1056
    v = pr_table_get(auth_tab, name, NULL);
1057
    if (v != NULL) {
×
1058
      m = *((module **) v);
×
1059

×
1060
      pr_trace_msg(trace_channel, 4,
1061
        "using module 'mod_%s.c' from authcache to authorize user '%s'",
×
1062
        m->name, name);
1063
    }
1064
  }
1065

1066
  mr = dispatch_auth(cmd, "authorize", m ? &m : NULL);
1067

8✔
1068
  /* Unlike the other auth calls, we assume here that unless the handlers
1069
   * explicitly return ERROR, the user is authorized.  Thus HANDLED and
1070
   * DECLINED are both treated as "yes, this user is authorized".  This
1071
   * handles the case where the authenticating module (e.g. mod_sql)
1072
   * does NOT provide an 'authorize' handler.
1073
   */
1074

1075
  if (MODRET_ISERROR(mr)) {
1076
    res = MODRET_ERROR(mr);
4✔
1077
    pr_trace_msg(trace_channel, 9,
2✔
1078
      "obtained ERROR (%s) for authorizing user '%s'", get_authcode_str(res),
2✔
1079
      name);
1080
  }
1081

1082
  if (cmd->tmp_pool) {
1083
    destroy_pool(cmd->tmp_pool);
4✔
1084
    cmd->tmp_pool = NULL;
4✔
1085
  }
4✔
1086

1087
  return res;
1088
}
1089

1090
int pr_auth_check(pool *p, const char *ciphertext_passwd, const char *name,
1091
    const char *cleartext_passwd) {
23✔
1092
  cmd_rec *cmd = NULL;
1093
  modret_t *mr = NULL;
23✔
1094
  module *m = NULL;
23✔
1095
  int res = PR_AUTH_BADPWD;
23✔
1096
  size_t cleartext_passwd_len = 0;
23✔
1097

23✔
1098
  /* Note: it's possible for ciphertext_passwd to be NULL (mod_ldap might do
1099
   * this, for example), so we cannot enforce that it be non-NULL.
1100
   */
1101

1102
  if (p == NULL ||
1103
      name == NULL ||
23✔
1104
      cleartext_passwd == NULL) {
23✔
1105
    errno = EINVAL;
1106
    return -1;
3✔
1107
  }
3✔
1108

1109
  cleartext_passwd_len = strlen(cleartext_passwd);
1110
  if (cleartext_passwd_len > auth_max_passwd_len) {
20✔
1111
    pr_log_auth(PR_LOG_INFO,
20✔
1112
      "client-provided password size exceeds MaxPasswordSize (%lu), "
1✔
1113
      "rejecting", (unsigned long) auth_max_passwd_len);
1114
    errno = EPERM;
1115
    return -1;
1✔
1116
  }
1✔
1117

1118
  cmd = make_cmd(p, 3, ciphertext_passwd, name, cleartext_passwd);
1119

19✔
1120
  /* First, check for any of the modules in the "authenticating only" list
1121
   * of modules.  This is usually only mod_auth_pam, but other modules
1122
   * might also add themselves (e.g. mod_radius under certain conditions).
1123
   */
1124
  if (auth_module_list != NULL) {
1125
    struct auth_module_elt *elt;
19✔
1126

14✔
1127
    for (elt = (struct auth_module_elt *) auth_module_list->xas_list; elt;
1128
        elt = elt->next) {
16✔
1129
      pr_signals_handle();
2✔
1130

15✔
1131
      m = pr_module_get(elt->name);
1132
      if (m != NULL) {
15✔
1133
        mr = dispatch_auth(cmd, "check", &m);
15✔
1134
        if (MODRET_ISHANDLED(mr)) {
13✔
1135
          pr_trace_msg(trace_channel, 4,
13✔
1136
            "module '%s' used for authenticating user '%s'", elt->name, name);
×
1137

1138
          res = MODRET_HASDATA(mr) ? PR_AUTH_RFC2228_OK : PR_AUTH_OK;
1139

×
1140
          if (cmd->tmp_pool != NULL) {
1141
            destroy_pool(cmd->tmp_pool);
×
1142
            cmd->tmp_pool = NULL;
×
1143
          }
×
1144

1145
          pr_trace_msg(trace_channel, 9,
1146
            "module '%s' returned HANDLED (%s) for checking user '%s'",
×
1147
            elt->name, get_authcode_str(res), name);
1148
          return res;
1149
        }
×
1150

1151
        if (MODRET_ISERROR(mr)) {
1152
          res = MODRET_ERROR(mr);
13✔
1153

13✔
1154
          if (cmd->tmp_pool != NULL) {
1155
            destroy_pool(cmd->tmp_pool);
13✔
1156
            cmd->tmp_pool = NULL;
13✔
1157
          }
13✔
1158

1159
          pr_trace_msg(trace_channel, 9,
1160
            "module '%s' returned ERROR (%d %s) for checking user '%s'",
13✔
1161
            elt->name, res, get_authcode_str(res), name);
1162
          return res;
1163
        }
13✔
1164

1165
        m = NULL;
1166
      }
×
1167
    }
1168
  }
1169

1170
  if (auth_tab != NULL) {
1171
    const void *v;
6✔
1172

×
1173
    /* Fetch the specific module to be used for authenticating this user. */
1174
    v = pr_table_get(auth_tab, name, NULL);
1175
    if (v != NULL) {
×
1176
      m = *((module **) v);
×
1177

×
1178
      pr_trace_msg(trace_channel, 4,
1179
        "using module 'mod_%s.c' from authcache to authenticate user '%s'",
×
1180
        m->name, name);
1181
    }
1182
  }
1183

1184
  mr = dispatch_auth(cmd, "check", m ? &m : NULL);
1185

12✔
1186
  if (MODRET_ISHANDLED(mr)) {
1187
    res = MODRET_HASDATA(mr) ? PR_AUTH_RFC2228_OK : PR_AUTH_OK;
6✔
1188
    pr_trace_msg(trace_channel, 9,
2✔
1189
      "obtained HANDLED (%s) for checking user '%s'", get_authcode_str(res),
2✔
1190
      name);
1191
  }
1192

1193
  if (cmd->tmp_pool != NULL) {
1194
    destroy_pool(cmd->tmp_pool);
6✔
1195
    cmd->tmp_pool = NULL;
6✔
1196
  }
6✔
1197

1198
  return res;
1199
}
1200

1201
int pr_auth_requires_pass(pool *p, const char *name) {
1202
  cmd_rec *cmd;
5✔
1203
  modret_t *mr;
5✔
1204
  int res = TRUE;
5✔
1205

5✔
1206
  if (p == NULL ||
1207
      name == NULL) {
5✔
1208
    errno = EINVAL;
5✔
1209
    return -1;
2✔
1210
  }
2✔
1211

1212
  cmd = make_cmd(p, 1, name);
1213
  mr = dispatch_auth(cmd, "requires_pass", NULL);
3✔
1214

3✔
1215
  if (MODRET_ISHANDLED(mr)) {
1216
    res = FALSE;
3✔
1217

1218
  } else if (MODRET_ISERROR(mr)) {
1219
    res = MODRET_ERROR(mr);
2✔
1220
  }
1✔
1221

1222
  if (cmd->tmp_pool) {
1223
    destroy_pool(cmd->tmp_pool);
3✔
1224
    cmd->tmp_pool = NULL;
3✔
1225
  }
3✔
1226

1227
  return res;
1228
}
1229

1230
const char *pr_auth_uid2name(pool *p, uid_t uid) {
1231
  static char namebuf[PR_TUNABLE_LOGIN_MAX+1];
7✔
1232
  cmd_rec *cmd = NULL;
7✔
1233
  modret_t *mr = NULL;
7✔
1234
  char *res = NULL;
7✔
1235
  unsigned int cache_lookup_flags = (PR_AUTH_CACHE_FL_UID2NAME|PR_AUTH_CACHE_FL_BAD_UID2NAME);
7✔
1236
  int have_name = FALSE;
7✔
1237

7✔
1238
  if (p == NULL) {
1239
    errno = EINVAL;
7✔
1240
    return NULL;
1✔
1241
  }
1✔
1242

1243
  uidcache_create();
1244

6✔
1245
  if (auth_caching & cache_lookup_flags) {
1246
    if (uidcache_get(uid, namebuf, sizeof(namebuf)) == 0) {
6✔
1247
      res = namebuf;
6✔
1248
      return res;
7✔
1249
    }
1250
  }
1251

1252
  cmd = make_cmd(p, 1, (void *) &uid);
1253
  mr = dispatch_auth(cmd, "uid2name", NULL);
4✔
1254

4✔
1255
  if (MODRET_ISHANDLED(mr) &&
1256
      MODRET_HASDATA(mr)) {
4✔
1257
    res = mr->data;
2✔
1258
    sstrncpy(namebuf, res, sizeof(namebuf));
2✔
1259
    res = namebuf;
2✔
1260

2✔
1261
    if (auth_caching & PR_AUTH_CACHE_FL_UID2NAME) {
1262
      uidcache_add(uid, res);
2✔
1263
    }
2✔
1264

1265
    have_name = TRUE;
1266
  }
1267

1268
  if (cmd->tmp_pool) {
1269
    destroy_pool(cmd->tmp_pool);
4✔
1270
    cmd->tmp_pool = NULL;
4✔
1271
  }
4✔
1272

1273
  if (!have_name) {
1274
    /* TODO: This conversion is data type sensitive, per Bug#4164. */
4✔
1275
    pr_snprintf(namebuf, sizeof(namebuf)-1, "%lu", (unsigned long) uid);
1276
    res = namebuf;
2✔
1277

2✔
1278
    if (auth_caching & PR_AUTH_CACHE_FL_BAD_UID2NAME) {
1279
      uidcache_add(uid, res);
2✔
1280
    }
1✔
1281
  }
1282

1283
  return res;
1284
}
1285

1286
const char *pr_auth_gid2name(pool *p, gid_t gid) {
1287
  static char namebuf[PR_TUNABLE_LOGIN_MAX+1];
7✔
1288
  cmd_rec *cmd = NULL;
7✔
1289
  modret_t *mr = NULL;
7✔
1290
  char *res = NULL;
7✔
1291
  unsigned int cache_lookup_flags = (PR_AUTH_CACHE_FL_GID2NAME|PR_AUTH_CACHE_FL_BAD_GID2NAME);
7✔
1292
  int have_name = FALSE;
7✔
1293

7✔
1294
  if (p == NULL) {
1295
    errno = EINVAL;
7✔
1296
    return NULL;
1✔
1297
  }
1✔
1298

1299
  gidcache_create();
1300

6✔
1301
  if (auth_caching & cache_lookup_flags) {
1302
    if (gidcache_get(gid, namebuf, sizeof(namebuf)) == 0) {
6✔
1303
      res = namebuf;
6✔
1304
      return res;
7✔
1305
    }
1306
  }
1307

1308
  cmd = make_cmd(p, 1, (void *) &gid);
1309
  mr = dispatch_auth(cmd, "gid2name", NULL);
4✔
1310

4✔
1311
  if (MODRET_ISHANDLED(mr) &&
1312
      MODRET_HASDATA(mr)) {
4✔
1313
    res = mr->data;
2✔
1314
    sstrncpy(namebuf, res, sizeof(namebuf));
2✔
1315
    res = namebuf;
2✔
1316

2✔
1317
    if (auth_caching & PR_AUTH_CACHE_FL_GID2NAME) {
1318
      gidcache_add(gid, res);
2✔
1319
    }
2✔
1320

1321
    have_name = TRUE;
1322
  }
1323

1324
  if (cmd->tmp_pool) {
1325
    destroy_pool(cmd->tmp_pool);
4✔
1326
    cmd->tmp_pool = NULL;
4✔
1327
  }
4✔
1328

1329
  if (!have_name) {
1330
    /* TODO: This conversion is data type sensitive, per Bug#4164. */
4✔
1331
    pr_snprintf(namebuf, sizeof(namebuf)-1, "%lu", (unsigned long) gid);
1332
    res = namebuf;
2✔
1333

2✔
1334
    if (auth_caching & PR_AUTH_CACHE_FL_BAD_GID2NAME) {
1335
      gidcache_add(gid, res);
2✔
1336
    }
1✔
1337
  }
1338

1339
  return res;
1340
}
1341

1342
uid_t pr_auth_name2uid(pool *p, const char *name) {
1343
  cmd_rec *cmd = NULL;
23✔
1344
  modret_t *mr = NULL;
23✔
1345
  uid_t res = (uid_t) -1;
23✔
1346
  unsigned int cache_lookup_flags = (PR_AUTH_CACHE_FL_NAME2UID|PR_AUTH_CACHE_FL_BAD_NAME2UID);
23✔
1347
  int have_id = FALSE;
23✔
1348

23✔
1349
  if (p == NULL ||
1350
      name == NULL) {
23✔
1351
    errno = EINVAL;
23✔
1352
    return (uid_t) -1;
1✔
1353
  }
1✔
1354

1355
  usercache_create();
1356

22✔
1357
  if (auth_caching & cache_lookup_flags) {
1358
    uid_t cache_uid;
22✔
1359

22✔
1360
    if (usercache_get(name, &cache_uid) == 0) {
1361
      res = cache_uid;
22✔
1362

2✔
1363
      if (res == (uid_t) -1) {
1364
        errno = ENOENT;
2✔
1365
      }
1✔
1366

1367
      return res;
1368
    }
2✔
1369
  }
1370

1371
  cmd = make_cmd(p, 1, name);
1372
  mr = dispatch_auth(cmd, "name2uid", NULL);
20✔
1373

20✔
1374
  if (MODRET_ISHANDLED(mr) &&
1375
      MODRET_HASDATA(mr)) {
20✔
1376
    res = *((uid_t *) mr->data);
1✔
1377

1✔
1378
    if (auth_caching & PR_AUTH_CACHE_FL_NAME2UID) {
1379
      usercache_add(name, res);
1✔
1380
    }
1✔
1381

1382
    have_id = TRUE;
1383

1384
  } else {
1385
    errno = ENOENT;
1386
  }
19✔
1387

1388
  if (cmd->tmp_pool) {
1389
    destroy_pool(cmd->tmp_pool);
20✔
1390
    cmd->tmp_pool = NULL;
20✔
1391
  }
20✔
1392

1393
  if (!have_id &&
1394
      (auth_caching & PR_AUTH_CACHE_FL_BAD_NAME2UID)) {
20✔
1395
    usercache_add(name, res);
19✔
1396
  }
18✔
1397

1398
  return res;
1399
}
1400

1401
gid_t pr_auth_name2gid(pool *p, const char *name) {
1402
  cmd_rec *cmd = NULL;
18✔
1403
  modret_t *mr = NULL;
18✔
1404
  gid_t res = (gid_t) -1;
18✔
1405
  unsigned int cache_lookup_flags = (PR_AUTH_CACHE_FL_NAME2GID|PR_AUTH_CACHE_FL_BAD_NAME2GID);
18✔
1406
  int have_id = FALSE;
18✔
1407

18✔
1408
  if (p == NULL ||
1409
      name == NULL) {
18✔
1410
    errno = EINVAL;
18✔
1411
    return (gid_t) -1;
1✔
1412
  }
1✔
1413

1414
  groupcache_create();
1415

17✔
1416
  if (auth_caching & cache_lookup_flags) {
1417
    gid_t cache_gid;
17✔
1418

17✔
1419
    if (groupcache_get(name, &cache_gid) == 0) {
1420
      res = cache_gid;
17✔
1421

2✔
1422
      if (res == (gid_t) -1) {
1423
        errno = ENOENT;
2✔
1424
      }
1✔
1425

1426
      return res;
1427
    }
2✔
1428
  }
1429

1430
  cmd = make_cmd(p, 1, name);
1431
  mr = dispatch_auth(cmd, "name2gid", NULL);
15✔
1432

15✔
1433
  if (MODRET_ISHANDLED(mr) &&
1434
      MODRET_HASDATA(mr)) {
15✔
1435
    res = *((gid_t *) mr->data);
1✔
1436

1✔
1437
    if (auth_caching & PR_AUTH_CACHE_FL_NAME2GID) {
1438
      groupcache_add(name, res);
1✔
1439
    }
1✔
1440

1441
    have_id = TRUE;
1442

1443
  } else {
1444
    errno = ENOENT;
1445
  }
14✔
1446

1447
  if (cmd->tmp_pool) {
1448
    destroy_pool(cmd->tmp_pool);
15✔
1449
    cmd->tmp_pool = NULL;
15✔
1450
  }
15✔
1451

1452
  if (!have_id &&
1453
      (auth_caching & PR_AUTH_CACHE_FL_BAD_NAME2GID)) {
15✔
1454
    groupcache_add(name, res);
14✔
1455
  }
13✔
1456

1457
  return res;
1458
}
1459

1460
int pr_auth_getgroups(pool *p, const char *name, array_header **group_ids,
1461
    array_header **group_names) {
4✔
1462
  cmd_rec *cmd = NULL;
1463
  modret_t *mr = NULL;
4✔
1464
  int res = -1;
4✔
1465

4✔
1466
  if (p == NULL ||
1467
      name == NULL) {
4✔
1468
    errno = EINVAL;
4✔
1469
    return -1;
1✔
1470
  }
1✔
1471

1472
  /* Allocate memory for the array_headers of GIDs and group names. */
1473
  if (group_ids != NULL) {
1474
    *group_ids = make_array(p, 2, sizeof(gid_t));
3✔
1475
  }
3✔
1476

1477
  if (group_names != NULL) {
1478
    *group_names = make_array(p, 2, sizeof(char *));
3✔
1479
  }
2✔
1480

1481
  cmd = make_cmd(p, 3, name, group_ids ? *group_ids : NULL,
1482
    group_names ? *group_names : NULL);
3✔
1483

1484
  mr = dispatch_auth(cmd, "getgroups", NULL);
1485

3✔
1486
  if (MODRET_ISHANDLED(mr) &&
1487
      MODRET_HASDATA(mr)) {
3✔
1488
    res = *((int *) mr->data);
1✔
1489

1✔
1490
    /* Note: the number of groups returned should, barring error,
1491
     * always be at least 1, as per getgroups(2) behavior.  This one
1492
     * ID is present because it is the primary group membership set in
1493
     * struct passwd, from /etc/passwd.  This will need to be documented
1494
     * for the benefit of auth_getgroup() implementors.
1495
     */
1496

1497
    if (group_ids != NULL) {
1498
      register unsigned int i;
1✔
1499
      char *strgids = "";
1✔
1500
      gid_t *gids = (*group_ids)->elts;
1✔
1501

1✔
1502
      for (i = 0; i < (*group_ids)->nelts; i++) {
1503
        pr_signals_handle();
2✔
1504
        strgids = pstrcat(p, strgids, i != 0 ? ", " : "",
1✔
1505
          pr_gid2str(NULL, gids[i]), NULL);
2✔
1506
      }
1✔
1507

1508
      pr_log_debug(DEBUG10, "retrieved group %s: %s",
1509
        (*group_ids)->nelts == 1 ? "ID" : "IDs",
1✔
1510
        *strgids ? strgids : "(None; corrupted group file?)");
1511
    }
1✔
1512

1513
    if (group_names != NULL) {
1514
      register unsigned int i;
1✔
1515
      char *strgroups = "";
1✔
1516
      char **groups = (*group_names)->elts;
1✔
1517

1✔
1518
      for (i = 0; i < (*group_names)->nelts; i++) {
1519
        pr_signals_handle();
2✔
1520
        strgroups = pstrcat(p, strgroups, i != 0 ? ", " : "", groups[i], NULL);
1✔
1521
      }
2✔
1522

1523
      pr_log_debug(DEBUG10, "retrieved group %s: %s",
1524
        (*group_names)->nelts == 1 ? "name" : "names",
1✔
1525
        *strgroups ? strgroups : "(None; corrupted group file?)");
1526
    }
1✔
1527
  }
1528

1529
  if (cmd->tmp_pool != NULL) {
1530
    destroy_pool(cmd->tmp_pool);
3✔
1531
    cmd->tmp_pool = NULL;
3✔
1532
  }
3✔
1533

1534
  return res;
1535
}
1536

1537
/* This is one messy function.  Yuck.  Yay legacy code. */
1538
config_rec *pr_auth_get_anon_config(pool *p, const char **login_user,
1539
    char **real_user, char **anon_name) {
6✔
1540
  config_rec *c = NULL, *alias_config = NULL, *anon_config = NULL;
1541
  char *config_user_name = NULL, *config_anon_name = NULL;
6✔
1542
  unsigned char is_alias = FALSE, *auth_alias_only = NULL;
6✔
1543
  unsigned long config_flags = (PR_CONFIG_FIND_FL_SKIP_DIR|PR_CONFIG_FIND_FL_SKIP_LIMIT|PR_CONFIG_FIND_FL_SKIP_DYNDIR);
6✔
1544

6✔
1545
  /* Precedence rules:
1546
   *   1. Search for UserAlias directive.
1547
   *   2. Search for Anonymous directive.
1548
   *   3. Normal user login
1549
   */
1550

1551
  config_user_name = get_param_ptr(main_server->conf, "UserName", FALSE);
1552
  if (config_user_name != NULL &&
6✔
1553
      real_user != NULL) {
6✔
1554
    *real_user = config_user_name;
6✔
1555
  }
×
1556

1557
  /* If the main_server->conf->set list is large (e.g. there are many
1558
   * config_recs in the list, as can happen if MANY <Directory> sections are
1559
   * configured), the login can timeout because this find_config() call takes
1560
   * a long time.  The reason this issue strikes HERE first in the login
1561
   * process is that this appears to the first find_config() call which has
1562
   * a TRUE recurse flag.
1563
   *
1564
   * The find_config() call below is looking for a UserAlias directive
1565
   * anywhere in the configuration, no matter how deeply buried in nested
1566
   * config contexts it might be.
1567
   */
1568

1569
  c = find_config2(main_server->conf, CONF_PARAM, "UserAlias", TRUE,
1570
    config_flags);
6✔
1571
  if (c != NULL) {
1572
    do {
6✔
1573
      const char *alias;
5✔
1574

5✔
1575
      pr_signals_handle();
1576

5✔
1577
      alias = c->argv[0];
1578
      if (strcmp(alias, "*") == 0 ||
5✔
1579
          strcmp(alias, *login_user) == 0) {
5✔
1580
        is_alias = TRUE;
4✔
1581
        alias_config = c;
1582
        break;
1583
      }
1584

1585
    } while ((c = find_config_next2(c, c->next, CONF_PARAM, "UserAlias",
1586
      TRUE, config_flags)) != NULL);
2✔
1587
  }
1✔
1588

1589
  /* This is where things get messy, rapidly. */
1590
  if (is_alias == TRUE) {
1591
    c = alias_config;
6✔
1592
  }
4✔
1593

1594
  while (c != NULL &&
1595
         c->parent != NULL &&
4✔
1596
         (auth_alias_only = get_param_ptr(c->parent->subset, "AuthAliasOnly", FALSE))) {
8✔
1597

2✔
1598
    pr_signals_handle();
1599

2✔
1600
    /* If AuthAliasOnly is on, ignore this one and continue. */
1601
    if (auth_alias_only != NULL &&
1602
        *auth_alias_only == TRUE) {
4✔
1603
      c = find_config_next2(c, c->next, CONF_PARAM, "UserAlias", TRUE,
2✔
1604
        config_flags);
2✔
1605
      continue;
1606
    }
2✔
1607

1608
    /* At this point, we have found an "AuthAliasOnly off" config in
1609
     * c->parent->set (which means that we cannot use the UserAlias, and thus
1610
     * is_alias is set to false).  See if there's a UserAlias in the same
1611
     * config set.
1612
     */
1613

1614
    is_alias = FALSE;
1615

×
1616
    find_config_set_top(alias_config);
1617
    c = find_config_next2(c, c->next, CONF_PARAM, "UserAlias", TRUE,
×
1618
      config_flags);
×
1619

1620
    if (c != NULL &&
1621
        (strcmp(c->argv[0], "*") == 0 ||
×
1622
         strcmp(c->argv[0], *login_user) == 0)) {
×
1623
      is_alias = TRUE;
×
1624
      alias_config = c;
×
1625
    }
×
1626
  }
1627

1628
  /* At this point in time, c is guaranteed (if not null) to be pointing at
1629
   * a UserAlias config, either the original OR one found in the AuthAliasOnly
1630
   * config set.
1631
   */
1632
  if (c != NULL) {
1633
    *login_user = c->argv[1];
6✔
1634

2✔
1635
    /* If the alias is applied inside an <Anonymous> context, we have found
1636
     * our <Anonymous> section.
1637
     */
1638
    if (c->parent &&
1639
        c->parent->config_type == CONF_ANON) {
2✔
1640
      anon_config = c->parent;
×
1641

×
1642
    } else {
1643
      c = NULL;
1644
    }
6✔
1645
  }
1646

1647
  /* Next, search for an anonymous entry. */
1648
  if (anon_config == NULL) {
1649
    c = find_config(main_server->conf, CONF_ANON, NULL, FALSE);
×
1650

6✔
1651
  } else {
1652
    find_config_set_top(anon_config);
1653
    c = anon_config;
×
1654
  }
×
1655

1656
  /* If anon_config is null here but c is not null, then we may have found
1657
   * a candidate <Anonymous> section.  Let's examine it more closely.
1658
   */
1659
  if (c != NULL) {
1660
    config_rec *starting_c;
6✔
1661

1662
    starting_c = c;
1663
    do {
1664
      pr_signals_handle();
×
1665

×
1666
      config_anon_name = get_param_ptr(c->subset, "UserName", FALSE);
1667
      if (config_anon_name == NULL) {
×
1668
        config_anon_name = config_user_name;
×
1669
      }
×
1670

1671
      if (config_anon_name != NULL &&
1672
          strcmp(config_anon_name, *login_user) == 0) {
×
1673

×
1674
        /* We found our <Anonymous> section. */
1675
        anon_config = c;
1676

×
1677
        if (anon_name != NULL) {
1678
          *anon_name = config_anon_name;
×
1679
        }
×
1680
        break;
1681
      }
1682

1683
    } while ((c = find_config_next(c, c->next, CONF_ANON, NULL,
1684
      FALSE)) != NULL);
×
1685

×
1686
    c = starting_c;
1687
  }
1688

1689
  if (is_alias == FALSE) {
1690
    auth_alias_only = get_param_ptr(c ? c->subset : main_server->conf,
6✔
1691
      "AuthAliasOnly", FALSE);
2✔
1692

1693
    if (auth_alias_only != NULL &&
1694
        *auth_alias_only == TRUE) {
2✔
1695
      if (c != NULL &&
1✔
1696
          c->config_type == CONF_ANON) {
1✔
1697
        c = NULL;
×
1698

1699
      } else {
1700
        *login_user = NULL;
1701
      }
1✔
1702

1703
      /* Note: We only need to look for AuthAliasOnly in main_server IFF
1704
       * c is NOT null.  If c IS null, then we will already have looked up
1705
       * AuthAliasOnly in main_server above.
1706
       */
1707
      if (c != NULL) {
1708
        auth_alias_only = get_param_ptr(main_server->conf, "AuthAliasOnly",
1✔
1709
          FALSE);
×
1710
      }
1711

1712
      if (login_user != NULL &&
1713
          auth_alias_only != NULL &&
1✔
1714
          *auth_alias_only == TRUE) {
1✔
1715
        *login_user = NULL;
1✔
1716
      }
1✔
1717

1718
      if ((login_user == NULL || anon_config == NULL) &&
1719
          anon_name != NULL) {
1✔
1720
        *anon_name = NULL;
1721
      }
1✔
1722
    }
1723

1724
  } else {
1725
    config_rec *alias_parent_config = NULL;
1726

4✔
1727
    /* We have found a matching UserAlias for the USER name sent by the client.
1728
     * But we need to properly handle any AuthAliasOnly directives in that
1729
     * config as well (Bug#2070).
1730
     */
1731
    if (alias_config != NULL) {
1732
      alias_parent_config = alias_config->parent;
4✔
1733
    }
4✔
1734

1735
    auth_alias_only = get_param_ptr(alias_parent_config ?
1736
      alias_parent_config->subset : main_server->conf, "AuthAliasOnly", FALSE);
6✔
1737

2✔
1738
    if (auth_alias_only != NULL &&
1739
        *auth_alias_only == TRUE) {
4✔
1740
      if (alias_parent_config != NULL &&
4✔
1741
          alias_parent_config->config_type == CONF_ANON) {
4✔
1742
        anon_config = alias_parent_config;
2✔
1743
      }
2✔
1744
    }
1745
  }
1746

1747
  if (anon_config != NULL) {
1748
    config_user_name = get_param_ptr(anon_config->subset, "UserName", FALSE);
6✔
1749
    if (config_user_name != NULL &&
2✔
1750
        real_user != NULL) {
2✔
1751
      *real_user = config_user_name;
1752
    }
1✔
1753
  }
1754

1755
  return anon_config;
1756
}
6✔
1757

1758
int pr_auth_banned_by_ftpusers(xaset_t *ctx, const char *user) {
1759
  int res = FALSE;
4✔
1760
  unsigned char *use_ftp_users;
4✔
1761

4✔
1762
  if (user == NULL) {
1763
    return res;
4✔
1764
  }
1765

1766
  use_ftp_users = get_param_ptr(ctx, "UseFtpUsers", FALSE);
1767
  if (use_ftp_users == NULL ||
2✔
1768
      *use_ftp_users == TRUE) {
2✔
1769
    FILE *fh = NULL;
1✔
1770
    char buf[512];
1✔
1771
    int xerrno;
1✔
1772

1✔
1773
    PRIVS_ROOT
1774
    fh = fopen(PR_FTPUSERS_PATH, "r");
1✔
1775
    xerrno = errno;
1✔
1776
    PRIVS_RELINQUISH
1✔
1777

1✔
1778
    if (fh == NULL) {
1779
      pr_trace_msg(trace_channel, 14,
1✔
1780
        "error opening '%s' for checking user '%s': %s", PR_FTPUSERS_PATH,
1✔
1781
        user, strerror(xerrno));
1782
      return res;
1783
    }
1✔
1784

1785
    memset(buf, '\0', sizeof(buf));
1786

×
1787
    while (fgets(buf, sizeof(buf)-1, fh) != NULL) {
1788
      char *ptr;
×
1789

×
1790
      pr_signals_handle();
1791

×
1792
      buf[sizeof(buf)-1] = '\0';
1793
      CHOP(buf);
×
1794

×
1795
      ptr = buf;
1796
      while (PR_ISSPACE(*ptr) && *ptr) {
×
1797
        ptr++;
×
1798
      }
×
1799

1800
      if (!*ptr ||
1801
          *ptr == '#') {
×
1802
        continue;
1803
      }
×
1804

1805
      if (strcmp(ptr, user) == 0 ) {
1806
        res = TRUE;
×
1807
        break;
1808
      }
1809

1810
      memset(buf, '\0', sizeof(buf));
1811
    }
×
1812

1813
    fclose(fh);
1814
  }
×
1815

1816
  return res;
1817
}
1818

1819
int pr_auth_is_valid_shell(xaset_t *ctx, const char *shell) {
1820
  int res = TRUE;
5✔
1821
  unsigned char *require_valid_shell;
5✔
1822

5✔
1823
  if (shell == NULL) {
1824
    return res;
5✔
1825
  }
1826

1827
  require_valid_shell = get_param_ptr(ctx, "RequireValidShell", FALSE);
1828

3✔
1829
  if (require_valid_shell == NULL ||
1830
      *require_valid_shell == TRUE) {
3✔
1831
    FILE *fh = NULL;
1✔
1832
    char buf[256];
2✔
1833

2✔
1834
    fh = fopen(PR_VALID_SHELL_PATH, "r");
1835
    if (fh == NULL) {
2✔
1836
      return res;
2✔
1837
    }
×
1838

1839
    res = FALSE;
1840
    memset(buf, '\0', sizeof(buf));
2✔
1841

2✔
1842
    while (fgets(buf, sizeof(buf)-1, fh) != NULL) {
1843
      pr_signals_handle();
12✔
1844

11✔
1845
      buf[sizeof(buf)-1] = '\0';
1846
      CHOP(buf);
11✔
1847

11✔
1848
      if (strcmp(buf, shell) == 0) {
1849
        res = TRUE;
11✔
1850
        break;
1851
      }
1852

1853
      memset(buf, '\0', sizeof(buf));
1854
    }
12✔
1855

1856
    fclose(fh);
1857
  }
2✔
1858

1859
  return res;
1860
}
1861

1862
int pr_auth_chroot(const char *path) {
1863
  int res, xerrno = 0;
5✔
1864
  time_t now;
5✔
1865
  char *tz = NULL;
5✔
1866
  const char *default_tz;
5✔
1867
  pool *tmp_pool;
5✔
1868
  pr_error_t *err = NULL;
5✔
1869

5✔
1870
  if (path == NULL) {
1871
    errno = EINVAL;
5✔
1872
    return -1;
1✔
1873
  }
1✔
1874

1875
#if defined(__GLIBC__) && \
1876
    defined(__GLIBC_MINOR__) && \
1877
    __GLIBC__ == 2 && __GLIBC_MINOR__ >= 3
1878
  default_tz = tzname[0];
1879
#else
4✔
1880
  /* Per the tzset(3) man page, this should be the assumed default. */
1881
  default_tz = ":/etc/localtime";
1882
#endif
1883

1884
  tz = pr_env_get(session.pool, "TZ");
1885
  if (tz == NULL) {
4✔
1886
    if (pr_env_set(session.pool, "TZ", pstrdup(permanent_pool,
4✔
1887
        default_tz)) < 0) {
3✔
1888
      pr_log_debug(DEBUG0, "error setting TZ environment variable to "
1889
        "'%s': %s", default_tz, strerror(errno));
2✔
1890

2✔
1891
    } else {
1892
      pr_log_debug(DEBUG10, "set TZ environment variable to '%s'", default_tz);
1893
    }
1✔
1894

1895
  } else {
1896
    pr_log_debug(DEBUG10, "TZ environment variable already set to '%s'", tz);
1897
  }
1✔
1898

1899
  pr_log_debug(DEBUG1, "Preparing to chroot to directory '%s'", path);
1900

4✔
1901
  /* Prepare for chroots and the ensuing timezone chicanery by calling
1902
   * our pr_localtime() routine now, which will cause libc (via localtime(2))
1903
   * to load the tzinfo data into memory, and hopefully retain it (Bug#3431).
1904
   */
1905
  tmp_pool = make_sub_pool(session.pool);
1906
  now = time(NULL);
4✔
1907
  (void) pr_localtime(tmp_pool, &now);
4✔
1908

4✔
1909
  pr_event_generate("core.chroot", path);
1910

4✔
1911
  PRIVS_ROOT
1912
  res = pr_fsio_chroot_with_error(tmp_pool, path, &err);
4✔
1913
  xerrno = errno;
4✔
1914
  PRIVS_RELINQUISH
4✔
1915

4✔
1916
  if (res < 0) {
1917
    pr_error_set_where(err, NULL, __FILE__, __LINE__ - 5);
4✔
1918
    pr_error_set_why(err, pstrcat(tmp_pool, "chroot to directory '", path,
4✔
1919
      "'", NULL));
4✔
1920

1921
    if (err != NULL) {
1922
      pr_log_pri(PR_LOG_ERR, "%s", pr_error_strerror(err, 0));
4✔
1923
      pr_error_destroy(err);
×
1924
      err = NULL;
×
1925

×
1926
    } else {
1927
      pr_log_pri(PR_LOG_ERR, "chroot to '%s' failed for user '%s': %s", path,
1928
        session.user ? session.user : "(unknown)", strerror(xerrno));
8✔
1929
    }
4✔
1930

1931
    destroy_pool(tmp_pool);
1932
    errno = xerrno;
4✔
1933
    return -1;
4✔
1934
  }
4✔
1935

1936
  pr_log_debug(DEBUG1, "Environment successfully chroot()ed");
1937
  destroy_pool(tmp_pool);
×
1938
  return 0;
×
1939
}
×
1940

1941
int set_groups(pool *p, gid_t primary_gid, array_header *suppl_gids) {
1942
  int res = 0;
3✔
1943
  pool *tmp_pool = NULL;
3✔
1944

3✔
1945
#ifdef HAVE_SETGROUPS
1946
  register unsigned int i = 0;
1947
  gid_t *gids = NULL, *proc_gids = NULL;
3✔
1948
  size_t ngids = 0, nproc_gids = 0;
3✔
1949
  char *strgids = "";
3✔
1950
  int have_root_privs = TRUE;
3✔
1951

3✔
1952
  /* First, check to see whether we even CAN set the process GIDs, which
1953
   * requires root privileges.
1954
   */
1955
  if (getuid() != PR_ROOT_UID) {
1956
    have_root_privs = FALSE;
3✔
1957
  }
×
1958

1959
  if (have_root_privs == FALSE) {
1960
    pr_trace_msg(trace_channel, 3,
3✔
1961
      "unable to set groups due to lack of root privs");
×
1962
    errno = ENOSYS;
1963
    return -1;
×
1964
  }
×
1965

1966
  /* sanity check */
1967
  if (p == NULL ||
1968
      suppl_gids == NULL) {
3✔
1969

3✔
1970
# ifndef PR_DEVEL_COREDUMP
1971
    /* Set the primary GID of the process. */
1972
    res = setgid(primary_gid);
1973
# endif /* PR_DEVEL_COREDUMP */
3✔
1974

1975
    return res;
1976
  }
3✔
1977

1978
  ngids = suppl_gids->nelts;
1979
  gids = suppl_gids->elts;
×
1980

×
1981
  if (ngids == 0 ||
1982
      gids == NULL) {
×
1983
    /* No supplemental GIDs to process. */
×
1984

1985
# ifndef PR_DEVEL_COREDUMP
1986
    /* Set the primary GID of the process. */
1987
    res = setgid(primary_gid);
1988
# endif /* PR_DEVEL_COREDUMP */
×
1989

1990
    return res;
1991
  }
×
1992

1993
  tmp_pool = make_sub_pool(p);
1994
  pr_pool_tag(tmp_pool, "set_groups() tmp pool");
×
1995

×
1996
  proc_gids = pcalloc(tmp_pool, sizeof(gid_t) * (ngids));
1997

×
1998
  /* Note: the list of supplemental GIDs may contain duplicates.  Sort
1999
   * through the list and keep only the unique IDs - this should help avoid
2000
   * running into the NGROUPS limit when possible.  This algorithm may slow
2001
   * things down some; optimize it if/when possible.
2002
   */
2003
  proc_gids[nproc_gids++] = gids[0];
2004

×
2005
  for (i = 1; i < ngids; i++) {
2006
    register unsigned int j = 0;
×
2007
    unsigned char skip_gid = FALSE;
2008

×
2009
    /* This duplicate ID search only needs to be done after the first GID
2010
     * in the given list is examined, as the first GID cannot be a duplicate.
2011
     */
2012
    for (j = 0; j < nproc_gids; j++) {
2013
      if (proc_gids[j] == gids[i]) {
×
2014
        skip_gid = TRUE;
×
2015
        break;
2016
      }
2017
    }
2018

2019
    if (!skip_gid) {
2020
      proc_gids[nproc_gids++] = gids[i];
×
2021
    }
×
2022
  }
2023

2024
  for (i = 0; i < nproc_gids; i++) {
2025
    char buf[64];
×
2026
    pr_snprintf(buf, sizeof(buf)-1, "%lu", (unsigned long) proc_gids[i]);
×
2027
    buf[sizeof(buf)-1] = '\0';
×
2028

×
2029
    strgids = pstrcat(p, strgids, i != 0 ? ", " : "", buf, NULL);
2030
  }
×
2031

2032
  pr_log_debug(DEBUG10, "setting group %s: %s", nproc_gids == 1 ? "ID" : "IDs",
2033
    strgids);
×
2034

2035
  /* Set the supplemental groups. */
2036
  res = setgroups(nproc_gids, proc_gids);
2037
  if (res < 0) {
×
2038
    int xerrno = errno;
×
2039

×
2040
    destroy_pool(tmp_pool);
2041

×
2042
    errno = xerrno;
2043
    return res;
×
2044
  }
×
2045
#endif /* !HAVE_SETGROUPS */
2046

2047
#ifndef PR_DEVEL_COREDUMP
2048
  /* Set the primary GID of the process. */
2049
  res = setgid(primary_gid);
2050
  if (res < 0) {
×
2051
    int xerrno = errno;
×
2052

×
2053
    if (tmp_pool != NULL) {
2054
      destroy_pool(tmp_pool);
×
2055
    }
×
2056

2057
    errno = xerrno;
2058
    return res;
×
2059
  }
×
2060
#endif /* PR_DEVEL_COREDUMP */
2061

2062
  if (tmp_pool != NULL) {
2063
    destroy_pool(tmp_pool);
×
2064
  }
×
2065

2066
  return res;
2067
}
2068

2069
void pr_auth_cache_clear(void) {
2070
  if (auth_tab != NULL) {
41✔
2071
    pr_table_empty(auth_tab);
41✔
2072
    pr_table_free(auth_tab);
×
2073
    auth_tab = NULL;
×
2074
  }
×
2075

2076
  if (uid_tab != NULL) {
2077
    pr_table_empty(uid_tab);
41✔
2078
    pr_table_free(uid_tab);
×
2079
    uid_tab = NULL;
×
2080
  }
×
2081

2082
  if (user_tab != NULL) {
2083
    pr_table_empty(user_tab);
41✔
2084
    pr_table_free(user_tab);
×
2085
    user_tab = NULL;
×
2086
  }
×
2087

2088
  if (gid_tab != NULL) {
2089
    pr_table_empty(gid_tab);
41✔
2090
    pr_table_free(gid_tab);
×
2091
    gid_tab = NULL;
×
2092
  }
×
2093

2094
  if (group_tab != NULL) {
2095
    pr_table_empty(group_tab);
41✔
2096
    pr_table_free(group_tab);
1✔
2097
    group_tab = NULL;
1✔
2098
  }
1✔
2099
}
2100

41✔
2101
int pr_auth_cache_set(int enable, unsigned int flags) {
2102
  if (enable != FALSE &&
93✔
2103
      enable != TRUE) {
93✔
2104
    errno = EINVAL;
2105
    return -1;
1✔
2106
  }
1✔
2107

2108
  if (enable == FALSE) {
2109
    if (flags & PR_AUTH_CACHE_FL_UID2NAME) {
92✔
2110
      auth_caching &= ~PR_AUTH_CACHE_FL_UID2NAME;
5✔
2111
      pr_trace_msg(trace_channel, 7, "UID-to-name caching (uidcache) disabled");
1✔
2112
    }
1✔
2113

2114
    if (flags & PR_AUTH_CACHE_FL_GID2NAME) {
2115
      auth_caching &= ~PR_AUTH_CACHE_FL_GID2NAME;
5✔
2116
      pr_trace_msg(trace_channel, 7, "GID-to-name caching (gidcache) disabled");
1✔
2117
    }
1✔
2118

2119
    if (flags & PR_AUTH_CACHE_FL_AUTH_MODULE) {
2120
      auth_caching &= ~PR_AUTH_CACHE_FL_AUTH_MODULE;
5✔
2121
      pr_trace_msg(trace_channel, 7,
1✔
2122
        "auth module caching (authcache) disabled");
1✔
2123
    }
2124

2125
    if (flags & PR_AUTH_CACHE_FL_NAME2UID) {
2126
      auth_caching &= ~PR_AUTH_CACHE_FL_NAME2UID;
5✔
2127
      pr_trace_msg(trace_channel, 7,
1✔
2128
        "name-to-UID caching (usercache) disabled");
1✔
2129
    }
2130

2131
    if (flags & PR_AUTH_CACHE_FL_NAME2GID) {
2132
      auth_caching &= ~PR_AUTH_CACHE_FL_NAME2GID;
5✔
2133
      pr_trace_msg(trace_channel, 7,
1✔
2134
        "name-to-GID caching (groupcache) disabled");
1✔
2135
    }
2136

2137
    if (flags & PR_AUTH_CACHE_FL_BAD_UID2NAME) {
2138
      auth_caching &= ~PR_AUTH_CACHE_FL_BAD_UID2NAME;
5✔
2139
      pr_trace_msg(trace_channel, 7,
2✔
2140
        "UID-to-name negative caching (uidcache) disabled");
2✔
2141
    }
2142

2143
    if (flags & PR_AUTH_CACHE_FL_BAD_GID2NAME) {
2144
      auth_caching &= ~PR_AUTH_CACHE_FL_BAD_GID2NAME;
5✔
2145
      pr_trace_msg(trace_channel, 7,
2✔
2146
        "GID-to-name negative caching (gidcache) disabled");
2✔
2147
    }
2148

2149
    if (flags & PR_AUTH_CACHE_FL_BAD_NAME2UID) {
2150
      auth_caching &= ~PR_AUTH_CACHE_FL_BAD_NAME2UID;
5✔
2151
      pr_trace_msg(trace_channel, 7,
2✔
2152
        "name-to-UID negative caching (usercache) disabled");
2✔
2153
    }
2154

2155
    if (flags & PR_AUTH_CACHE_FL_BAD_NAME2GID) {
2156
      auth_caching &= ~PR_AUTH_CACHE_FL_BAD_NAME2GID;
5✔
2157
      pr_trace_msg(trace_channel, 7,
2✔
2158
        "name-to-GID negative caching (groupcache) disabled");
2✔
2159
    }
2160
  }
2161

2162
  if (enable == TRUE) {
2163
    if (flags & PR_AUTH_CACHE_FL_UID2NAME) {
92✔
2164
      auth_caching |= PR_AUTH_CACHE_FL_UID2NAME;
87✔
2165
      pr_trace_msg(trace_channel, 7, "UID-to-name caching (uidcache) enabled");
80✔
2166
    }
80✔
2167

2168
    if (flags & PR_AUTH_CACHE_FL_GID2NAME) {
2169
      auth_caching |= PR_AUTH_CACHE_FL_GID2NAME;
87✔
2170
      pr_trace_msg(trace_channel, 7, "GID-to-name caching (gidcache) enabled");
80✔
2171
    }
80✔
2172

2173
    if (flags & PR_AUTH_CACHE_FL_AUTH_MODULE) {
2174
      auth_caching |= PR_AUTH_CACHE_FL_AUTH_MODULE;
87✔
2175
      pr_trace_msg(trace_channel, 7, "auth module caching (authcache) enabled");
87✔
2176
    }
87✔
2177

2178
    if (flags & PR_AUTH_CACHE_FL_NAME2UID) {
2179
      auth_caching |= PR_AUTH_CACHE_FL_NAME2UID;
87✔
2180
      pr_trace_msg(trace_channel, 7, "name-to-UID caching (usercache) enabled");
80✔
2181
    }
80✔
2182

2183
    if (flags & PR_AUTH_CACHE_FL_NAME2GID) {
2184
      auth_caching |= PR_AUTH_CACHE_FL_NAME2GID;
87✔
2185
      pr_trace_msg(trace_channel, 7,
80✔
2186
        "name-to-GID caching (groupcache) enabled");
80✔
2187
    }
2188

2189
    if (flags & PR_AUTH_CACHE_FL_BAD_UID2NAME) {
2190
      auth_caching |= PR_AUTH_CACHE_FL_BAD_UID2NAME;
87✔
2191
      pr_trace_msg(trace_channel, 7,
80✔
2192
        "UID-to-name negative caching (uidcache) enabled");
80✔
2193
    }
2194

2195
    if (flags & PR_AUTH_CACHE_FL_BAD_GID2NAME) {
2196
      auth_caching |= PR_AUTH_CACHE_FL_BAD_GID2NAME;
87✔
2197
      pr_trace_msg(trace_channel, 7,
80✔
2198
        "GID-to-name negative caching (gidcache) enabled");
80✔
2199
    }
2200

2201
    if (flags & PR_AUTH_CACHE_FL_BAD_NAME2UID) {
2202
      auth_caching |= PR_AUTH_CACHE_FL_BAD_NAME2UID;
87✔
2203
      pr_trace_msg(trace_channel, 7,
80✔
2204
        "name-to-UID negative caching (usercache) enabled");
80✔
2205
    }
2206

2207
    if (flags & PR_AUTH_CACHE_FL_BAD_NAME2GID) {
2208
      auth_caching |= PR_AUTH_CACHE_FL_BAD_NAME2GID;
87✔
2209
      pr_trace_msg(trace_channel, 7,
80✔
2210
        "name-to-GID negative caching (groupcache) enabled");
80✔
2211
    }
2212
  }
2213

2214
  return 0;
2215
}
2216

2217
int pr_auth_add_auth_only_module(const char *name) {
2218
  struct auth_module_elt *elt = NULL;
11✔
2219

11✔
2220
  if (name == NULL) {
2221
    errno = EINVAL;
11✔
2222
    return -1;
1✔
2223
  }
1✔
2224

2225
  if (auth_pool == NULL) {
2226
    auth_pool = make_sub_pool(permanent_pool);
10✔
2227
    pr_pool_tag(auth_pool, "Auth API");
×
2228
  }
×
2229

2230
  if (!(auth_caching & PR_AUTH_CACHE_FL_AUTH_MODULE)) {
2231
    /* We won't be using the auth-only module cache, so there's no need to
10✔
2232
     * accept this.
2233
     */
2234
    pr_trace_msg(trace_channel, 9, "not adding '%s' to the auth-only list: "
2235
      "caching of auth-only modules disabled", name);
×
2236
    return 0;
2237
  }
×
2238

2239
  if (auth_module_list == NULL) {
2240
    auth_module_list = xaset_create(auth_pool, NULL);
10✔
2241
  }
6✔
2242

2243
  /* Prevent duplicates; they could lead to a memory leak. */
2244
  for (elt = (struct auth_module_elt *) auth_module_list->xas_list; elt;
2245
      elt = elt->next) {
13✔
2246
    if (strcmp(elt->name, name) == 0) {
3✔
2247
      errno = EEXIST;
4✔
2248
      return -1;
1✔
2249
    }
1✔
2250
  }
2251

2252
  elt = pcalloc(auth_pool, sizeof(struct auth_module_elt));
2253
  elt->name = pstrdup(auth_pool, name);
9✔
2254

9✔
2255
  if (xaset_insert_end(auth_module_list, (xasetmember_t *) elt) < 0) {
2256
    pr_trace_msg(trace_channel, 1, "error adding '%s' to auth-only "
9✔
2257
      "module set: %s", name, strerror(errno));
×
2258
    return -1;
×
2259
  }
×
2260

2261
  pr_trace_msg(trace_channel, 5, "added '%s' to auth-only module list", name);
2262
  return 0;
9✔
2263
}
9✔
2264

2265
int pr_auth_clear_auth_only_modules(void) {
2266
  if (auth_module_list == NULL) {
7✔
2267
    errno = EPERM;
7✔
2268
    return -1;
1✔
2269
  }
1✔
2270

2271
  auth_module_list = NULL;
2272
  pr_trace_msg(trace_channel, 5, "cleared auth-only module list");
6✔
2273
  return 0;
6✔
2274
}
6✔
2275

2276
int pr_auth_remove_auth_only_module(const char *name) {
2277
  struct auth_module_elt *elt = NULL;
3✔
2278

3✔
2279
  if (name == NULL) {
2280
    errno = EINVAL;
3✔
2281
    return -1;
1✔
2282
  }
1✔
2283

2284
  if (!(auth_caching & PR_AUTH_CACHE_FL_AUTH_MODULE)) {
2285
    /* We won't be using the auth-only module cache, so there's no need to
2✔
2286
     * accept this.
2287
     */
2288
    pr_trace_msg(trace_channel, 9, "not removing '%s' from the auth-only list: "
2289
      "caching of auth-only modules disabled", name);
×
2290
    return 0;
2291
  }
×
2292

2293
  if (auth_module_list == NULL) {
2294
    pr_trace_msg(trace_channel, 9, "not removing '%s' from list: "
2✔
2295
      "empty auth-only module list", name);
1✔
2296
    errno = EPERM;
2297
    return -1;
1✔
2298
  }
1✔
2299

2300
  for (elt = (struct auth_module_elt *) auth_module_list->xas_list; elt;
2301
      elt = elt->next) {
1✔
2302
    if (strcmp(elt->name, name) == 0) {
×
2303
      if (xaset_remove(auth_module_list, (xasetmember_t *) elt) < 0) {
1✔
2304
        pr_trace_msg(trace_channel, 1, "error removing '%s' from auth-only "
1✔
2305
          "module set: %s", name, strerror(errno));
×
2306
        return -1;
×
2307
      }
×
2308

2309
      pr_trace_msg(trace_channel, 5, "removed '%s' from auth-only module list",
2310
        name);
1✔
2311
      return 0;
2312
    }
1✔
2313
  }
2314

2315
  errno = ENOENT;
2316
  return -1;
×
2317
}
×
2318

2319
const char *pr_auth_get_home(pool *p, const char *pw_dir) {
2320
  config_rec *c;
8✔
2321
  const char *home_dir;
8✔
2322

8✔
2323
  if (p == NULL ||
2324
      pw_dir == NULL) {
8✔
2325
    errno = EINVAL;
8✔
2326
    return NULL;
2✔
2327
  }
2✔
2328

2329
  home_dir = pw_dir;
2330

6✔
2331
  c = find_config(main_server->conf, CONF_PARAM, "RewriteHome", FALSE);
2332
  if (c == NULL) {
6✔
2333
    return home_dir;
6✔
2334
  }
2335

2336
  if (*((int *) c->argv[0]) == FALSE) {
2337
    return home_dir;
3✔
2338
  }
2339

2340
  /* Rather than using a cmd_rec dispatched to mod_rewrite's PRE_CMD handler,
2341
   * we use an approach with looser coupling to mod_rewrite: stash the
2342
   * home directory in the session.notes table, and generate an event.
2343
   * The mod_rewrite module will listen for this event, rewrite the stashed
2344
   * home directory as necessary, and be done.
2345
   *
2346
   * Thus after the event has been generated, we retrieve (and remove) the
2347
   * (possibly rewritten) home directory from the session.notes table.
2348
   * This approach means that other modules which wish to get involved
2349
   * in the rewriting of the home directory can also do so.
2350
   */
2351

2352
  (void) pr_table_remove(session.notes, "mod_auth.home-dir", NULL);
2353
  if (pr_table_add(session.notes, "mod_auth.home-dir",
2✔
2354
      pstrdup(p, pw_dir), 0) < 0) {
2✔
2355
    pr_trace_msg(trace_channel, 3,
2✔
2356
      "error stashing home dir in session.notes: %s", strerror(errno));
2✔
2357
    return home_dir;
1✔
2358
  }
1✔
2359

2360
  pr_event_generate("mod_auth.rewrite-home", NULL);
2361

1✔
2362
  home_dir = pr_table_get(session.notes, "mod_auth.home-dir", NULL);
2363
  if (home_dir == NULL) {
1✔
2364
    pr_trace_msg(trace_channel, 3,
1✔
2365
      "error getting home dir from session.notes: %s", strerror(errno));
×
2366
    return pw_dir;
×
2367
  }
×
2368

2369
  (void) pr_table_remove(session.notes, "mod_auth.home-dir", NULL);
2370

1✔
2371
  pr_log_debug(DEBUG9, "returning rewritten home directory '%s' for original "
2372
    "home directory '%s'", home_dir, pw_dir);
1✔
2373
  pr_trace_msg(trace_channel, 9, "returning rewritten home directory '%s' "
2374
    "for original home directory '%s'", home_dir, pw_dir);
1✔
2375

2376
  return home_dir;
2377
}
1✔
2378

2379
size_t pr_auth_set_max_password_len(pool *p, size_t len) {
2380
  size_t prev_len;
3✔
2381

3✔
2382
  prev_len = auth_max_passwd_len;
2383

3✔
2384
  if (len == 0) {
2385
    /* Restore default. */
3✔
2386
    auth_max_passwd_len = PR_TUNABLE_PASSWORD_MAX;
2387

2✔
2388
  } else {
2389
    auth_max_passwd_len = len;
2390
  }
1✔
2391

2392
  return prev_len;
2393
}
3✔
2394

2395
char *pr_auth_bcrypt(pool *p, const char *key, const char *salt,
2396
    size_t *hashed_len) {
7✔
2397
  char hashed[128], *res;
2398

7✔
2399
  if (p == NULL ||
2400
      key == NULL ||
7✔
2401
      salt == NULL ||
7✔
2402
      hashed_len == NULL) {
5✔
2403
    errno = EINVAL;
5✔
2404
    return NULL;
4✔
2405
  }
4✔
2406

2407
  if (bcrypt_hashpass(key, salt, hashed, sizeof(hashed)) != 0) {
2408
    return NULL;
3✔
2409
  }
2410

2411
  res = palloc(p, sizeof(hashed));
2412
  memcpy(res, hashed, sizeof(hashed));
1✔
2413
  *hashed_len = sizeof(hashed);
1✔
2414

1✔
2415
  return res;
2416
}
1✔
2417

2418
/* Internal use only.  To be called in the session process. */
2419
int init_auth(void) {
2420
  if (auth_pool == NULL) {
39✔
2421
    auth_pool = make_sub_pool(permanent_pool);
39✔
2422
    pr_pool_tag(auth_pool, "Auth API");
39✔
2423
  }
39✔
2424

2425
  return 0;
2426
}
39✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc