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

proftpd / proftpd / 14526507026

17 Apr 2025 11:25PM UTC coverage: 93.03% (+0.4%) from 92.667%
14526507026

push

github

51358 of 55206 relevant lines covered (93.03%)

234.02 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-2024 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, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20
 *
21
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
22
 * and other respective copyright holders give permission to link this program
23
 * with OpenSSL, and distribute the resulting executable, without including
24
 * the source code for OpenSSL in the source distribution.
25
 */
26

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

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

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

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

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

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

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

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

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

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

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

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

75
  return res;
7✔
76
}
77

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

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

85
  return res;
6✔
86
}
87

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

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

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

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

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

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

118
      namelen = strlen(name);
4✔
119

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

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

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

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

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

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

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

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

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

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

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

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

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

190
      namelen = strlen(name);
4✔
191

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

380
    va_start(args, argc);
104✔
381

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

386
    va_end(args);
104✔
387

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

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

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

400
  return c;
122✔
401
}
402

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

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

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

417
  iter_tab = start_tab;
418

419
  while (iter_tab) {
85✔
420
    pr_signals_handle();
70✔
421

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

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

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

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

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

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

446
      break;
447
    }
448

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

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

456
      break;
457
    }
458

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

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

473
  return mr;
474
}
475

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

577
  return res;
578
}
579

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

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

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

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

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

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

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

615
  return res;
616
}
617

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

923
    default:
924
      break;
925
  }
926

927
  return name;
24✔
928
}
929

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

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

943
  cmd = make_cmd(p, 2, name, pw);
9✔
944

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

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

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

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

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

967
          res = MODRET_HASDATA(mr) ? PR_AUTH_RFC2228_OK : PR_AUTH_OK;
2✔
968

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

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

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

984
          res = MODRET_ERROR(mr);
1✔
985

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

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

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

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

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

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

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

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

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

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

1036
  return res;
1037
}
1038

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

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

1051
  cmd = make_cmd(p, 1, name);
4✔
1052

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

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

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

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

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

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

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

1088
  return res;
1089
}
1090

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

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

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

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

1119
  cmd = make_cmd(p, 3, ciphertext_passwd, name, cleartext_passwd);
19✔
1120

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1199
  return res;
1200
}
1201

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

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

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

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

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

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

1228
  return res;
1229
}
1230

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

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

1244
  uidcache_create();
6✔
1245

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

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

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

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

1266
    have_name = TRUE;
1267
  }
1268

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

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

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

1284
  return res;
1285
}
1286

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

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

1300
  gidcache_create();
6✔
1301

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

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

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

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

1322
    have_name = TRUE;
1323
  }
1324

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

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

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

1340
  return res;
1341
}
1342

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

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

1356
  usercache_create();
22✔
1357

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

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

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

1368
      return res;
2✔
1369
    }
1370
  }
1371

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

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

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

1383
    have_id = TRUE;
1384

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

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

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

1399
  return res;
1400
}
1401

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

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

1415
  groupcache_create();
17✔
1416

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

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

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

1427
      return res;
2✔
1428
    }
1429
  }
1430

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

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

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

1442
    have_id = TRUE;
1443

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

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

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

1458
  return res;
1459
}
1460

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

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

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

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

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

1485
  mr = dispatch_auth(cmd, "getgroups", NULL);
3✔
1486

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

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

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

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

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

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

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

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

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

1535
  return res;
1536
}
1537

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

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

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

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

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

1576
      pr_signals_handle();
5✔
1577

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

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

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

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

1599
    pr_signals_handle();
2✔
1600

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

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

1615
    is_alias = FALSE;
×
1616

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

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

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

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

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

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

1652
  } else {
1653
    find_config_set_top(anon_config);
×
1654
    c = anon_config;
×
1655
  }
1656

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

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

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

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

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

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

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

1687
    c = starting_c;
1688
  }
1689

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

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

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

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

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

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

1725
  } else {
1726
    config_rec *alias_parent_config = NULL;
4✔
1727

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

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

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

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

1756
  return anon_config;
6✔
1757
}
1758

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

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

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

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

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

1786
    memset(buf, '\0', sizeof(buf));
×
1787

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

1791
      pr_signals_handle();
×
1792

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

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

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

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

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

1814
    fclose(fh);
×
1815
  }
1816

1817
  return res;
1818
}
1819

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

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

1828
  require_valid_shell = get_param_ptr(ctx, "RequireValidShell", FALSE);
3✔
1829

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

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

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

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

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

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

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

1857
    fclose(fh);
2✔
1858
  }
1859

1860
  return res;
1861
}
1862

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1976
    return res;
3✔
1977
  }
1978

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

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

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

1991
    return res;
×
1992
  }
1993

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

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

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

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

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

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

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

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

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

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

2041
    destroy_pool(tmp_pool);
×
2042

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

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

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

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

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

2067
  return res;
2068
}
2069

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2215
  return 0;
2216
}
2217

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2330
  home_dir = pw_dir;
6✔
2331

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

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

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

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

2361
  pr_event_generate("mod_auth.rewrite-home", NULL);
1✔
2362

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

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

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

2377
  return home_dir;
1✔
2378
}
2379

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

2383
  prev_len = auth_max_passwd_len;
3✔
2384

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

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

2393
  return prev_len;
3✔
2394
}
2395

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

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

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

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

2416
  return res;
1✔
2417
}
2418

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

2426
  return 0;
39✔
2427
}
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