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

proftpd / proftpd / 25508939015

07 May 2026 04:34PM UTC coverage: 92.632% (-0.4%) from 93.024%
25508939015

push

github

web-flow
Issue #2052: It is possible that some environment values come from user-supplied text, so we should always escape `%{env:...}` variables, too.

47243 of 51001 relevant lines covered (92.63%)

241.97 hits per line

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

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

24
/* Controls API routines */
25

26
#include "conf.h"
27
#include "privs.h"
28

29
#if defined(HAVE_UCRED_H)
30
# include <ucred.h>
31
#endif /* !HAVE_UCRED_H */
32

33
#if defined(HAVE_SYS_UCRED_H)
34
# include <sys/ucred.h>
35
#endif /* !HAVE_SYS_UCRED_H */
36

37
#if defined(HAVE_SYS_UIO_H)
38
# include <sys/uio.h>
39
#endif /* !HAVE_SYS_UIO_H */
40

41
#if defined(PR_USE_CTRLS)
42

43
#include "mod_ctrls.h"
44

45
#define CTRLS_REQ_ACTION_KEY        "action"
46
#define CTRLS_REQ_ARGS_KEY        "args"
47
#define CTRLS_RESP_STATUS_KEY        "status"
48
#define CTRLS_RESP_RESPS_KEY        "responses"
49

50
typedef struct ctrls_act_obj {
51
  struct ctrls_act_obj *prev, *next;
52
  pool *pool;
53
  unsigned int id;
54
  const char *action;
55
  const char *desc;
56
  const module *module;
57
  volatile unsigned int flags;
58
  int (*action_cb)(pr_ctrls_t *, int, char **);
59
} ctrls_action_t;
60

61
static unsigned char ctrls_blocked = FALSE;
62

63
static pool *ctrls_pool = NULL;
64
static ctrls_action_t *ctrls_action_list = NULL;
65

66
static pr_ctrls_t *ctrls_active_list = NULL;
67
static pr_ctrls_t *ctrls_free_list = NULL;
68

69
static int ctrls_use_isfifo = FALSE;
70

71
static const char *trace_channel = "ctrls";
72

73
/* lookup/lookup_next indices */
74
static ctrls_action_t *action_lookup_next = NULL;
75
static const char *action_lookup_action = NULL;
76
static module *action_lookup_module = NULL;
77

78
/* Logging */
79
static int ctrls_logfd = -1;
80

81
/* necessary prototypes */
82
static ctrls_action_t *ctrls_action_new(void);
83
static pr_ctrls_t *ctrls_lookup_action(module *, const char *, unsigned char);
84
static pr_ctrls_t *ctrls_lookup_next_action(module *, unsigned char);
85

86
static pr_ctrls_t *ctrls_prepare(ctrls_action_t *act) {
7✔
87
  pr_ctrls_t *ctrl = NULL;
7✔
88

89
  pr_block_ctrls();
90

91
  /* Get a blank ctrl object */
92
  ctrl = pr_ctrls_alloc();
7✔
93

94
  /* Fill in the fields from the action object. */
95
  ctrl->ctrls_id = act->id;
7✔
96
  ctrl->ctrls_module = act->module;
7✔
97
  ctrl->ctrls_action = act->action;
7✔
98
  ctrl->ctrls_desc = act->desc;
7✔
99
  ctrl->ctrls_cb = act->action_cb;
7✔
100
  ctrl->ctrls_flags = act->flags;
7✔
101

102
  /* Add this to the "in use" list */
103
  ctrl->ctrls_next = ctrls_active_list;
7✔
104
  ctrls_active_list = ctrl;
7✔
105

106
  pr_unblock_ctrls();
107
  return ctrl;
7✔
108
}
109

110
static ctrls_action_t *ctrls_action_new(void) {
12✔
111
  ctrls_action_t *act = NULL;
12✔
112
  pool *sub_pool = NULL;
12✔
113

114
  sub_pool = make_sub_pool(ctrls_pool);
12✔
115
  pr_pool_tag(sub_pool, "ctrls action subpool");
12✔
116

117
  act = pcalloc(sub_pool, sizeof(ctrls_action_t));
12✔
118
  act->pool = sub_pool;
12✔
119

120
  return act;
12✔
121
}
122

123
pr_ctrls_t *pr_ctrls_alloc(void) {
27✔
124
  pr_ctrls_t *ctrl = NULL;
27✔
125

126
  /* Check for a free ctrl first */
127
  if (ctrls_free_list != NULL) {
27✔
128

129
    /* Take one from the top */
130
    ctrl = ctrls_free_list;
5✔
131
    ctrls_free_list = ctrls_free_list->ctrls_next;
5✔
132

133
    if (ctrls_free_list != NULL) {
5✔
134
      ctrls_free_list->ctrls_prev = NULL;
2✔
135
    }
136

137
  } else {
138
    /* Have to allocate a new one. */
139
    ctrl = (pr_ctrls_t *) pcalloc(ctrls_pool, sizeof(pr_ctrls_t));
22✔
140

141
    /* It's important that a new ctrl object have the retval initialized
142
     * to 1; this tells the Controls layer that it is "pending", not yet
143
     * handled.
144
     */
145
    ctrl->ctrls_cb_retval = PR_CTRLS_STATUS_PENDING;
22✔
146
  }
147

148
  return ctrl;
27✔
149
}
150

151
int pr_ctrls_free(pr_ctrls_t *ctrl) {
9✔
152
  if (ctrl == NULL) {
9✔
153
    errno = EINVAL;
1✔
154
    return -1;
1✔
155
  }
156

157
  /* Make sure that ctrls are blocked while we're doing this */
158
  pr_block_ctrls();
159

160
  /* Remove this object from the active list */
161
  if (ctrl->ctrls_prev != NULL) {
8✔
162
    ctrl->ctrls_prev->ctrls_next = ctrl->ctrls_next;
×
163

164
  } else {
165
    ctrls_active_list = ctrl->ctrls_next;
8✔
166
  }
167

168
  if (ctrl->ctrls_next != NULL) {
8✔
169
    ctrl->ctrls_next->ctrls_prev = ctrl->ctrls_prev;
2✔
170
  }
171

172
  /* Clear its fields, and add it to the free list */
173
  ctrl->ctrls_next = NULL;
8✔
174
  ctrl->ctrls_prev = NULL;
8✔
175
  ctrl->ctrls_id = 0;
8✔
176
  ctrl->ctrls_module = NULL;
8✔
177
  ctrl->ctrls_action = NULL;
8✔
178
  ctrl->ctrls_cb = NULL;
8✔
179
  ctrl->ctrls_cb_retval = PR_CTRLS_STATUS_PENDING;
8✔
180
  ctrl->ctrls_flags = 0;
8✔
181

182
  if (ctrl->ctrls_tmp_pool != NULL) {
8✔
183
    destroy_pool(ctrl->ctrls_tmp_pool);
4✔
184
    ctrl->ctrls_tmp_pool = NULL;
4✔
185
  }
186

187
  ctrl->ctrls_cb_args = NULL;
8✔
188
  ctrl->ctrls_cb_resps = NULL;
8✔
189
  ctrl->ctrls_data = NULL;
8✔
190

191
  ctrl->ctrls_next = ctrls_free_list;
8✔
192
  ctrls_free_list = ctrl;
8✔
193

194
  pr_unblock_ctrls();
195
  return 0;
8✔
196
}
197

198
int pr_ctrls_register(const module *mod, const char *action,
15✔
199
    const char *desc, int (*cb)(pr_ctrls_t *, int, char **)) {
200
  ctrls_action_t *act = NULL, *acti = NULL;
15✔
201
  unsigned int act_id = 0;
15✔
202

203
  /* sanity checks */
204
  if (action == NULL ||
30✔
205
      desc == NULL ||
28✔
206
      cb == NULL) {
207
    errno = EINVAL;
3✔
208
    return -1;
3✔
209
  }
210

211
  pr_trace_msg("ctrls", 3,
12✔
212
    "module '%s' registering handler for ctrl action '%s' (at %p)",
213
    mod ? mod->name : "(none)", action, cb);
214

215
  /* Block ctrls while we're doing this */
216
  pr_block_ctrls();
217

218
  /* Get a ctrl action object */
219
  act = ctrls_action_new();
12✔
220

221
  /* Randomly generate a unique random ID for this object */
222
  while (TRUE) {
223
    int have_id = FALSE;
12✔
224

225
    act_id = (unsigned int) pr_random_next(1L, RAND_MAX);
12✔
226

227
    /* Check the list for this ID */
228
    for (acti = ctrls_action_list; acti; acti = acti->next) {
16✔
229
      if (acti->id == act_id) {
4✔
230
        have_id = TRUE;
231
        break;
232
      }
233
    }
234

235
    if (have_id == FALSE) {
12✔
236
      break;
237
    }
238
  }
239

240
  act->next = NULL;
12✔
241
  act->id = act_id;
12✔
242
  act->action = pstrdup(ctrls_pool, action);
12✔
243
  act->desc = desc;
12✔
244
  act->module = mod;
12✔
245
  act->action_cb = cb;
12✔
246

247
  /* Add this to the list of "registered" actions */
248

249
  if (ctrls_action_list != NULL) {
12✔
250
    act->next = ctrls_action_list;
4✔
251
    ctrls_action_list->prev = act;
4✔
252
  }
253

254
  ctrls_action_list = act;
12✔
255

256
  pr_unblock_ctrls();
257
  return act_id;
12✔
258
}
259

260
int pr_ctrls_unregister(module *mod, const char *action) {
15✔
261
  ctrls_action_t *act = NULL, *next_act = NULL;
15✔
262
  unsigned char have_action = FALSE;
15✔
263

264
  /* Make sure that ctrls are blocked while we're doing this */
265
  pr_block_ctrls();
266

267
  for (act = ctrls_action_list; act != NULL; act = next_act) {
42✔
268
    next_act = act->next;
12✔
269

270
    if ((action == NULL || strcmp(act->action, action) == 0) &&
24✔
271
        (act->module == mod || mod == ANY_MODULE || mod == NULL)) {
15✔
272
      have_action = TRUE;
12✔
273

274
      /* Remove this object from the list of registered actions */
275
      if (act->prev != NULL) {
12✔
276
        act->prev->next = act->next;
×
277

278
      } else {
279
        ctrls_action_list = act->next;
12✔
280
      }
281

282
      if (act->next != NULL) {
12✔
283
        act->next->prev = act->prev;
4✔
284
      }
285

286
      pr_trace_msg("ctrls", 3,
12✔
287
        "module '%s' unregistering handler for ctrl action '%s'",
288
        mod ? mod->name : "(none)", act->action);
289

290
      /* Destroy this action. */
291
      destroy_pool(act->pool);
12✔
292
    }
293
  }
294

295
  pr_unblock_ctrls();
296

297
  if (have_action == FALSE) {
15✔
298
    errno = ENOENT;
7✔
299
    return -1;
7✔
300
  }
301

302
  return 0;
303
}
304

305
int pr_ctrls_add_arg(pr_ctrls_t *ctrl, char *ctrls_arg, size_t ctrls_arglen) {
11✔
306
  register unsigned int i;
307

308
  /* Sanity checks */
309
  if (ctrl == NULL ||
22✔
310
      ctrls_arg == NULL) {
11✔
311
    errno = EINVAL;
2✔
312
    return -1;
2✔
313
  }
314

315
  /* Scan for non-printable characters. */
316
  for (i = 0; i < ctrls_arglen; i++) {
26✔
317
    if (!PR_ISPRINT((int) ctrls_arg[i])) {
27✔
318
      errno = EPERM;
1✔
319
      return -1;
1✔
320
    }
321
  }
322

323
  /* Make sure the pr_ctrls_t has a temporary pool, from which the args will
324
   * be allocated.
325
   */
326
  if (ctrl->ctrls_tmp_pool == NULL) {
8✔
327
    ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
6✔
328
    pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool");
6✔
329
  }
330

331
  if (ctrl->ctrls_cb_args == NULL) {
8✔
332
    ctrl->ctrls_cb_args = make_array(ctrl->ctrls_tmp_pool, 0, sizeof(char *));
6✔
333
  }
334

335
  /* Add the given argument */
336
  *((char **) push_array(ctrl->ctrls_cb_args)) = pstrndup(ctrl->ctrls_tmp_pool,
8✔
337
    ctrls_arg, ctrls_arglen);
338

339
  return 0;
8✔
340
}
341

342
int pr_ctrls_copy_args(pr_ctrls_t *src_ctrl, pr_ctrls_t *dst_ctrl) {
6✔
343
  if (src_ctrl == NULL ||
12✔
344
      dst_ctrl == NULL ||
10✔
345
      src_ctrl == dst_ctrl) {
346
    errno = EINVAL;
3✔
347
    return -1;
3✔
348
  }
349

350
  /* If source ctrl has no ctrls_cb_args member, there's nothing to be
351
   * done.
352
   */
353
  if (src_ctrl->ctrls_cb_args == NULL) {
3✔
354
    return 0;
355
  }
356

357
  /* Make sure the pr_ctrls_t has a temporary pool, from which the args will
358
   * be allocated.
359
   */
360
  if (dst_ctrl->ctrls_tmp_pool == NULL) {
2✔
361
    dst_ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
2✔
362
    pr_pool_tag(dst_ctrl->ctrls_tmp_pool, "ctrls tmp pool");
2✔
363
  }
364

365
  /* Overwrite any existing dst_ctrl->ctrls_cb_args.  This is OK, as
366
   * the ctrl will be reset (cleared) once it has been processed.
367
   */
368
  dst_ctrl->ctrls_cb_args = copy_array(dst_ctrl->ctrls_tmp_pool,
2✔
369
    src_ctrl->ctrls_cb_args);
2✔
370

371
  return 0;
2✔
372
}
373

374
int pr_ctrls_copy_resps(pr_ctrls_t *src_ctrl, pr_ctrls_t *dst_ctrl) {
6✔
375
  if (src_ctrl == NULL ||
12✔
376
      dst_ctrl == NULL ||
10✔
377
      src_ctrl == dst_ctrl) {
378
    errno = EINVAL;
3✔
379
    return -1;
3✔
380
  }
381

382
  /* The source ctrl must have a ctrls_cb_resps member, and the destination
383
   * ctrl must not have a ctrls_cb_resps member.
384
   */
385
  if (src_ctrl->ctrls_cb_resps == NULL ||
5✔
386
      dst_ctrl->ctrls_cb_resps != NULL) {
2✔
387
    errno = EPERM;
2✔
388
    return -1;
2✔
389
  }
390

391
  dst_ctrl->ctrls_cb_resps = copy_array(dst_ctrl->ctrls_tmp_pool,
1✔
392
    src_ctrl->ctrls_cb_resps);
393

394
  return 0;
1✔
395
}
396

397
int pr_ctrls_add_response(pr_ctrls_t *ctrl, const char *fmt, ...) {
14✔
398
  char buf[PR_TUNABLE_BUFFER_SIZE] = {'\0'};
14✔
399
  va_list resp;
400

401
  /* Sanity check */
402
  if (ctrl == NULL ||
28✔
403
      fmt == NULL) {
14✔
404
    errno = EINVAL;
2✔
405
    return -1;
2✔
406
  }
407

408
  /* Make sure the pr_ctrls_t has a temporary pool, from which the responses
409
   * will be allocated
410
   */
411
  if (ctrl->ctrls_tmp_pool == NULL) {
12✔
412
    ctrl->ctrls_tmp_pool = make_sub_pool(ctrls_pool);
6✔
413
    pr_pool_tag(ctrl->ctrls_tmp_pool, "ctrls tmp pool");
6✔
414
  }
415

416
  if (ctrl->ctrls_cb_resps == NULL) {
12✔
417
    ctrl->ctrls_cb_resps = make_array(ctrl->ctrls_tmp_pool, 0,
7✔
418
      sizeof(char *));
419
  }
420

421
  /* Affix the message */
422
  va_start(resp, fmt);
12✔
423
  pr_vsnprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), fmt, resp);
12✔
424
  va_end(resp);
12✔
425

426
  buf[sizeof(buf) - 1] = '\0';
12✔
427

428
  /* add the given response */
429
  *((char **) push_array(ctrl->ctrls_cb_resps)) =
24✔
430
    pstrdup(ctrl->ctrls_tmp_pool, buf);
24✔
431

432
  return 0;
12✔
433
}
434

435
int pr_ctrls_flush_response(pr_ctrls_t *ctrl) {
5✔
436
  if (ctrl == NULL) {
5✔
437
    errno = EINVAL;
1✔
438
    return -1;
1✔
439
  }
440

441
  /* Make sure the callback(s) added responses */
442
  if (ctrl->ctrls_cb_resps != NULL) {
4✔
443
    int res;
444

445
    if (ctrl->ctrls_cl == NULL) {
3✔
446
      errno = EPERM;
1✔
447
      return -1;
1✔
448
    }
449

450
    res = pr_ctrls_send_response(ctrl->ctrls_tmp_pool, ctrl->ctrls_cl->cl_fd,
2✔
451
      ctrl->ctrls_cb_retval, ctrl->ctrls_cb_resps->nelts,
452
      (char **) ctrl->ctrls_cb_resps->elts);
2✔
453
    if (res < 0) {
2✔
454
      return -1;
455
    }
456
  }
457

458
  return 0;
459
}
460

461
static int ctrls_send_msg(pool *p, int fd, pr_json_object_t *json) {
7✔
462
  uint32_t msglen;
463
  int res, xerrno;
464
  char *msg;
465

466
  msg = pr_json_object_to_text(p, json, "");
7✔
467
  if (msg == NULL) {
7✔
468
    return -1;
469
  }
470

471
  msglen = strlen(msg);
7✔
472

473
  /* No interruptions. */
474
  pr_signals_block();
7✔
475

476
  res = write(fd, &msglen, sizeof(uint32_t));
7✔
477
  xerrno = errno;
7✔
478

479
  if ((size_t) res != sizeof(uint32_t)) {
7✔
480
    pr_signals_unblock();
3✔
481

482
    errno = xerrno;
3✔
483
    return -1;
3✔
484
  }
485

486
  while (TRUE) {
487
    res = write(fd, msg, msglen);
4✔
488
    xerrno = errno;
4✔
489

490
    if ((size_t) res != msglen) {
4✔
491
      if (xerrno == EAGAIN) {
×
492
        continue;
×
493
      }
494

495
      pr_signals_unblock();
×
496

497
      errno = xerrno;
×
498
      return -1;
×
499
    }
500

501
    break;
502
  }
503

504
  pr_signals_unblock();
4✔
505
  return 0;
4✔
506
}
507

508
int pr_ctrls_send_request(pool *p, int fd, const char *action,
6✔
509
    unsigned int argc, char **argv) {
510
  register unsigned int i;
511
  pool *tmp_pool;
512
  int res, xerrno;
513
  pr_json_object_t *json;
514
  pr_json_array_t *args;
515

516
  if (p == NULL ||
12✔
517
      fd < 0 ||
10✔
518
      action == NULL) {
519
    errno = EINVAL;
3✔
520
    return -1;
3✔
521
  }
522

523
  if (argc > 0 &&
6✔
524
      argv == NULL) {
3✔
525
    errno = EINVAL;
1✔
526
    return -1;
1✔
527
  }
528

529
  tmp_pool = make_sub_pool(p);
2✔
530
  pr_pool_tag(tmp_pool, "Controls API send_request pool");
2✔
531

532
  json = pr_json_object_alloc(tmp_pool);
2✔
533

534
  res = pr_json_object_set_string(tmp_pool, json, CTRLS_REQ_ACTION_KEY, action);
2✔
535
  xerrno = errno;
2✔
536

537
  if (res < 0) {
2✔
538
    pr_json_object_free(json);
×
539
    destroy_pool(tmp_pool);
×
540

541
    errno = xerrno;
×
542
    return -1;
×
543
  }
544

545
  args = pr_json_array_alloc(tmp_pool);
2✔
546

547
  for (i = 0; i < argc; i++) {
4✔
548
    res = pr_json_array_append_string(tmp_pool, args, argv[i]);
2✔
549
    xerrno = errno;
2✔
550

551
    if (res < 0) {
2✔
552
      pr_json_array_free(args);
×
553
      pr_json_object_free(json);
×
554
      destroy_pool(tmp_pool);
×
555

556
      errno = xerrno;
×
557
      return -1;
×
558
    }
559
  }
560

561
  res = pr_json_object_set_array(tmp_pool, json, CTRLS_REQ_ARGS_KEY, args);
2✔
562
  xerrno = errno;
2✔
563

564
  if (res < 0) {
2✔
565
    pr_json_array_free(args);
×
566
    pr_json_object_free(json);
×
567
    destroy_pool(tmp_pool);
×
568

569
    errno = xerrno;
×
570
    return -1;
×
571
  }
572

573
  res = ctrls_send_msg(tmp_pool, fd, json);
2✔
574
  xerrno = errno;
2✔
575

576
  pr_json_array_free(args);
2✔
577
  pr_json_object_free(json);
2✔
578
  destroy_pool(tmp_pool);
2✔
579

580
  errno = xerrno;
2✔
581
  return res;
2✔
582
}
583

584
int pr_ctrls_recv_request(pr_ctrls_cl_t *cl) {
15✔
585
  register int i = 0;
15✔
586
  pr_ctrls_t *ctrl = NULL, *next_ctrl = NULL;
15✔
587
  pool *tmp_pool = NULL;
15✔
588
  int nread, nreqargs = 0, res, xerrno;
15✔
589
  uint32_t msglen;
590
  char *msg = NULL, *reqaction = NULL;
15✔
591
  pr_json_object_t *json = NULL;
15✔
592
  pr_json_array_t *args = NULL;
15✔
593

594
  if (cl == NULL ||
29✔
595
      cl->cl_ctrls == NULL) {
14✔
596
    errno = EINVAL;
2✔
597
    return -1;
2✔
598
  }
599

600
  if (cl->cl_fd < 0) {
13✔
601
    errno = EBADF;
1✔
602
    return -1;
1✔
603
  }
604

605
  /* No interruptions */
606
  pr_signals_block();
12✔
607

608
  /* Read in the size of the message, as JSON text. */
609

610
  nread = read(cl->cl_fd, &msglen, sizeof(uint32_t));
24✔
611
  xerrno = errno;
12✔
612

613
  if (nread < 0) {
12✔
614
    pr_trace_msg(trace_channel, 3,
1✔
615
      "error reading %lu bytes of request message size: %s",
616
      sizeof(msglen), strerror(xerrno));
617
    pr_signals_unblock();
1✔
618

619
    errno = xerrno;
1✔
620
    return -1;
1✔
621
  }
622

623
  /* Watch for short reads. */
624
  if (nread != sizeof(uint32_t)) {
11✔
625
    (void) pr_trace_msg(trace_channel, 3,
1✔
626
      "short read (%d of %u bytes) of message size, unable to receive request",
627
      nread, (unsigned int) sizeof(uint32_t));
628
    pr_signals_unblock();
1✔
629
    errno = EPERM;
1✔
630
    return -1;
1✔
631
  }
632

633
  tmp_pool = make_sub_pool(cl->cl_pool);
10✔
634
  pr_pool_tag(tmp_pool, "Controls API recv_request pool");
10✔
635

636
  /* Allocate one byte for the terminating NUL. */
637
  msg = pcalloc(tmp_pool, msglen + 1);
10✔
638

639
  nread = read(cl->cl_fd, msg, msglen);
20✔
640
  xerrno = errno;
10✔
641

642
  if (nread < 0) {
10✔
643
    pr_trace_msg(trace_channel, 3,
×
644
      "error reading %lu bytes of request message: %s",
645
      (unsigned long) msglen, strerror(xerrno));
646
    destroy_pool(tmp_pool);
×
647
    pr_signals_unblock();
×
648

649
    errno = xerrno;
×
650
    return -1;
×
651
  }
652

653
  /* Watch for short reads. */
654
  if ((unsigned int) nread != msglen) {
10✔
655
    (void) pr_trace_msg(trace_channel, 3,
1✔
656
      "short read (%d of %u bytes) of message text, unable to receive request",
657
      nread, (unsigned int) msglen);
658
    destroy_pool(tmp_pool);
1✔
659
    pr_signals_unblock();
1✔
660
    errno = EPERM;
1✔
661
    return -1;
1✔
662
  }
663

664
  json = pr_json_object_from_text(tmp_pool, msg);
9✔
665
  xerrno = errno;
9✔
666

667
  if (json == NULL) {
9✔
668
    (void) pr_trace_msg(trace_channel, 3,
1✔
669
      "read invalid JSON message text ('%.*s' [%lu bytes]), unable to "
670
      "receive request: %s", (int) msglen, msg, (unsigned long) msglen,
671
      strerror(xerrno));
672
    destroy_pool(tmp_pool);
1✔
673
    pr_signals_unblock();
1✔
674

675
    errno = EINVAL;
1✔
676
    return -1;
1✔
677
  }
678

679
  res = pr_json_object_get_string(tmp_pool, json, CTRLS_REQ_ACTION_KEY,
8✔
680
    &reqaction);
681
  xerrno = errno;
8✔
682

683
  if (res < 0) {
8✔
684
    (void) pr_trace_msg(trace_channel, 3,
1✔
685
      "unable to read message action (%s), unable to receive request",
686
      strerror(xerrno));
687
    pr_json_object_free(json);
1✔
688
    destroy_pool(tmp_pool);
1✔
689
    pr_signals_unblock();
1✔
690

691
    errno = EINVAL;
1✔
692
    return -1;
1✔
693
  }
694

695
  res = pr_json_object_get_array(tmp_pool, json, CTRLS_REQ_ARGS_KEY, &args);
7✔
696
  xerrno = errno;
7✔
697

698
  if (res < 0) {
7✔
699
    (void) pr_trace_msg(trace_channel, 3,
1✔
700
      "unable to read message arguments (%s), unable to receive request",
701
      strerror(xerrno));
702
    pr_json_object_free(json);
1✔
703
    destroy_pool(tmp_pool);
1✔
704
    pr_signals_unblock();
1✔
705

706
    errno = EINVAL;
1✔
707
    return -1;
1✔
708
  }
709

710
  nreqargs = pr_json_array_count(args);
6✔
711
  pr_trace_msg(trace_channel, 19, "received request argc: %u", nreqargs);
6✔
712

713
  /* Find a matching action object, and use it to populate a ctrl object,
714
   * preparing the ctrl object for dispatching to the action handlers.
715
   */
716
  ctrl = ctrls_lookup_action(NULL, reqaction, TRUE);
12✔
717
  if (ctrl == NULL) {
6✔
718
    (void) pr_trace_msg(trace_channel, 3,
1✔
719
      "unknown action requested '%s', unable to receive request", reqaction);
720
    pr_json_array_free(args);
1✔
721
    pr_json_object_free(json);
1✔
722
    destroy_pool(tmp_pool);
1✔
723
    pr_signals_unblock();
1✔
724

725
    /* XXX This is where we could also add "did you mean" functionality. */
726
    errno = EINVAL;
1✔
727
    return -1;
1✔
728
  }
729

730
  pr_trace_msg(trace_channel, 19, "known action '%s' requested", reqaction);
5✔
731

732
  for (i = 0; i < nreqargs; i++) {
14✔
733
    size_t reqarglen = 0;
4✔
734
    char *reqarg = NULL;
4✔
735

736
    res = pr_json_array_get_string(tmp_pool, args, i, &reqarg);
4✔
737
    xerrno = errno;
4✔
738

739
    if (res < 0) {
4✔
740
      (void) pr_trace_msg(trace_channel, 3,
×
741
        "unable to read message argument #%u (%s), unable to receive request",
742
        i+1, strerror(xerrno));
743
      pr_json_array_free(args);
×
744
      pr_json_object_free(json);
×
745
      destroy_pool(tmp_pool);
×
746
      pr_signals_unblock();
×
747

748
      errno = EINVAL;
×
749
      return -1;
×
750
    }
751

752
    reqarglen = strlen(reqarg);
4✔
753
    res = pr_ctrls_add_arg(ctrl, reqarg, reqarglen);
4✔
754
    xerrno = errno;
4✔
755

756
    if (res < 0) {
4✔
757
      pr_trace_msg(trace_channel, 3,
×
758
        "error adding message argument #%u (%s): %s", i+1, reqarg,
759
        strerror(xerrno));
760
      pr_json_array_free(args);
×
761
      pr_json_object_free(json);
×
762
      destroy_pool(tmp_pool);
×
763
      pr_signals_unblock();
×
764

765
      errno = xerrno;
×
766
      return -1;
×
767
    }
768
  }
769

770
  /* Add this ctrls object to the client object. */
771
  *((pr_ctrls_t **) push_array(cl->cl_ctrls)) = ctrl;
5✔
772

773
  /* Set the flag that this control is ready to go */
774
  ctrl->ctrls_flags |= PR_CTRLS_FL_REQUESTED;
5✔
775
  ctrl->ctrls_cl = cl;
5✔
776

777
  /* Copy the populated ctrl object args to ctrl objects for all other
778
   * matching action objects.
779
   */
780
  next_ctrl = ctrls_lookup_next_action(NULL, TRUE);
5✔
781

782
  while (next_ctrl != NULL) {
11✔
783
    (void) pr_ctrls_copy_args(ctrl, next_ctrl);
1✔
784

785
    /* Add this ctrl object to the client object. */
786
    *((pr_ctrls_t **) push_array(cl->cl_ctrls)) = next_ctrl;
1✔
787

788
    /* Set the flag that this control is ready to go. */
789
    next_ctrl->ctrls_flags |= PR_CTRLS_FL_REQUESTED;
1✔
790
    next_ctrl->ctrls_cl = cl;
1✔
791

792
    next_ctrl = ctrls_lookup_next_action(NULL, TRUE);
1✔
793
  }
794

795
  pr_json_array_free(args);
5✔
796
  pr_json_object_free(json);
5✔
797
  destroy_pool(tmp_pool);
5✔
798
  pr_signals_unblock();
5✔
799

800
  return 0;
5✔
801
}
802

803
int pr_ctrls_send_response(pool *p, int fd, int status, unsigned int argc,
8✔
804
    char **argv) {
805
  register unsigned int i;
806
  pool *tmp_pool;
807
  int res, xerrno;
808
  pr_json_object_t *json;
809
  pr_json_array_t *resps;
810

811
  if (p == NULL ||
16✔
812
      fd < 0) {
8✔
813
    errno = EINVAL;
2✔
814
    return -1;
2✔
815
  }
816

817
  if (argc > 0 &&
12✔
818
      argv == NULL) {
6✔
819
    errno = EINVAL;
1✔
820
    return -1;
1✔
821
  }
822

823
  tmp_pool = make_sub_pool(p);
5✔
824
  pr_pool_tag(tmp_pool, "Controls API send_response pool");
5✔
825

826
  json = pr_json_object_alloc(tmp_pool);
5✔
827

828
  res = pr_json_object_set_number(tmp_pool, json, CTRLS_RESP_STATUS_KEY,
5✔
829
    (double) status);
830
  xerrno = errno;
5✔
831

832
  if (res < 0) {
5✔
833
    pr_json_object_free(json);
×
834
    destroy_pool(tmp_pool);
×
835

836
    errno = xerrno;
×
837
    return -1;
×
838
  }
839

840
  resps = pr_json_array_alloc(tmp_pool);
5✔
841

842
  for (i = 0; i < argc; i++) {
9✔
843
    res = pr_json_array_append_string(tmp_pool, resps, argv[i]);
4✔
844
    xerrno = errno;
4✔
845

846
    if (res < 0) {
4✔
847
      pr_json_array_free(resps);
×
848
      pr_json_object_free(json);
×
849
      destroy_pool(tmp_pool);
×
850

851
      errno = xerrno;
×
852
      return -1;
×
853
    }
854
  }
855

856
  res = pr_json_object_set_array(tmp_pool, json, CTRLS_RESP_RESPS_KEY, resps);
5✔
857
  xerrno = errno;
5✔
858

859
  if (res < 0) {
5✔
860
    pr_json_array_free(resps);
×
861
    pr_json_object_free(json);
×
862
    destroy_pool(tmp_pool);
×
863

864
    errno = xerrno;
×
865
    return -1;
×
866
  }
867

868
  res = ctrls_send_msg(tmp_pool, fd, json);
5✔
869
  xerrno = errno;
5✔
870

871
  pr_json_array_free(resps);
5✔
872
  pr_json_object_free(json);
5✔
873
  destroy_pool(tmp_pool);
5✔
874

875
  errno = xerrno;
5✔
876
  return res;
5✔
877
}
878

879
int pr_ctrls_recv_response(pool *p, int fd, int *status, char ***respargv) {
10✔
880
  register int i = 0;
10✔
881
  pool *tmp_pool;
882
  int nread, res, respargc = 0, xerrno;
10✔
883
  uint32_t msglen = 0;
10✔
884
  char *msg = NULL;
10✔
885
  pr_json_object_t *json = NULL;
10✔
886
  pr_json_array_t *resps = NULL;
10✔
887
  double dv;
888
  array_header *resparr = NULL;
10✔
889

890
  /* Sanity checks */
891
  if (p == NULL ||
20✔
892
      fd < 0 ||
18✔
893
      status == NULL) {
894
    errno = EINVAL;
3✔
895
    return -1;
3✔
896
  }
897

898
  /* No interruptions. */
899
  pr_signals_block();
7✔
900

901
  /* Read in the size of the message, as JSON text. */
902

903
  nread = read(fd, &msglen, sizeof(uint32_t));
7✔
904
  xerrno = errno;
7✔
905

906
  if (nread != sizeof(uint32_t)) {
7✔
907
    pr_signals_unblock();
1✔
908

909
    if (nread < 0) {
1✔
910
      (void) pr_trace_msg(trace_channel, 3,
×
911
        "error reading %u of response message size: %s",
912
        (unsigned int) sizeof(uint32_t), strerror(xerrno));
913
      errno = xerrno;
×
914
      return -1;
×
915
    }
916

917
    (void) pr_trace_msg(trace_channel, 3,
1✔
918
      "short read (%d of %u bytes) of response message, unable to receive "
919
      "response", nread, (unsigned int) sizeof(uint32_t));
920
    errno = EPERM;
1✔
921
    return -1;
1✔
922
  }
923

924
  tmp_pool = make_sub_pool(p);
6✔
925
  pr_pool_tag(tmp_pool, "Controls API recv_response pool");
6✔
926

927
  /* Allocate one byte for the terminating NUL. */
928
  msg = pcalloc(tmp_pool, msglen + 1);
6✔
929
  nread = read(fd, msg, msglen);
12✔
930
  xerrno = errno;
6✔
931

932
  if (nread < 0) {
6✔
933
    pr_trace_msg(trace_channel, 3,
×
934
      "error reading %lu bytes of response message: %s",
935
      (unsigned long) msglen, strerror(xerrno));
936
    destroy_pool(tmp_pool);
×
937
    pr_signals_unblock();
×
938

939
    errno = xerrno;
×
940
    return -1;
×
941
  }
942

943
  /* Watch for short reads. */
944
  if ((unsigned int) nread != msglen) {
6✔
945
    (void) pr_trace_msg(trace_channel, 3,
1✔
946
      "short read (%d of %u bytes) of message text, unable to receive response",
947
      nread, (unsigned int) msglen);
948
    destroy_pool(tmp_pool);
1✔
949
    pr_signals_unblock();
1✔
950

951
    errno = EPERM;
1✔
952
    return -1;
1✔
953
  }
954

955
  json = pr_json_object_from_text(tmp_pool, msg);
5✔
956
  xerrno = errno;
5✔
957

958
  if (json == NULL) {
5✔
959
    (void) pr_trace_msg(trace_channel, 3,
1✔
960
      "read invalid JSON message text ('%.*s' [%lu bytes]), unable to "
961
      "receive response: %s", (int) msglen, msg, (unsigned long) msglen,
962
      strerror(xerrno));
963
    destroy_pool(tmp_pool);
1✔
964
    pr_signals_unblock();
1✔
965

966
    errno = EINVAL;
1✔
967
    return -1;
1✔
968
  }
969

970
  res = pr_json_object_get_number(tmp_pool, json, CTRLS_RESP_STATUS_KEY, &dv);
4✔
971
  xerrno = errno;
4✔
972

973
  if (res < 0) {
4✔
974
    (void) pr_trace_msg(trace_channel, 3,
1✔
975
      "unable to read response status (%s), unable to receive response",
976
      strerror(xerrno));
977
    pr_json_object_free(json);
1✔
978
    destroy_pool(tmp_pool);
1✔
979
    pr_signals_unblock();
1✔
980

981
    errno = EINVAL;
1✔
982
    return -1;
1✔
983
  }
984

985
  *status = (int) dv;
3✔
986
  pr_trace_msg(trace_channel, 19, "received response status: %d", *status);
3✔
987

988
  res = pr_json_object_get_array(tmp_pool, json, CTRLS_RESP_RESPS_KEY, &resps);
3✔
989
  xerrno = errno;
3✔
990

991
  if (res < 0) {
3✔
992
    (void) pr_trace_msg(trace_channel, 3,
1✔
993
      "unable to read message responses (%s), unable to receive response",
994
      strerror(xerrno));
995
    pr_json_object_free(json);
1✔
996
    destroy_pool(tmp_pool);
1✔
997
    pr_signals_unblock();
1✔
998

999
    errno = EINVAL;
1✔
1000
    return -1;
1✔
1001
  }
1002

1003
  respargc = pr_json_array_count(resps);
2✔
1004
  pr_trace_msg(trace_channel, 19, "received response argc: %u", respargc);
2✔
1005

1006
  resparr = make_array(p, 0, sizeof(char *));
2✔
1007

1008
  /* Read each response, and add it to the array */
1009
  for (i = 0; i < respargc; i++) {
3✔
1010
    char *resp = NULL;
2✔
1011

1012
    /* TODO: Handle other response types, such as arrays or objects, for
1013
     * more complex responses.  Think of an action that dumps the memory
1014
     * pools, for example.
1015
     */
1016
    res = pr_json_array_get_string(tmp_pool, resps, i, &resp);
2✔
1017
    xerrno = errno;
2✔
1018

1019
    if (res < 0) {
2✔
1020
      (void) pr_trace_msg(trace_channel, 3,
1✔
1021
        "unable to read message response #%u (%s), unable to receive response",
1022
        i+1, strerror(xerrno));
1023
      pr_json_array_free(resps);
1✔
1024
      pr_json_object_free(json);
1✔
1025
      destroy_pool(tmp_pool);
1✔
1026
      pr_signals_unblock();
1✔
1027

1028
      errno = EINVAL;
1✔
1029
      return -1;
1✔
1030
    }
1031

1032
    *((char **) push_array(resparr)) = pstrdup(p, resp);
1✔
1033
  }
1034

1035
  if (respargv != NULL) {
1✔
1036
    *respargv = ((char **) resparr->elts);
×
1037
  }
1038

1039
  pr_json_array_free(resps);
1✔
1040
  pr_json_object_free(json);
1✔
1041
  destroy_pool(tmp_pool);
1✔
1042
  pr_signals_unblock();
1✔
1043

1044
  return respargc;
1✔
1045
}
1046

1047
static pr_ctrls_t *ctrls_lookup_action(module *mod, const char *action,
×
1048
    unsigned char skip_disabled) {
1049

1050
  /* (Re)set the current indices */
1051
  action_lookup_next = ctrls_action_list;
7✔
1052
  action_lookup_action = action;
7✔
1053
  action_lookup_module = mod;
7✔
1054

1055
  /* Wrapper around ctrls_lookup_next_action() */
1056
  return ctrls_lookup_next_action(mod, skip_disabled);
7✔
1057
}
1058

1059
static pr_ctrls_t *ctrls_lookup_next_action(module *mod,
13✔
1060
    unsigned char skip_disabled) {
1061
  register ctrls_action_t *act = NULL;
13✔
1062

1063
  /* Sanity check */
1064
  if (action_lookup_action == NULL) {
13✔
1065
    errno = EINVAL;
×
1066
    return NULL;
×
1067
  }
1068

1069
  if (mod != action_lookup_module) {
13✔
1070
    return ctrls_lookup_action(mod, action_lookup_action, skip_disabled);
×
1071
  }
1072

1073
  for (act = action_lookup_next; act; act = act->next) {
13✔
1074
    if (skip_disabled && (act->flags & PR_CTRLS_ACT_DISABLED)) {
7✔
1075
      continue;
×
1076
    }
1077

1078
    if (strcmp(act->action, action_lookup_action) == 0 &&
14✔
1079
        (act->module == mod || mod == ANY_MODULE || mod == NULL)) {
13✔
1080
      action_lookup_next = act->next;
7✔
1081

1082
      /* Use this action object to prepare a ctrl object. */
1083
      return ctrls_prepare(act);
7✔
1084
    }
1085
  }
1086

1087
  return NULL;
1088
}
1089

1090
int pr_get_registered_actions(pr_ctrls_t *ctrl, int flags) {
7✔
1091
  register ctrls_action_t *act = NULL;
7✔
1092
  int count = 0;
7✔
1093

1094
  if (ctrl == NULL) {
7✔
1095
    errno = EINVAL;
1✔
1096
    return -1;
1✔
1097
  }
1098

1099
  /* Are ctrls blocked? */
1100
  if (ctrls_blocked == TRUE) {
6✔
1101
    errno = EPERM;
1✔
1102
    return -1;
1✔
1103
  }
1104

1105
  for (act = ctrls_action_list; act; act = act->next) {
13✔
1106
    switch (flags) {
8✔
1107
      case CTRLS_GET_ACTION_ALL:
2✔
1108
        if (act->module != NULL) {
2✔
1109
          pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", act->action,
1✔
1110
            act->module->name);
1111

1112
        } else {
1113
           pr_ctrls_add_response(ctrl, "%s (core)", act->action);
1✔
1114
        }
1115

1116
        count++;
2✔
1117
        break;
2✔
1118

1119
      case CTRLS_GET_ACTION_ENABLED:
2✔
1120
        if (act->flags & PR_CTRLS_ACT_DISABLED) {
2✔
1121
          continue;
×
1122
        }
1123

1124
        if (act->module != NULL) {
2✔
1125
          pr_ctrls_add_response(ctrl, "%s (mod_%s.c)", act->action,
1✔
1126
            act->module->name);
1127

1128
        } else {
1129
          pr_ctrls_add_response(ctrl, "%s (core)", act->action);
1✔
1130
        }
1131

1132
        count++;
2✔
1133
        break;
2✔
1134

1135
      case CTRLS_GET_DESC:
2✔
1136
        pr_ctrls_add_response(ctrl, "%s: %s", act->action,
2✔
1137
          act->desc);
1138
        count++;
2✔
1139
        break;
2✔
1140
    }
1141
  }
1142

1143
  return count;
1144
}
1145

1146
int pr_set_registered_actions(module *mod, const char *action,
7✔
1147
    unsigned char skip_disabled, unsigned int flags) {
1148
  register ctrls_action_t *act = NULL;
7✔
1149
  unsigned char have_action = FALSE;
7✔
1150

1151
  /* Is flags a valid combination of settable flags? */
1152
  if (flags > 0 &&
14✔
1153
      flags != PR_CTRLS_ACT_SOLITARY &&
7✔
1154
      flags != PR_CTRLS_ACT_DISABLED &&
2✔
1155
      flags != (PR_CTRLS_ACT_SOLITARY|PR_CTRLS_ACT_DISABLED)) {
1✔
1156
    errno = EINVAL;
1✔
1157
    return -1;
1✔
1158
  }
1159

1160
  /* Are ctrls blocked? */
1161
  if (ctrls_blocked == TRUE) {
6✔
1162
    errno = EPERM;
1✔
1163
    return -1;
1✔
1164
  }
1165

1166
  for (act = ctrls_action_list; act; act = act->next) {
10✔
1167
    if (skip_disabled == TRUE &&
5✔
1168
        (act->flags & PR_CTRLS_ACT_DISABLED)) {
×
1169
      continue;
×
1170
    }
1171

1172
    if ((action == NULL ||
9✔
1173
         strcmp(action, "all") == 0 ||
7✔
1174
         strcmp(act->action, action) == 0) &&
8✔
1175
        (act->module == mod || mod == ANY_MODULE || mod == NULL)) {
5✔
1176
      have_action = TRUE;
5✔
1177
      act->flags = flags;
5✔
1178
    }
1179
  }
1180

1181
  if (have_action == FALSE) {
5✔
1182
    errno = ENOENT;
1✔
1183
    return -1;
1✔
1184
  }
1185

1186
  return 0;
1187
}
1188

1189
#if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
1190
    !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS)
1191
static int ctrls_connect_local_creds(int fd) {
1192
  char buf[1] = {'\0'};
1193
  int res;
1194

1195
  /* The backend doesn't care what we send here, but it wants
1196
   * exactly one character to force recvmsg() to block and wait
1197
   * for us.
1198
   */
1199

1200
  res = write(fd, buf, 1);
1201
  while (res < 0) {
1202
    if (errno == EINTR) {
1203
      pr_signals_handle();
1204

1205
      res = write(fd, buf, 1);
1206
      continue;
1207
    }
1208

1209
    pr_trace_msg(trace_channel, 5,
1210
      "error writing credentials byte for LOCAL_CREDS to fd %d: %s", fd,
1211
      strerror(errno));
1212
    return -1;
1213
  }
1214

1215
  return res;
1216
}
1217
#endif /* !SCM_CREDS */
1218

1219
int pr_ctrls_connect(const char *socket_file) {
3✔
1220
  int fd = -1, len = 0;
3✔
1221
  struct sockaddr_un cl_sock, ctrl_sock;
1222

1223
  if (socket_file == NULL) {
3✔
1224
    errno = EINVAL;
1✔
1225
    return -1;
1✔
1226
  }
1227

1228
  /* No interruptions */
1229
  pr_signals_block();
2✔
1230

1231
  /* Create a Unix domain socket */
1232
  fd = socket(AF_UNIX, SOCK_STREAM, 0);
2✔
1233
  if (fd < 0) {
2✔
1234
    int xerrno = errno;
×
1235

1236
    pr_signals_unblock();
×
1237

1238
    errno = xerrno;
×
1239
    return -1;
×
1240
  }
1241

1242
  if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
2✔
1243
    int xerrno = errno;
×
1244

1245
    (void) close(fd);
×
1246
    pr_signals_unblock();
×
1247

1248
    errno = xerrno;
×
1249
    return -1;
×
1250
  }
1251

1252
  /* Fill in the socket address */
1253
  memset(&cl_sock, 0, sizeof(cl_sock));
2✔
1254

1255
  /* This first part is clever.  First, this process creates a socket in
1256
   * the file system.  It _then_ connect()s to the server.  Upon accept()ing
1257
   * the connection, the server examines the created socket to see that it
1258
   * is indeed a socket, with the proper mode and time.  Clever, but not
1259
   * ideal.
1260
   */
1261

1262
  cl_sock.sun_family = AF_UNIX;
2✔
1263
  pr_snprintf(cl_sock.sun_path, sizeof(cl_sock.sun_path) - 1, "%s%05u",
2✔
1264
    "/tmp/ftp.cl", (unsigned int) getpid());
2✔
1265
  len = sizeof(cl_sock);
2✔
1266

1267
  /* Make sure the file doesn't already exist */
1268
  (void) unlink(cl_sock.sun_path);
2✔
1269

1270
  /* Make it a socket */
1271
  if (bind(fd, (struct sockaddr *) &cl_sock, len) < 0) {
2✔
1272
    int xerrno = errno;
×
1273

1274
    pr_trace_msg(trace_channel, 19, "error binding local socket to '%s': %s",
×
1275
      cl_sock.sun_path, strerror(xerrno));
1276
    (void) unlink(cl_sock.sun_path);
×
1277
    (void) close(fd);
×
1278
    pr_signals_unblock();
×
1279

1280
    errno = xerrno;
×
1281
    return -1;
×
1282
  }
1283

1284
  /* Set the proper mode */
1285
  if (chmod(cl_sock.sun_path, PR_CTRLS_CL_MODE) < 0) {
2✔
1286
    int xerrno = errno;
×
1287

1288
    pr_trace_msg(trace_channel, 19, "error setting local socket mode: %s",
×
1289
      strerror(xerrno));
1290
    (void) unlink(cl_sock.sun_path);
×
1291
    (void) close(fd);
×
1292
    pr_signals_unblock();
×
1293

1294
    errno = xerrno;
×
1295
    return -1;
×
1296
  }
1297

1298
  /* Now connect to the real server */
1299
  memset(&ctrl_sock, 0, sizeof(ctrl_sock));
2✔
1300

1301
  ctrl_sock.sun_family = AF_UNIX;
2✔
1302
  sstrncpy(ctrl_sock.sun_path, socket_file, sizeof(ctrl_sock.sun_path));
2✔
1303
  len = sizeof(ctrl_sock);
2✔
1304

1305
  if (connect(fd, (struct sockaddr *) &ctrl_sock, len) < 0) {
2✔
1306
    int xerrno = errno;
1✔
1307

1308
    pr_trace_msg(trace_channel, 19, "error connecting to local socket '%s': %s",
1✔
1309
      ctrl_sock.sun_path, strerror(xerrno));
1310
    (void) unlink(cl_sock.sun_path);
1✔
1311
    (void) close(fd);
1✔
1312
    pr_signals_unblock();
1✔
1313

1314
    errno = xerrno;
1✔
1315
    return -1;
1✔
1316
  }
1317

1318
#if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
1319
    !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS)
1320
  if (ctrls_connect_local_creds(fd) < 0) {
1321
    int xerrno = errno;
1322

1323
    pr_trace_msg(trace_channel, 19, "error sending creds to local socket: %s",
1324
      strerror(xerrno));
1325
    (void) unlink(cl_sock.sun_path);
1326
    (void) close(fd);
1327
    pr_signals_unblock();
1328

1329
    errno = xerrno;
1330
    return -1;
1331
  }
1332
#endif /* LOCAL_CREDS */
1333

1334
  pr_signals_unblock();
1✔
1335
  return fd;
1✔
1336
}
1337

1338
int pr_ctrls_issock_unix(mode_t sock_mode) {
3✔
1339

1340
  if (ctrls_use_isfifo == TRUE) {
3✔
1341
#if defined(S_ISFIFO)
1342
    if (S_ISFIFO(sock_mode)) {
×
1343
      return 0;
1344
    }
1345
#endif /* S_ISFIFO */
1346
  } else {
1347
#if defined(S_ISSOCK)
1348
    if (S_ISSOCK(sock_mode)) {
3✔
1349
      return 0;
1350
    }
1351
#endif /* S_ISSOCK */
1352
  }
1353

1354
  errno = ENOSYS;
2✔
1355
  return -1;
2✔
1356
}
1357

1358
#if defined(SO_PEERCRED)
1359
static int ctrls_get_creds_peercred(int fd, uid_t *uid, gid_t *gid,
×
1360
    pid_t *pid) {
1361
# if defined(HAVE_STRUCT_SOCKPEERCRED)
1362
  struct sockpeercred cred;
1363
# else
1364
  struct ucred cred;
1365
# endif /* HAVE_STRUCT_SOCKPEERCRED */
1366
  socklen_t cred_len;
1367

1368
  cred_len = sizeof(cred);
×
1369
  if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) < 0) {
×
1370
    int xerrno = errno;
×
1371

1372
    pr_trace_msg(trace_channel, 2,
×
1373
      "error obtaining peer credentials using SO_PEERCRED: %s",
1374
      strerror(xerrno));
1375

1376
    errno = EPERM;
×
1377
    return -1;
×
1378
  }
1379

1380
  if (uid != NULL) {
×
1381
    *uid = cred.uid;
×
1382
  }
1383

1384
  if (gid != NULL) {
×
1385
    *gid = cred.gid;
×
1386
  }
1387

1388
  if (pid != NULL) {
×
1389
    *pid = cred.pid;
×
1390
  }
1391

1392
  return 0;
1393
}
1394
#endif /* SO_PEERCRED */
1395

1396
#if !defined(SO_PEERCRED) && defined(HAVE_GETPEEREID)
1397
static int ctrls_get_creds_peereid(int fd, uid_t *uid, gid_t *gid) {
1398
  if (getpeereid(fd, uid, gid) < 0) {
1399
    int xerrno = errno;
1400

1401
    pr_trace_msg(trace_channel, 7, "error obtaining credentials using "
1402
      "getpeereid(2) on fd %d: %s", fd, strerror(xerrno));
1403

1404
    errno = xerrno;
1405
    return -1;
1406
  }
1407

1408
  return 0;
1409
}
1410
#endif /* !HAVE_GETPEEREID */
1411

1412
#if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
1413
    defined(HAVE_GETPEERUCRED)
1414
static int ctrls_get_creds_peerucred(int fd, uid_t *uid, gid_t *gid) {
1415
  ucred_t *cred = NULL;
1416

1417
  if (getpeerucred(fd, &cred) < 0) {
1418
    int xerrno = errno;
1419

1420
    pr_trace_msg(trace_channel, 7, "error obtaining credentials using "
1421
      "getpeerucred(3) on fd %d: %s", fd, strerror(xerrno));
1422

1423
    errno = xerrno;
1424
    return -1;
1425
  }
1426

1427
  if (uid != NULL) {
1428
    *uid = ucred_getruid(cred);
1429
  }
1430

1431
  if (gid != NULL) {
1432
    *gid = ucred_getrgid(cred);
1433
  }
1434

1435
  ucred_free(cred);
1436
  return 0;
1437
}
1438
#endif /* !HAVE_GETPEERUCRED */
1439

1440
#if !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
1441
    !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS)
1442
static int ctrls_get_creds_local(int fd, uid_t *uid, gid_t *gid,
1443
    pid_t *pid) {
1444
  int res;
1445
  char buf[1];
1446
  struct iovec iov;
1447
  struct msghdr msg;
1448

1449
# if defined(SOCKCREDSIZE)
1450
#  define MINCREDSIZE                (sizeof(struct cmsghdr) + SOCKCREDSIZE(0))
1451
# else
1452
#  if defined(HAVE_STRUCT_CMSGCRED)
1453
#   define MINCREDSIZE                (sizeof(struct cmsghdr) + sizeof(struct cmsgcred))
1454
#  elif defined(HAVE_STRUCT_SOCKCRED)
1455
#   define MINCREDSIZE                (sizeof(struct cmsghdr) + sizeof(struct sockcred))
1456
#  endif
1457
# endif /* !SOCKCREDSIZE */
1458

1459
  char control[MINCREDSIZE];
1460

1461
  iov.iov_base = buf;
1462
  iov.iov_len = 1;
1463

1464
  memset(&msg, 0, sizeof(msg));
1465
  msg.msg_iov = &iov;
1466
  msg.msg_iovlen = 1;
1467
  msg.msg_control = control;
1468
  msg.msg_controllen = sizeof(control);
1469
  msg.msg_flags = 0;
1470

1471
  res = recvmsg(fd, &msg, 0);
1472
  while (res < 0) {
1473
    int xerrno = errno;
1474

1475
    if (xerrno == EINTR) {
1476
      pr_signals_handle();
1477

1478
      res = recvmsg(fd, &msg, 0);
1479
      continue;
1480
    }
1481

1482
    pr_trace_msg(trace_channel, 6,
1483
      "error calling recvmsg() on fd %d: %s", fd, strerror(xerrno));
1484

1485
    errno = xerrno;
1486
    return -1;
1487
  }
1488

1489
  if (msg.msg_controllen > 0) {
1490
#if defined(HAVE_STRUCT_CMSGCRED)
1491
    struct cmsgcred cred;
1492
#elif defined(HAVE_STRUCT_SOCKCRED)
1493
    struct sockcred cred;
1494
#endif /* !CMSGCRED and !SOCKCRED */
1495

1496
    struct cmsghdr *hdr = (struct cmsghdr *) control;
1497

1498
    if (hdr->cmsg_level != SOL_SOCKET) {
1499
      pr_trace_msg(trace_channel, 5,
1500
        "message received via recvmsg() on fd %d was not a SOL_SOCKET message",
1501
        fd);
1502

1503
      errno = EINVAL;
1504
      return -1;
1505
    }
1506

1507
    if (hdr->cmsg_len < MINCREDSIZE) {
1508
      pr_trace_msg(trace_channel, 5,
1509
        "message received via recvmsg() on fd %d was not of proper "
1510
        "length (%u bytes)", fd, MINCREDSIZE);
1511

1512
      errno = EINVAL;
1513
      return -1;
1514
    }
1515

1516
    if (hdr->cmsg_type != SCM_CREDS) {
1517
      pr_trace_msg(trace_channel, 5,
1518
        "message received via recvmsg() on fd %d was not of type SCM_CREDS",
1519
        fd);
1520

1521
      errno = EINVAL;
1522
      return -1;
1523
    }
1524

1525
#if defined(HAVE_STRUCT_CMSGCRED)
1526
    memcpy(&cred, CMSG_DATA(hdr), sizeof(struct cmsgcred));
1527

1528
    if (uid != NULL) {
1529
      *uid = cred.cmcred_uid;
1530
    }
1531

1532
    if (gid != NULL) {
1533
      *gid = cred.cmcred_gid;
1534
    }
1535

1536
    if (pid != NULL) {
1537
      *pid = cred.cmcred_pid;
1538
    }
1539

1540
#elif defined(HAVE_STRUCT_SOCKCRED)
1541
    memcpy(&cred, CMSG_DATA(hdr), sizeof(struct sockcred));
1542

1543
    if (uid != NULL) {
1544
      *uid = cred.sc_uid;
1545
    }
1546

1547
    if (gid != NULL) {
1548
      *gid = cred.sc_gid;
1549
    }
1550
#endif
1551

1552
    return 0;
1553
  }
1554

1555
  return -1;
1556
}
1557
#endif /* !SCM_CREDS */
1558

1559
static int ctrls_get_creds_basic(struct sockaddr_un *sock, int cl_fd,
×
1560
    unsigned int max_age, uid_t *uid, gid_t *gid, pid_t *pid) {
1561
  pid_t cl_pid = 0;
×
1562
  char *tmp = NULL;
×
1563
  time_t stale_time;
1564
  struct stat st;
1565

1566
  /* Check the path -- hmmm... */
1567
  PRIVS_ROOT
×
1568
  while (stat(sock->sun_path, &st) < 0) {
×
1569
    int xerrno = errno;
×
1570

1571
    if (xerrno == EINTR) {
×
1572
      pr_signals_handle();
×
1573
      continue;
×
1574
    }
1575

1576
    PRIVS_RELINQUISH
×
1577
    pr_trace_msg(trace_channel, 2, "error: unable to stat %s: %s",
×
1578
      sock->sun_path, strerror(xerrno));
1579
    (void) close(cl_fd);
×
1580

1581
    errno = xerrno;
×
1582
    return -1;
×
1583
  }
1584
  PRIVS_RELINQUISH
×
1585

1586
  /* Is it a socket? */
1587
  if (pr_ctrls_issock_unix(st.st_mode) < 0) {
×
1588
    (void) close(cl_fd);
×
1589
    errno = ENOTSOCK;
×
1590
    return -1;
×
1591
  }
1592

1593
  /* Are the perms _not_ rwx------? */
1594
  if (st.st_mode & (S_IRWXG|S_IRWXO) ||
×
1595
      ((st.st_mode & S_IRWXU) != PR_CTRLS_CL_MODE)) {
1596
    pr_trace_msg(trace_channel, 3,
×
1597
      "error: unable to accept connection: incorrect mode");
1598
    (void) close(cl_fd);
×
1599
    errno = EPERM;
×
1600
    return -1;
×
1601
  }
1602

1603
  /* Is it new enough? */
1604
  stale_time = time(NULL) - max_age;
×
1605

1606
  if (st.st_atime < stale_time ||
×
1607
      st.st_ctime < stale_time ||
×
1608
      st.st_mtime < stale_time) {
×
1609
    pool *tmp_pool;
1610
    char *msg = "error: stale connection";
×
1611

1612
    pr_trace_msg(trace_channel, 3,
×
1613
      "unable to accept connection: stale connection");
1614

1615
    /* Log the times being compared, to aid in debugging this situation. */
1616
    if (st.st_atime < stale_time) {
×
1617
      time_t age = stale_time - st.st_atime;
×
1618

1619
      pr_trace_msg(trace_channel, 3,
×
1620
        "last access time of '%s' is %lu secs old (must be less than %u secs)",
1621
        sock->sun_path, (unsigned long) age, max_age);
1622
    }
1623

1624
    if (st.st_ctime < stale_time) {
×
1625
      time_t age = stale_time - st.st_ctime;
×
1626

1627
      pr_trace_msg(trace_channel, 3,
×
1628
        "last change time of '%s' is %lu secs old (must be less than %u secs)",
1629
        sock->sun_path, (unsigned long) age, max_age);
1630
    }
1631

1632
    if (st.st_mtime < stale_time) {
×
1633
      time_t age = stale_time - st.st_mtime;
×
1634

1635
      pr_trace_msg(trace_channel, 3,
×
1636
        "last modified time of '%s' is %lu secs old (must be less than %u "
1637
        "secs)", sock->sun_path, (unsigned long) age, max_age);
1638
    }
1639

1640
    tmp_pool = make_sub_pool(permanent_pool);
×
1641

1642
    if (pr_ctrls_send_response(tmp_pool, cl_fd, -1, 1, &msg) < 0) {
×
1643
      pr_trace_msg(trace_channel, 2, "error sending message: %s",
×
1644
        strerror(errno));
×
1645
    }
1646

1647
    destroy_pool(tmp_pool);
×
1648
    (void) close(cl_fd);
×
1649

1650
    errno = ETIMEDOUT;
×
1651
    return -1;
1652
  }
1653

1654
  /* Parse the PID out of the path */
1655
  tmp = sock->sun_path;
×
1656
  tmp += strlen("/tmp/ftp.cl");
×
1657
  cl_pid = atol(tmp);
×
1658

1659
  /* Return the IDs of the caller */
1660
  if (uid != NULL) {
×
1661
    *uid = st.st_uid;
×
1662
  }
1663

1664
  if (gid != NULL) {
×
1665
    *gid = st.st_gid;
×
1666
  }
1667

1668
  if (pid != NULL) {
×
1669
    *pid = cl_pid;
×
1670
  }
1671

1672
  return 0;
1673
}
1674

1675
int pr_ctrls_accept(int fd, uid_t *uid, gid_t *gid, pid_t *pid,
2✔
1676
    unsigned int max_age) {
1677
  socklen_t len = 0;
1678
  struct sockaddr_un sock;
1679
  int cl_fd = -1, res = -1, xerrno;
2✔
1680

1681
  len = sizeof(sock);
2✔
1682

1683
  cl_fd = accept(fd, (struct sockaddr *) &sock, &len);
2✔
1684
  xerrno = errno;
2✔
1685

1686
  while (cl_fd < 0) {
4✔
1687
    if (xerrno == EINTR) {
2✔
1688
      pr_signals_handle();
×
1689

1690
      cl_fd = accept(fd, (struct sockaddr *) &sock, &len);
×
1691
      xerrno = errno;
×
1692
      continue;
×
1693
    }
1694

1695
    pr_trace_msg(trace_channel, 3,
2✔
1696
      "error: unable to accept on local socket: %s", strerror(xerrno));
1697

1698
    errno = xerrno;
2✔
1699
    return -1;
2✔
1700
  }
1701

1702
  /* NULL terminate the name */
1703
  sock.sun_path[sizeof(sock.sun_path)-1] = '\0';
×
1704

1705
#if defined(SO_PEERCRED)
1706
  pr_trace_msg(trace_channel, 5,
×
1707
    "checking client credentials using SO_PEERCRED");
1708
  res = ctrls_get_creds_peercred(cl_fd, uid, gid, pid);
×
1709

1710
#elif !defined(SO_PEERCRED) && defined(HAVE_GETPEEREID)
1711
  pr_trace_msg(trace_channel, 5,
1712
    "checking client credentials using getpeereid(2)");
1713
  res = ctrls_get_creds_peereid(cl_fd, uid, gid);
1714

1715
#elif !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
1716
      defined(HAVE_GETPEERUCRED)
1717
  pr_trace_msg(trace_channel, 5,
1718
    "checking client credentials using getpeerucred(3)");
1719
  res = ctrls_get_creds_peerucred(cl_fd, uid, gid);
1720

1721
#elif !defined(SO_PEERCRED) && !defined(HAVE_GETPEEREID) && \
1722
      !defined(HAVE_GETPEERUCRED) && defined(LOCAL_CREDS)
1723
  pr_trace_msg(trace_channel, 5,
1724
    "checking client credentials using SCM_CREDS");
1725
  res = ctrls_get_creds_local(cl_fd, uid, gid, pid);
1726
#endif
1727

1728
  /* Fallback to the Stevens method of determining connection credentials,
1729
   * if the kernel-enforced methods did not pan out.
1730
   */
1731
  if (res < 0) {
×
1732
    pr_trace_msg(trace_channel, 5,
×
1733
      "checking client credentials using Stevens' method");
1734
    res = ctrls_get_creds_basic(&sock, cl_fd, max_age, uid, gid, pid);
×
1735
    if (res < 0) {
×
1736
      return res;
1737
    }
1738
  }
1739

1740
  /* Done with the path now */
1741
  PRIVS_ROOT
×
1742
  (void) unlink(sock.sun_path);
×
1743
  PRIVS_RELINQUISH
×
1744

1745
  return cl_fd;
×
1746
}
1747

1748
void pr_block_ctrls(void) {
3✔
1749
  ctrls_blocked = TRUE;
46✔
1750
}
3✔
1751

1752
void pr_unblock_ctrls(void) {
3✔
1753
  ctrls_blocked = FALSE;
46✔
1754
}
3✔
1755

1756
int pr_ctrls_check_actions(void) {
4✔
1757
  register ctrls_action_t *act = NULL;
4✔
1758

1759
  for (act = ctrls_action_list; act; act = act->next) {
7✔
1760
    if (act->flags & PR_CTRLS_ACT_SOLITARY) {
4✔
1761
      /* This is a territorial action -- only one instance allowed */
1762
      if (ctrls_lookup_action(NULL, act->action, FALSE)) {
2✔
1763
        pr_log_pri(PR_LOG_NOTICE,
1✔
1764
          "duplicate controls for '%s' action not allowed",
1765
          act->action);
1766
        errno = EEXIST;
1✔
1767
        return -1;
1✔
1768
      }
1769
    }
1770
  }
1771

1772
  return 0;
1773
}
1774

1775
int pr_run_ctrls(module *mod, const char *action) {
12✔
1776
  register pr_ctrls_t *ctrl = NULL;
12✔
1777
  time_t now;
1778

1779
  /* Are ctrls blocked? */
1780
  if (ctrls_blocked == TRUE) {
12✔
1781
    errno = EPERM;
1✔
1782
    return -1;
1✔
1783
  }
1784

1785
  now = time(NULL);
11✔
1786

1787
  for (ctrl = ctrls_active_list; ctrl; ctrl = ctrl->ctrls_next) {
18✔
1788
    int res;
1789

1790
    if (mod != NULL &&
14✔
1791
        ctrl->ctrls_module != NULL &&
14✔
1792
        ctrl->ctrls_module != mod) {
1793
      pr_trace_msg(trace_channel, 19,
1✔
1794
        "skipping ctrl due to module mismatch: module = %p, ctrl module = %p",
1795
        mod, ctrl->ctrls_module);
1796
      continue;
1✔
1797
    }
1798

1799
    /* Be watchful of the various client-side flags.  Note: if
1800
     * ctrl->ctrls_cl is ever NULL, it means there's a bug in the code.
1801
     */
1802
    if (ctrl->ctrls_cl->cl_flags != PR_CTRLS_CL_HAVEREQ) {
6✔
1803
      pr_trace_msg(trace_channel, 19,
1✔
1804
        "skipping ctrl due to missing client HAVEREQ flag");
1805
      continue;
1✔
1806
    }
1807

1808
    /* Has this control been disabled? */
1809
    if (ctrl->ctrls_flags & PR_CTRLS_ACT_DISABLED) {
5✔
1810
      pr_trace_msg(trace_channel, 19,
1✔
1811
        "skipping ctrl due to ACT_DISABLED flag");
1812
      continue;
1✔
1813
    }
1814

1815
    /* Is it time to trigger this ctrl? */
1816
    if (!(ctrl->ctrls_flags & PR_CTRLS_FL_REQUESTED)) {
4✔
1817
      pr_trace_msg(trace_channel, 19,
1✔
1818
        "skipping ctrl due to missing CTRLS_REQUESTED flag");
1819
      continue;
1✔
1820
    }
1821

1822
    if (ctrl->ctrls_when > now) {
3✔
1823
      pr_trace_msg(trace_channel, 19,
1✔
1824
        "skipping ctrl because it is still pending: now = %lu, ctrl when = %lu",
1825
        (unsigned long) now, (unsigned long) ctrl->ctrls_when);
1826
      ctrl->ctrls_flags |= PR_CTRLS_FL_PENDING;
1✔
1827
      pr_ctrls_add_response(ctrl, "request pending");
1✔
1828
      continue;
1✔
1829
    }
1830

1831
    if (action == NULL ||
4✔
1832
        strcmp(ctrl->ctrls_action, action) == 0) {
2✔
1833
      pr_trace_msg(trace_channel, 7, "calling '%s' control handler",
1✔
1834
        ctrl->ctrls_action);
1835

1836
    } else {
1837
      continue;
1✔
1838
    }
1839

1840
    pr_unblock_ctrls();
1841
    res = ctrl->ctrls_cb(ctrl,
4✔
1842
      (ctrl->ctrls_cb_args ? ctrl->ctrls_cb_args->nelts : 0),
1✔
1843
      (ctrl->ctrls_cb_args ? (char **) ctrl->ctrls_cb_args->elts : NULL));
1✔
1844
    pr_block_ctrls();
1845

1846
    pr_trace_msg(trace_channel, 19,
1✔
1847
      "ran '%s' ctrl, callback value = %d", ctrl->ctrls_action, res);
1848

1849
    if (res >= PR_CTRLS_STATUS_PENDING) {
1✔
1850
      pr_trace_msg(trace_channel, 1, "'%s' ctrl returned inappropriate "
×
1851
        "value %d, treating as GENERIC_ERROR (%d)", ctrl->ctrls_action, res,
1852
        PR_CTRLS_STATUS_GENERIC_ERROR);
1853
      res = PR_CTRLS_STATUS_GENERIC_ERROR;
×
1854
    }
1855

1856
    ctrl->ctrls_flags &= ~PR_CTRLS_FL_REQUESTED;
1✔
1857
    ctrl->ctrls_flags &= ~PR_CTRLS_FL_PENDING;
1✔
1858
    ctrl->ctrls_flags |= PR_CTRLS_FL_HANDLED;
1✔
1859

1860
    ctrl->ctrls_cb_retval = res;
1✔
1861
  }
1862

1863
  return 0;
1864
}
1865

1866
int pr_ctrls_reset(void) {
2✔
1867
  pr_ctrls_t *ctrl = NULL;
2✔
1868

1869
  /* NOTE: need a clean_ctrls() or somesuch that will, after sending any
1870
   * responses, iterate through the list and "free" any ctrls whose
1871
   * ctrls_cb_retval is zero.  This feature is used to handle things like
1872
   * shutdown requests in the future -- the request is only considered
1873
   * "processed" when the callback returns zero.  Any non-zero requests are
1874
   * not cleared, and are considered "pending".  However, this brings up the
1875
   * complication of an additional request for that action being issued by the
1876
   * client before the request is processed.  Simplest solution: remove the
1877
   * old request args, and replace them with the new ones.
1878
   *
1879
   * This requires that the return value of the ctrl callback be explicitly
1880
   * documented.
1881
   *
1882
   * How about: ctrls_cb_retval = 1  pending
1883
   *                              0  processed, OK    (reset)
1884
   *                             -1  processed, error (reset)
1885
   */
1886

1887
  for (ctrl = ctrls_active_list; ctrl; ctrl = ctrl->ctrls_next) {
2✔
1888
    if (ctrl->ctrls_cb_retval < PR_CTRLS_STATUS_PENDING) {
×
1889
      pr_ctrls_free(ctrl);
×
1890
    }
1891
  }
1892

1893
  return 0;
2✔
1894
}
1895

1896
/* From include/mod_ctrls.h */
1897

1898
/* Returns TRUE if the given cl_gid is allowed by the group ACL, FALSE
1899
 * otherwise. Note that the default is to deny everyone, unless an ACL has
1900
 * been configured.
1901
 */
1902
int pr_ctrls_check_group_acl(gid_t cl_gid, const ctrls_group_acl_t *group_acl) {
10✔
1903
  int res = FALSE;
10✔
1904

1905
  if (group_acl == NULL) {
10✔
1906
    errno = EINVAL;
1✔
1907
    return -1;
1✔
1908
  }
1909

1910
  /* Note: the special condition of ngids of 1 and gids of NULL signals
1911
   * that all groups are to be treated according to the allow member.
1912
   */
1913
  if (group_acl->gids != NULL) {
9✔
1914
    register unsigned int i = 0;
1915

1916
    for (i = 0; i < group_acl->ngids; i++) {
2✔
1917
      if ((group_acl->gids)[i] == cl_gid) {
2✔
1918
        res = TRUE;
1✔
1919
      }
1920
    }
1921

1922
  } else if (group_acl->ngids == 1) {
7✔
1923
    res = TRUE;
4✔
1924
  }
1925

1926
  if (!group_acl->allow) {
9✔
1927
    res = !res;
2✔
1928
  }
1929

1930
  return res;
1931
}
1932

1933
/* Returns TRUE if the given cl_uid is allowed by the user ACL, FALSE
1934
 * otherwise. Note that the default is to deny everyone, unless an ACL has
1935
 * been configured.
1936
 */
1937
int pr_ctrls_check_user_acl(uid_t cl_uid, const ctrls_user_acl_t *user_acl) {
10✔
1938
  int res = FALSE;
10✔
1939

1940
  if (user_acl == NULL) {
10✔
1941
    errno = EINVAL;
1✔
1942
    return -1;
1✔
1943
  }
1944

1945
  /* Note: the special condition of nuids of 1 and uids of NULL signals
1946
   * that all users are to be treated according to the allow member.
1947
   */
1948
  if (user_acl->uids != NULL) {
9✔
1949
    register unsigned int i = 0;
1950

1951
    for (i = 0; i < user_acl->nuids; i++) {
2✔
1952
      if ((user_acl->uids)[i] == cl_uid) {
2✔
1953
        res = TRUE;
1✔
1954
      }
1955
    }
1956

1957
  } else if (user_acl->nuids == 1) {
7✔
1958
    res = TRUE;
3✔
1959
  }
1960

1961
  if (!user_acl->allow) {
9✔
1962
    res = !res;
2✔
1963
  }
1964

1965
  return res;
1966
}
1967

1968
/* Returns TRUE for allowed, FALSE for denied. */
1969
int pr_ctrls_check_acl(const pr_ctrls_t *ctrl,
9✔
1970
    const ctrls_acttab_t *acttab, const char *action) {
1971
  register unsigned int i = 0;
9✔
1972

1973
  if (ctrl == NULL ||
17✔
1974
      ctrl->ctrls_cl == NULL ||
8✔
1975
      acttab == NULL ||
14✔
1976
      action == NULL) {
7✔
1977
    errno = EINVAL;
4✔
1978
    return -1;
4✔
1979
  }
1980

1981
  for (i = 0; acttab[i].act_action; i++) {
3✔
1982
    if (strcmp(acttab[i].act_action, action) == 0) {
5✔
1983
      int user_check = FALSE, group_check = FALSE;
4✔
1984

1985
      if (acttab[i].act_acl != NULL) {
4✔
1986
        user_check = pr_ctrls_check_user_acl(ctrl->ctrls_cl->cl_uid,
3✔
1987
          &(acttab[i].act_acl->acl_users));
3✔
1988
        group_check = pr_ctrls_check_group_acl(ctrl->ctrls_cl->cl_gid,
3✔
1989
          &(acttab[i].act_acl->acl_groups));
3✔
1990
      }
1991

1992
      if (user_check != TRUE &&
8✔
1993
          group_check != TRUE) {
4✔
1994
        return FALSE;
1995
      }
1996
    }
1997
  }
1998

1999
  return TRUE;
2000
}
2001

2002
int pr_ctrls_init_acl(ctrls_acl_t *acl) {
7✔
2003
  if (acl == NULL) {
7✔
2004
    errno = EINVAL;
1✔
2005
    return -1;
1✔
2006
  }
2007

2008
  memset(acl, 0, sizeof(ctrls_acl_t));
6✔
2009
  acl->acl_users.allow = acl->acl_groups.allow = TRUE;
6✔
2010

2011
  return 0;
6✔
2012
}
2013

2014
static char *ctrls_argsep(char **arg) {
46✔
2015
  char *ret = NULL, *dst = NULL;
46✔
2016
  char quote_mode = 0;
46✔
2017

2018
  if (arg == NULL ||
92✔
2019
      !*arg ||
92✔
2020
      !**arg) {
46✔
2021
    errno = EINVAL;
12✔
2022
    return NULL;
12✔
2023
  }
2024

2025
  while (**arg &&
34✔
2026
         PR_ISSPACE(**arg)) {
34✔
2027
    (*arg)++;
×
2028
  }
2029

2030
  if (!**arg) {
34✔
2031
    return NULL;
2032
  }
2033

2034
  ret = dst = *arg;
34✔
2035

2036
  if (**arg == '\"') {
34✔
2037
    quote_mode++;
×
2038
    (*arg)++;
×
2039
  }
2040

2041
  while (**arg && **arg != ',' &&
240✔
2042
      (quote_mode ? (**arg != '\"') : (!PR_ISSPACE(**arg)))) {
103✔
2043

2044
    if (**arg == '\\' && quote_mode) {
103✔
2045
      /* escaped char */
2046
      if (*((*arg) + 1)) {
×
2047
        *dst = *(++(*arg));
×
2048
      }
2049
    }
2050

2051
    *dst++ = **arg;
103✔
2052
    ++(*arg);
103✔
2053
  }
2054

2055
  if (**arg) {
34✔
2056
    (*arg)++;
22✔
2057
  }
2058

2059
  *dst = '\0';
34✔
2060
  return ret;
34✔
2061
}
2062

2063
char **pr_ctrls_parse_acl(pool *acl_pool, const char *acl_text) {
14✔
2064
  char *name = NULL, *acl_text_dup = NULL, **acl_list = NULL;
14✔
2065
  array_header *acl_arr = NULL;
14✔
2066
  pool *tmp_pool = NULL;
14✔
2067

2068
  if (acl_pool == NULL ||
28✔
2069
      acl_text == NULL) {
14✔
2070
    errno = EINVAL;
2✔
2071
    return NULL;
2✔
2072
  }
2073

2074
  tmp_pool = make_sub_pool(acl_pool);
12✔
2075
  acl_text_dup = pstrdup(tmp_pool, acl_text);
12✔
2076

2077
  /* Allocate an array */
2078
  acl_arr = make_array(acl_pool, 0, sizeof(char **));
12✔
2079

2080
  /* Add each name to the array */
2081
  while ((name = ctrls_argsep(&acl_text_dup)) != NULL) {
58✔
2082
    char *text;
2083

2084
    text = pstrdup(acl_pool, name);
34✔
2085

2086
    /* Push the name into the ACL array */
2087
    *((char **) push_array(acl_arr)) = text;
34✔
2088
  }
2089

2090
  /* Terminate the temp array with a NULL, as is proper. */
2091
  *((char **) push_array(acl_arr)) = NULL;
12✔
2092

2093
  acl_list = (char **) acl_arr->elts;
12✔
2094
  destroy_pool(tmp_pool);
12✔
2095

2096
  /* return the array of names */
2097
  return acl_list;
12✔
2098
}
2099

2100
int pr_ctrls_set_group_acl(pool *group_acl_pool, ctrls_group_acl_t *group_acl,
8✔
2101
    const char *allow, char *grouplist) {
2102
  char *group = NULL, **groups = NULL;
8✔
2103
  array_header *gid_list = NULL;
8✔
2104
  gid_t gid = 0;
8✔
2105
  pool *tmp_pool = NULL;
8✔
2106

2107
  if (group_acl_pool == NULL ||
16✔
2108
      group_acl == NULL ||
8✔
2109
      allow == NULL ||
12✔
2110
      grouplist == NULL) {
6✔
2111
    errno = EINVAL;
4✔
2112
    return -1;
4✔
2113
  }
2114

2115
  tmp_pool = make_sub_pool(group_acl_pool);
4✔
2116

2117
  if (strcasecmp(allow, "allow") == 0) {
4✔
2118
    group_acl->allow = TRUE;
3✔
2119

2120
  } else {
2121
    group_acl->allow = FALSE;
1✔
2122
  }
2123

2124
  /* Parse the given expression into an array, then retrieve the GID
2125
   * for each given name.
2126
   */
2127
  groups = pr_ctrls_parse_acl(group_acl_pool, grouplist);
4✔
2128

2129
  /* Allocate an array of gid_t's */
2130
  gid_list = make_array(group_acl_pool, 0, sizeof(gid_t));
4✔
2131

2132
  for (group = *groups; group != NULL; group = *++groups) {
15✔
2133

2134
    /* Handle a group name of "*" differently. */
2135
    if (strcmp(group, "*") == 0) {
12✔
2136
      group_acl->ngids = 1;
1✔
2137
      group_acl->gids = NULL;
1✔
2138
      destroy_pool(tmp_pool);
1✔
2139
      return 0;
1✔
2140
    }
2141

2142
    gid = pr_auth_name2gid(tmp_pool, group);
11✔
2143
    if (gid == (gid_t) -1) {
11✔
2144
      continue;
11✔
2145
    }
2146

2147
    *((gid_t *) push_array(gid_list)) = gid;
×
2148
  }
2149

2150
  group_acl->ngids = gid_list->nelts;
3✔
2151
  group_acl->gids = (gid_t *) gid_list->elts;
3✔
2152

2153
  destroy_pool(tmp_pool);
3✔
2154
  return 0;
3✔
2155
}
2156

2157
int pr_ctrls_set_user_acl(pool *user_acl_pool, ctrls_user_acl_t *user_acl,
10✔
2158
    const char *allow, char *userlist) {
2159
  char *user = NULL, **users = NULL;
10✔
2160
  array_header *uid_list = NULL;
10✔
2161
  uid_t uid = 0;
10✔
2162
  pool *tmp_pool = NULL;
10✔
2163

2164
  /* Sanity checks */
2165
  if (user_acl_pool == NULL ||
20✔
2166
      user_acl == NULL ||
10✔
2167
      allow == NULL ||
16✔
2168
      userlist == NULL) {
8✔
2169
    errno = EINVAL;
4✔
2170
    return -1;
4✔
2171
  }
2172

2173
  tmp_pool = make_sub_pool(user_acl_pool);
6✔
2174

2175
  if (strcasecmp(allow, "allow") == 0) {
6✔
2176
    user_acl->allow = TRUE;
5✔
2177

2178
  } else {
2179
    user_acl->allow = FALSE;
1✔
2180
  }
2181

2182
  /* Parse the given expression into an array, then retrieve the UID
2183
   * for each given name.
2184
   */
2185
  users = pr_ctrls_parse_acl(user_acl_pool, userlist);
6✔
2186

2187
  /* Allocate an array of uid_t's */
2188
  uid_list = make_array(user_acl_pool, 0, sizeof(uid_t));
6✔
2189

2190
  for (user = *users; user != NULL; user = *++users) {
23✔
2191

2192
    /* Handle a user name of "*" differently. */
2193
    if (strcmp(user, "*") == 0) {
18✔
2194
      user_acl->nuids = 1;
1✔
2195
      user_acl->uids = NULL;
1✔
2196
      destroy_pool(tmp_pool);
1✔
2197
      return 0;
1✔
2198
    }
2199

2200
    uid = pr_auth_name2uid(tmp_pool, user);
17✔
2201
    if (uid == (uid_t) -1) {
17✔
2202
      continue;
17✔
2203
    }
2204

2205
    *((uid_t *) push_array(uid_list)) = uid;
×
2206
  }
2207

2208
  user_acl->nuids = uid_list->nelts;
5✔
2209
  user_acl->uids = (uid_t *) uid_list->elts;
5✔
2210

2211
  destroy_pool(tmp_pool);
5✔
2212
  return 0;
5✔
2213
}
2214

2215
int pr_ctrls_set_module_acls2(ctrls_acttab_t *acttab, pool *acl_pool,
23✔
2216
    char **actions, const char *allow, const char *type, char *list,
2217
    const char **bad_action) {
2218
  register unsigned int i = 0;
23✔
2219
  int all_actions = FALSE;
23✔
2220

2221
  if (acttab == NULL ||
46✔
2222
      acl_pool == NULL ||
23✔
2223
      actions == NULL ||
38✔
2224
      type == NULL ||
32✔
2225
      bad_action == NULL) {
2226
    errno = EINVAL;
12✔
2227
    return -1;
12✔
2228
  }
2229

2230
  if (strcasecmp(type, "user") != 0 &&
16✔
2231
      strcasecmp(type, "group") != 0) {
5✔
2232
    errno = EINVAL;
3✔
2233
    return -1;
3✔
2234
  }
2235

2236
  /* First, sanity check the given list of actions against the actions
2237
   * in the given table.
2238
   */
2239
  for (i = 0; actions[i]; i++) {
6✔
2240
    register unsigned int j = 0;
8✔
2241
    int valid_action = FALSE;
8✔
2242

2243
    if (strcasecmp(actions[i], "all") == 0) {
8✔
2244
      continue;
2✔
2245
    }
2246

2247
    for (j = 0; acttab[j].act_action; j++) {
2✔
2248
      if (strcmp(actions[i], acttab[j].act_action) == 0) {
6✔
2249
        valid_action = TRUE;
2250
        break;
2251
      }
2252
    }
2253

2254
    if (valid_action == FALSE) {
6✔
2255
      *bad_action = actions[i];
2✔
2256
      errno = EPERM;
2✔
2257
      return -1;
2✔
2258
    }
2259
  }
2260

2261
  for (i = 0; actions[i]; i++) {
6✔
2262
    register unsigned int j = 0;
6✔
2263

2264
    if (all_actions == FALSE &&
12✔
2265
        strcasecmp(actions[i], "all") == 0) {
6✔
2266
      all_actions = TRUE;
2✔
2267
    }
2268

2269
    for (j = 0; acttab[j].act_action; j++) {
12✔
2270
      int res = 0;
6✔
2271

2272
      if (all_actions == TRUE ||
10✔
2273
          strcmp(actions[i], acttab[j].act_action) == 0) {
4✔
2274

2275
        /* Use the type parameter to determine whether the list is of users or
2276
         * of groups.
2277
         */
2278
        if (strcasecmp(type, "user") == 0) {
6✔
2279
          res = pr_ctrls_set_user_acl(acl_pool,
4✔
2280
            &(acttab[j].act_acl->acl_users), allow, list);
4✔
2281

2282
        } else if (strcasecmp(type, "group") == 0) {
2✔
2283
          res = pr_ctrls_set_group_acl(acl_pool,
2✔
2284
            &(acttab[j].act_acl->acl_groups), allow, list);
2✔
2285
        }
2286

2287
        if (res < 0) {
6✔
2288
          *bad_action = actions[i];
×
2289
          return -1;
×
2290
        }
2291
      }
2292
    }
2293
  }
2294

2295
  return 0;
2296
}
2297

2298
char *pr_ctrls_set_module_acls(ctrls_acttab_t *acttab, pool *acl_pool,
11✔
2299
    char **actions, const char *allow, const char *type, char *list) {
2300
  int res;
2301
  char *bad_action = NULL;
11✔
2302

2303
  res = pr_ctrls_set_module_acls2(acttab, acl_pool, actions, allow, type, list,
11✔
2304
    (const char **) &bad_action);
2305
  if (res < 0) {
11✔
2306
    return bad_action;
8✔
2307
  }
2308

2309
  return 0;
2310
}
2311

2312
int pr_ctrls_unregister_module_actions2(ctrls_acttab_t *acttab,
11✔
2313
    char **actions, module *mod, const char **bad_action) {
2314
  register unsigned int i = 0;
11✔
2315

2316
  if (acttab == NULL ||
22✔
2317
      actions == NULL ||
11✔
2318
      mod == NULL ||
14✔
2319
      bad_action == NULL) {
7✔
2320
    errno = EINVAL;
7✔
2321
    return -1;
7✔
2322
  }
2323

2324
  /* First, sanity check the given actions against the actions supported by
2325
   * this module.
2326
   */
2327
  for (i = 0; actions[i]; i++) {
2✔
2328
    register unsigned int j = 0;
2329
    int valid_action = FALSE;
2330

2331
    for (j = 0; acttab[j].act_action; j++) {
2✔
2332
      if (strcmp(actions[i], acttab[j].act_action) == 0) {
4✔
2333
        valid_action = TRUE;
2334
        break;
2335
      }
2336
    }
2337

2338
    if (valid_action == FALSE) {
4✔
2339
      *bad_action = actions[i];
2✔
2340
      errno = EPERM;
2✔
2341
      return -1;
2✔
2342
    }
2343
  }
2344

2345
  /* Next, iterate through both lists again, looking for actions of the
2346
   * module _not_ in the given list.
2347
   */
2348
  for (i = 0; acttab[i].act_action; i++) {
2✔
2349
    register unsigned int j = 0;
2350
    int have_action = FALSE;
2351

2352
    for (j = 0; actions[j]; j++) {
×
2353
      if (strcmp(acttab[i].act_action, actions[j]) == 0) {
2✔
2354
        have_action = TRUE;
2355
        break;
2356
      }
2357
    }
2358

2359
    if (have_action == TRUE) {
2✔
2360
      pr_trace_msg(trace_channel, 4, "mod_%s.c: removing '%s' control",
2✔
2361
        mod->name, acttab[i].act_action);
2362
      pr_ctrls_unregister(mod, acttab[i].act_action);
2✔
2363
      destroy_pool(acttab[i].act_acl->acl_pool);
2✔
2364
    }
2365
  }
2366

2367
  return 0;
2368
}
2369

2370
char *pr_ctrls_unregister_module_actions(ctrls_acttab_t *acttab,
5✔
2371
    char **actions, module *mod) {
2372
  int res;
2373
  char *bad_action = NULL;
5✔
2374

2375
  res = pr_ctrls_unregister_module_actions2(acttab, actions, mod,
5✔
2376
    (const char **) &bad_action);
2377
  if (res < 0) {
5✔
2378
    return bad_action;
4✔
2379
  }
2380

2381
  return 0;
2382
}
2383

2384
int pr_ctrls_set_logfd(int fd) {
4✔
2385

2386
  /* Close any existing log fd. */
2387
  if (ctrls_logfd >= 0) {
4✔
2388
    (void) close(ctrls_logfd);
1✔
2389
  }
2390

2391
  ctrls_logfd = fd;
4✔
2392
  return 0;
4✔
2393
}
2394

2395
int pr_ctrls_log(const char *module_version, const char *fmt, ...) {
4✔
2396
  va_list msg;
2397
  int res;
2398

2399
  if (ctrls_logfd < 0) {
4✔
2400
    return 0;
2401
  }
2402

2403
  if (fmt == NULL) {
3✔
2404
    return 0;
2405
  }
2406

2407
  va_start(msg, fmt);
1✔
2408
  res = pr_log_vwritefile(ctrls_logfd, module_version, fmt, msg);
1✔
2409
  va_end(msg);
1✔
2410

2411
  return res;
1✔
2412
}
2413

2414
static void ctrls_cleanup_cb(void *user_data) {
34✔
2415
  ctrls_pool = NULL;
34✔
2416
  ctrls_action_list = NULL;
34✔
2417
  ctrls_active_list = NULL;
34✔
2418
  ctrls_free_list = NULL;
34✔
2419

2420
  action_lookup_next = NULL;
34✔
2421
  action_lookup_action = NULL;
34✔
2422
  action_lookup_module = NULL;
34✔
2423
}
34✔
2424

2425
/* Initialize the Controls API. */
2426
int init_ctrls2(const char *socket_path) {
34✔
2427
  struct stat st;
2428
  int fd, xerrno;
2429
  struct sockaddr_un sockun;
2430
  size_t socklen;
2431

2432
  if (ctrls_pool != NULL) {
34✔
2433
    destroy_pool(ctrls_pool);
×
2434
  }
2435

2436
  ctrls_pool = make_sub_pool(permanent_pool);
34✔
2437
  pr_pool_tag(ctrls_pool, "Controls Pool");
34✔
2438
  register_cleanup2(ctrls_pool, NULL, ctrls_cleanup_cb);
34✔
2439

2440
  /* Make sure all of the lists are zero'd out. */
2441
  ctrls_action_list = NULL;
34✔
2442
  ctrls_active_list = NULL;
34✔
2443
  ctrls_free_list = NULL;
34✔
2444

2445
   /* And that the lookup indices are (re)set as well... */
2446
  action_lookup_next = NULL;
34✔
2447
  action_lookup_action = NULL;
34✔
2448
  action_lookup_module = NULL;
34✔
2449

2450
  /* Run-time check to find out whether this platform identifies a
2451
   * Unix domain socket file descriptor via the S_ISFIFO macro, or
2452
   * the S_ISSOCK macro.
2453
   */
2454

2455
  fd = socket(AF_UNIX, SOCK_STREAM, 0);
34✔
2456
  xerrno = errno;
34✔
2457

2458
  if (fd < 0) {
34✔
2459
    pr_log_debug(DEBUG10, "unable to create Unix domain socket: %s",
×
2460
      strerror(xerrno));
2461

2462
    errno = xerrno;
×
2463
    return -1;
×
2464
  }
2465

2466
  memset(&sockun, 0, sizeof(sockun));
34✔
2467
  sockun.sun_family = AF_UNIX;
34✔
2468
  sstrncpy(sockun.sun_path, socket_path, sizeof(sockun.sun_path));
34✔
2469
  socklen = sizeof(struct sockaddr_un);
34✔
2470

2471
  if (bind(fd, (struct sockaddr *) &sockun, socklen) < 0) {
34✔
2472
    xerrno = errno;
×
2473

2474
    pr_log_debug(DEBUG10, "unable to bind to Unix domain socket at '%s': %s",
×
2475
      socket_path, strerror(xerrno));
2476
    (void) close(fd);
×
2477
    (void) unlink(socket_path);
×
2478

2479
    errno = xerrno;
×
2480
    return -1;
×
2481
  }
2482

2483
  if (fstat(fd, &st) < 0) {
34✔
2484
    xerrno = errno;
×
2485

2486
    pr_log_debug(DEBUG10, "unable to stat Unix domain socket at '%s': %s",
×
2487
      socket_path, strerror(xerrno));
2488
    (void) close(fd);
×
2489
    (void) unlink(socket_path);
×
2490

2491
    errno = xerrno;
×
2492
    return -1;
×
2493
  }
2494

2495
#if defined(S_ISFIFO)
2496
  pr_trace_msg(trace_channel, 9, "testing Unix domain socket using S_ISFIFO");
34✔
2497
  if (S_ISFIFO(st.st_mode)) {
34✔
2498
    ctrls_use_isfifo = TRUE;
×
2499
  }
2500
#else
2501
  pr_log_debug(DEBUG10, "cannot test Unix domain socket using S_ISFIFO: "
2502
    "macro undefined");
2503
#endif /* S_ISFIFO */
2504

2505
#if defined(S_ISSOCK)
2506
  pr_trace_msg(trace_channel, 9, "testing Unix domain socket using S_ISSOCK");
34✔
2507
  if (S_ISSOCK(st.st_mode)) {
34✔
2508
    ctrls_use_isfifo = FALSE;
34✔
2509
  }
2510
#else
2511
  pr_log_debug(DEBUG10, "cannot test Unix domain socket using S_ISSOCK: "
2512
    "macro undefined");
2513
#endif /* S_ISSOCK */
2514

2515
  pr_trace_msg(trace_channel, 9,
34✔
2516
    "using %s macro for Unix domain socket detection",
2517
    ctrls_use_isfifo ? "S_ISFIFO" : "S_ISSOCK");
34✔
2518

2519
  (void) close(fd);
34✔
2520
  (void) unlink(socket_path);
34✔
2521
  return 0;
34✔
2522
}
2523

2524
void init_ctrls(void) {
×
2525
  const char *socket_path = PR_RUN_DIR "/test.sock";
×
2526

2527
  (void) init_ctrls2(socket_path);
×
2528
}
×
2529
#endif /* PR_USE_CTRLS */
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