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

proftpd / proftpd / 14581522647

21 Apr 2025 09:13PM UTC coverage: 92.667% (-0.4%) from 93.03%
14581522647

push

github

Castaglia
Adding a regression test for Bug#4417.

47172 of 50905 relevant lines covered (92.67%)

250.83 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-2023 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, write to the Free Software
17
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18
 *
19
 * As a special exemption, The ProFTPD Project team and other respective
20
 * copyright holders give permission to link this program with OpenSSL, and
21
 * distribute the resulting executable, without including the source code for
22
 * OpenSSL in the source distribution.
23
 */
24

25
/* Controls API routines */
26

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

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

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

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

42
#if defined(PR_USE_CTRLS)
43

44
#include "mod_ctrls.h"
45

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

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

62
static unsigned char ctrls_blocked = FALSE;
63

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

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

70
static int ctrls_use_isfifo = FALSE;
71

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

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

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

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

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

90
  pr_block_ctrls();
91

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

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

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

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

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

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

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

121
  return act;
12✔
122
}
123

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

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

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

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

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

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

149
  return ctrl;
27✔
150
}
151

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

255
  ctrls_action_list = act;
12✔
256

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

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

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

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

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

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

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

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

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

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

296
  pr_unblock_ctrls();
297

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

303
  return 0;
304
}
305

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

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

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

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

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

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

340
  return 0;
8✔
341
}
342

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

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

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

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

372
  return 0;
2✔
373
}
374

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

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

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

395
  return 0;
1✔
396
}
397

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

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

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

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

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

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

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

433
  return 0;
12✔
434
}
435

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

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

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

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

459
  return 0;
460
}
461

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

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

472
  msglen = strlen(msg);
7✔
473

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

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

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

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

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

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

496
      pr_signals_unblock();
×
497

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

502
    break;
503
  }
504

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

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

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

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

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

533
  json = pr_json_object_alloc(tmp_pool);
2✔
534

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

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

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

546
  args = pr_json_array_alloc(tmp_pool);
2✔
547

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

801
  return 0;
5✔
802
}
803

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

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

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

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

827
  json = pr_json_object_alloc(tmp_pool);
5✔
828

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

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

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

841
  resps = pr_json_array_alloc(tmp_pool);
5✔
842

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1045
  return respargc;
1✔
1046
}
1047

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

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

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

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

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

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

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

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

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

1088
  return NULL;
1089
}
1090

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

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

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

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

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

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

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

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

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

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

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

1144
  return count;
1145
}
1146

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

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

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

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

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

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

1187
  return 0;
1188
}
1189

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

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

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

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

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

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

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

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

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

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

1237
    pr_signals_unblock();
×
1238

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1339
int pr_ctrls_issock_unix(mode_t sock_mode) {
3✔
1340

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1460
  char control[MINCREDSIZE];
1461

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1553
    return 0;
1554
  }
1555

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1641
    tmp_pool = make_sub_pool(permanent_pool);
×
1642

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

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

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

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

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

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

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

1673
  return 0;
1674
}
1675

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

1682
  len = sizeof(sock);
2✔
1683

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

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

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

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

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

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

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

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

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

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

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

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

1746
  return cl_fd;
×
1747
}
1748

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

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

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

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

1773
  return 0;
1774
}
1775

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

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

1786
  now = time(NULL);
11✔
1787

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

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

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

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

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

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

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

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

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

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

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

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

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

1864
  return 0;
1865
}
1866

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

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

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

1894
  return 0;
2✔
1895
}
1896

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

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

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

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

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

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

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

1931
  return res;
1932
}
1933

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

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

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

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

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

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

1966
  return res;
1967
}
1968

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

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

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

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

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

2000
  return TRUE;
2001
}
2002

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

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

2012
  return 0;
6✔
2013
}
2014

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

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

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

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

2035
  ret = dst = *arg;
34✔
2036

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2116
  tmp_pool = make_sub_pool(group_acl_pool);
4✔
2117

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

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

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

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

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

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

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

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

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

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

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

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

2174
  tmp_pool = make_sub_pool(user_acl_pool);
6✔
2175

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2296
  return 0;
2297
}
2298

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

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

2310
  return 0;
2311
}
2312

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

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

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

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

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

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

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

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

2368
  return 0;
2369
}
2370

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

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

2382
  return 0;
2383
}
2384

2385
int pr_ctrls_set_logfd(int fd) {
4✔
2386

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

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

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

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

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

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

2412
  return res;
1✔
2413
}
2414

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2528
  (void) init_ctrls2(socket_path);
×
2529
}
×
2530
#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