Coverage Report

Created: 2024-09-30 06:24

/src/proftpd/src/scoreboard.c
Line
Count
Source (jump to first uncovered line)
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 and other respective copyright
20
 * holders give permission to link this program with OpenSSL, and distribute
21
 * the resulting executable, without including the source code for OpenSSL in
22
 * the source distribution.
23
 */
24
25
/* ProFTPD scoreboard support. */
26
27
#include "conf.h"
28
#include "privs.h"
29
30
/* From src/dirtree.c */
31
extern char ServerType;
32
33
static pid_t scoreboard_opener = 0;
34
35
static int scoreboard_engine = TRUE;
36
static int scoreboard_fd = -1;
37
static char scoreboard_file[PR_TUNABLE_PATH_MAX] = PR_RUN_DIR "/proftpd.scoreboard";
38
39
static int scoreboard_mutex_fd = -1;
40
static char scoreboard_mutex[PR_TUNABLE_PATH_MAX] = PR_RUN_DIR "/proftpd.scoreboard.lck";
41
42
static off_t current_pos = 0;
43
static pr_scoreboard_header_t header;
44
static pr_scoreboard_entry_t entry;
45
static int have_entry = FALSE;
46
static struct flock entry_lock;
47
48
static unsigned char scoreboard_read_locked = FALSE;
49
static unsigned char scoreboard_write_locked = FALSE;
50
51
/* Max number of attempts for lock requests */
52
#if !defined(SCOREBOARD_MAX_LOCK_ATTEMPTS)
53
0
# define SCOREBOARD_MAX_LOCK_ATTEMPTS 10
54
#endif
55
56
static const char *trace_channel = "scoreboard";
57
58
/* Internal routines */
59
60
0
static char *handle_score_str(const char *fmt, va_list cmdap) {
61
0
  static char buf[PR_TUNABLE_SCOREBOARD_BUFFER_SIZE] = {'\0'};
62
0
  memset(buf, '\0', sizeof(buf));
63
64
  /* Note that we deliberately do NOT use pr_vsnprintf() here, since
65
   * truncation of long strings is often normal for these entries; consider
66
   * paths longer than PR_TUNABLE_SCOREBOARD_BUFFER_SIZE (Issue#683).
67
   */
68
0
  vsnprintf(buf, sizeof(buf)-1, fmt, cmdap);
69
70
0
  buf[sizeof(buf)-1] = '\0';
71
0
  return buf;
72
0
}
73
74
0
static int read_scoreboard_header(pr_scoreboard_header_t *sch) {
75
0
  int res = 0;
76
77
0
  pr_trace_msg(trace_channel, 7, "reading scoreboard header");
78
79
  /* NOTE: reading a struct from a file using read(2) -- bad (in general).
80
   * Better would be to use readv(2).  Should also handle short-reads here.
81
   */
82
0
  res = read(scoreboard_fd, sch, sizeof(pr_scoreboard_header_t));
83
0
  while (res != sizeof(pr_scoreboard_header_t)) {
84
0
    if (res == 0) {
85
0
      errno = EIO;
86
0
      return -1;
87
0
    }
88
89
0
    if (errno == EINTR) {
90
0
      pr_signals_handle();
91
0
      continue;
92
0
    }
93
94
0
    return -1;
95
0
  }
96
97
  /* Note: these errors will most likely occur only for inetd-run daemons.
98
   * Standalone daemons erase the scoreboard on startup.
99
   */
100
101
0
  if (sch->sch_magic != PR_SCOREBOARD_MAGIC) {
102
0
    pr_trace_msg(trace_channel, 3, "scoreboard header magic %lu (expected %lu)",
103
0
      sch->sch_magic, (unsigned long) PR_SCOREBOARD_MAGIC);
104
0
    (void) pr_close_scoreboard(FALSE);
105
0
    return PR_SCORE_ERR_BAD_MAGIC;
106
0
  }
107
108
0
  if (sch->sch_version < PR_SCOREBOARD_VERSION) {
109
0
    pr_trace_msg(trace_channel, 3,
110
0
      "scoreboard header version %lu too old (expected %lu)",
111
0
      sch->sch_version, (unsigned long) PR_SCOREBOARD_VERSION);
112
0
    (void) pr_close_scoreboard(FALSE);
113
0
    return PR_SCORE_ERR_OLDER_VERSION;
114
0
  }
115
116
0
  if (sch->sch_version > PR_SCOREBOARD_VERSION) {
117
0
    pr_trace_msg(trace_channel, 3,
118
0
      "scoreboard header version %lu too new (expected %lu)",
119
0
      sch->sch_version, (unsigned long) PR_SCOREBOARD_VERSION);
120
0
    (void) pr_close_scoreboard(FALSE);
121
0
    return PR_SCORE_ERR_NEWER_VERSION;
122
0
  }
123
124
0
  return 0;
125
0
}
126
127
0
static const char *get_lock_type(struct flock *lock) {
128
0
  const char *lock_type;
129
130
0
  switch (lock->l_type) {
131
0
    case F_RDLCK:
132
0
      lock_type = "read-lock";
133
0
      break;
134
135
0
    case F_WRLCK:
136
0
      lock_type = "write-lock";
137
0
      break;
138
139
0
    case F_UNLCK:
140
0
      lock_type = "unlock";
141
0
      break;
142
143
0
    default:
144
0
      errno = EINVAL;
145
0
      lock_type = NULL;
146
0
  }
147
148
0
  return lock_type;
149
0
}
150
151
0
int pr_lock_scoreboard(int mutex_fd, int lock_type) {
152
0
  struct flock lock;
153
0
  unsigned int nattempts = 1;
154
0
  const char *lock_label;
155
156
0
  lock.l_type = lock_type;
157
0
  lock.l_whence = SEEK_SET;
158
0
  lock.l_start = 0;
159
0
  lock.l_len = 0;
160
161
0
  lock_label = get_lock_type(&lock);
162
0
  if (lock_label == NULL) {
163
0
    return -1;
164
0
  }
165
166
0
  pr_trace_msg("lock", 9, "attempt #%u to %s scoreboard mutex fd %d",
167
0
    nattempts, lock_label, mutex_fd);
168
169
0
  while (fcntl(mutex_fd, F_SETLK, &lock) < 0) {
170
0
    int xerrno = errno;
171
172
0
    if (xerrno == EINTR) {
173
0
      pr_signals_handle();
174
0
      continue;
175
0
    }
176
177
0
    pr_trace_msg("lock", 3,
178
0
      "%s (attempt #%u) of scoreboard mutex fd %d failed: %s", lock_label,
179
0
      nattempts, mutex_fd, strerror(xerrno));
180
0
    if (xerrno == EACCES) {
181
0
      struct flock locker;
182
183
      /* Get the PID of the process blocking this lock. */
184
0
      if (fcntl(mutex_fd, F_GETLK, &locker) == 0) {
185
0
        pr_trace_msg("lock", 3, "process ID %lu has blocking %s on "
186
0
          "scoreboard mutex fd %d", (unsigned long) locker.l_pid,
187
0
          get_lock_type(&locker), mutex_fd);
188
0
      }
189
0
    }
190
191
0
    if (xerrno == EAGAIN ||
192
0
        xerrno == EACCES) {
193
      /* Treat this as an interrupted call, call pr_signals_handle() (which
194
       * will delay for a few msecs because of EINTR), and try again.
195
       * After MAX_LOCK_ATTEMPTS attempts, give up altogether.
196
       */
197
198
0
      nattempts++;
199
0
      if (nattempts <= SCOREBOARD_MAX_LOCK_ATTEMPTS) {
200
0
        errno = EINTR;
201
202
0
        pr_signals_handle();
203
204
0
        errno = 0;
205
0
        pr_trace_msg("lock", 9,
206
0
          "attempt #%u to %s scoreboard mutex fd %d", nattempts, lock_label,
207
0
          mutex_fd);
208
0
        continue;
209
0
      }
210
211
0
      pr_trace_msg("lock", 9, "unable to acquire %s on scoreboard mutex fd %d "
212
0
        "after %u attempts: %s", lock_label, mutex_fd, nattempts,
213
0
        strerror(xerrno));
214
0
    }
215
216
0
    errno = xerrno;
217
0
    return -1;
218
0
  }
219
220
0
  pr_trace_msg("lock", 9,
221
0
    "%s of scoreboard mutex fd %d successful after %u %s", lock_label,
222
0
    mutex_fd, nattempts, nattempts != 1 ? "attempts" : "attempt");
223
224
0
  return 0;
225
0
}
226
227
0
static int rlock_scoreboard(void) {
228
0
  int res;
229
230
0
  res = pr_lock_scoreboard(scoreboard_mutex_fd, F_RDLCK);
231
0
  if (res == 0) {
232
0
    scoreboard_read_locked = TRUE;
233
0
  }
234
235
0
  return res;
236
0
}
237
238
0
static int wlock_scoreboard(void) {
239
0
  int res;
240
241
0
  res = pr_lock_scoreboard(scoreboard_mutex_fd, F_WRLCK);
242
0
  if (res == 0) {
243
0
    scoreboard_write_locked = TRUE;
244
0
  }
245
246
0
  return res;
247
0
}
248
249
0
static int unlock_scoreboard(void) {
250
0
  int res;
251
252
0
  res = pr_lock_scoreboard(scoreboard_mutex_fd, F_UNLCK);
253
0
  if (res == 0) {
254
0
    scoreboard_read_locked = scoreboard_write_locked = FALSE;
255
0
  }
256
257
0
  return res;
258
0
}
259
260
0
int pr_scoreboard_entry_lock(int fd, int lock_type) {
261
0
  unsigned int nattempts = 1;
262
0
  const char *lock_label;
263
264
0
  entry_lock.l_type = lock_type;
265
0
  entry_lock.l_whence = SEEK_CUR;
266
0
  entry_lock.l_len = sizeof(pr_scoreboard_entry_t);
267
268
0
  lock_label = get_lock_type(&entry_lock);
269
0
  if (lock_label == NULL) {
270
0
    return -1;
271
0
  }
272
273
0
  pr_trace_msg("lock", 9, "attempting to %s scoreboard fd %d entry, "
274
0
    "offset %" PR_LU, lock_label, fd, (pr_off_t) entry_lock.l_start);
275
276
0
  while (fcntl(fd, F_SETLK, &entry_lock) < 0) {
277
0
    int xerrno = errno;
278
279
0
    if (xerrno == EINTR) {
280
0
      pr_signals_handle();
281
0
      continue;
282
0
    }
283
284
0
    if (xerrno == EAGAIN) {
285
      /* Treat this as an interrupted call, call pr_signals_handle() (which
286
       * will delay for a few msecs because of EINTR), and try again.
287
       * After MAX_LOCK_ATTEMPTS attempts, give up altogether.
288
       */
289
290
0
      nattempts++;
291
0
      if (nattempts <= SCOREBOARD_MAX_LOCK_ATTEMPTS) {
292
0
        errno = EINTR;
293
294
0
        pr_signals_handle();
295
296
0
        errno = 0;
297
0
        pr_trace_msg("lock", 9,
298
0
          "attempt #%u to to %s scoreboard fd %d entry, offset %" PR_LU,
299
0
          nattempts, lock_label, fd, (pr_off_t) entry_lock.l_start);
300
0
        continue;
301
0
      }
302
0
    }
303
304
0
    pr_trace_msg("lock", 3, "%s of scoreboard fd %d entry failed: %s",
305
0
      lock_label, fd, strerror(xerrno));
306
307
0
    errno = xerrno;
308
0
    return -1;
309
0
  }
310
311
0
  pr_trace_msg("lock", 9, "%s of scoreboard fd %d entry, "
312
0
    "offset %" PR_LU " succeeded", lock_label, fd,
313
0
    (pr_off_t) entry_lock.l_start);
314
315
0
  return 0;
316
0
}
317
318
0
static int unlock_entry(int fd) {
319
0
  int res;
320
321
0
  res = pr_scoreboard_entry_lock(fd, F_UNLCK);
322
0
  return res;
323
0
}
324
325
0
static int wlock_entry(int fd) {
326
0
  int res;
327
328
0
  res = pr_scoreboard_entry_lock(fd, F_UNLCK);
329
0
  return res;
330
0
}
331
332
0
static int write_entry(int fd) {
333
0
  int res;
334
335
0
  if (fd < 0) {
336
0
    errno = EINVAL;
337
0
    return -1;
338
0
  }
339
340
#if !defined(HAVE_PWRITE)
341
  if (lseek(fd, entry_lock.l_start, SEEK_SET) < 0) {
342
    return -1;
343
  }
344
#endif /* HAVE_PWRITE */
345
346
0
#if defined(HAVE_PWRITE)
347
0
  res = pwrite(fd, &entry, sizeof(entry), entry_lock.l_start);
348
#else
349
  res = write(fd, &entry, sizeof(entry));
350
#endif /* HAVE_PWRITE */
351
352
0
  while (res != sizeof(entry)) {
353
0
    if (res < 0) {
354
0
      if (errno == EINTR) {
355
0
        pr_signals_handle();
356
0
#if defined(HAVE_PWRITE)
357
0
        res = pwrite(fd, &entry, sizeof(entry), entry_lock.l_start);
358
#else
359
        res = write(fd, &entry, sizeof(entry));
360
#endif /* HAVE_PWRITE */
361
0
        continue;
362
0
      }
363
364
0
      return -1;
365
0
    }
366
367
    /* Watch out for short writes here. */
368
0
    pr_log_pri(PR_LOG_NOTICE,
369
0
      "error updating scoreboard entry: only wrote %d of %lu bytes", res,
370
0
      (unsigned long) sizeof(entry));
371
0
    errno = EIO;
372
0
    return -1;
373
0
  }
374
375
#if !defined(HAVE_PWRITE)
376
  /* Rewind. */
377
  if (lseek(fd, entry_lock.l_start, SEEK_SET) < 0) {
378
    return -1;
379
  }
380
#endif /* HAVE_PWRITE */
381
382
0
  return 0;
383
0
}
384
385
/* Public routines */
386
387
0
int pr_close_scoreboard(int keep_mutex) {
388
0
  if (scoreboard_engine == FALSE) {
389
0
    return 0;
390
0
  }
391
392
0
  if (scoreboard_fd == -1) {
393
0
    return 0;
394
0
  }
395
396
0
  if (scoreboard_read_locked ||
397
0
      scoreboard_write_locked) {
398
0
    unlock_scoreboard();
399
0
  }
400
401
0
  pr_trace_msg(trace_channel, 4, "closing scoreboard fd %d", scoreboard_fd);
402
403
0
  (void) close(scoreboard_fd);
404
0
  scoreboard_fd = -1;
405
406
0
  if (keep_mutex == FALSE) {
407
0
    pr_trace_msg(trace_channel, 4, "closing scoreboard mutex fd %d",
408
0
      scoreboard_mutex_fd);
409
410
0
    (void) close(scoreboard_mutex_fd);
411
0
    scoreboard_mutex_fd = -1;
412
0
  }
413
414
0
  scoreboard_opener = 0;
415
0
  return 0;
416
0
}
417
418
0
void pr_delete_scoreboard(void) {
419
0
  if (scoreboard_engine == FALSE) {
420
0
    return;
421
0
  }
422
423
0
  if (scoreboard_fd > -1) {
424
0
    (void) close(scoreboard_fd);
425
0
  }
426
427
0
  if (scoreboard_mutex_fd > -1) {
428
0
    (void) close(scoreboard_mutex_fd);
429
0
  }
430
431
0
  scoreboard_fd = -1;
432
0
  scoreboard_mutex_fd = -1;
433
0
  scoreboard_opener = 0;
434
435
  /* As a performance hack, setting "ScoreboardFile /dev/null" makes
436
   * proftpd write all its scoreboard entries to /dev/null.  But we don't
437
   * want proftpd to delete /dev/null.
438
   */
439
0
  if (*scoreboard_file &&
440
0
      strcmp(scoreboard_file, "/dev/null") != 0) {
441
0
    struct stat st;
442
443
0
    if (stat(scoreboard_file, &st) == 0) {
444
0
      pr_log_debug(DEBUG3, "deleting existing scoreboard '%s'",
445
0
        scoreboard_file);
446
0
    }
447
448
0
    (void) unlink(scoreboard_file);
449
0
    (void) unlink(scoreboard_mutex);
450
0
  }
451
452
0
  if (*scoreboard_mutex) {
453
0
    struct stat st;
454
455
0
    if (stat(scoreboard_mutex, &st) == 0) {
456
0
      pr_log_debug(DEBUG3, "deleting existing scoreboard mutex '%s'",
457
0
        scoreboard_mutex);
458
0
    }
459
460
0
    (void) unlink(scoreboard_mutex);
461
0
  }
462
0
}
463
464
0
const char *pr_get_scoreboard(void) {
465
0
  return scoreboard_file;
466
0
}
467
468
0
const char *pr_get_scoreboard_mutex(void) {
469
0
  return scoreboard_mutex;
470
0
}
471
472
0
int pr_open_scoreboard(int flags) {
473
0
  int res;
474
0
  struct stat st;
475
476
0
  if (scoreboard_engine == FALSE) {
477
0
    return 0;
478
0
  }
479
480
0
  if (flags != O_RDWR) {
481
0
    errno = EINVAL;
482
0
    return -1;
483
0
  }
484
485
  /* Try to prevent a file descriptor leak by only opening the scoreboard
486
   * file if the scoreboard file descriptor is not already positive, i.e.
487
   * if the scoreboard has not already been opened.
488
   */
489
0
  if (scoreboard_fd >= 0 &&
490
0
      scoreboard_opener == getpid()) {
491
0
    pr_log_debug(DEBUG7, "scoreboard already opened");
492
0
    return 0;
493
0
  }
494
495
  /* Check for symlinks prior to opening the file. */
496
0
  if (lstat(scoreboard_file, &st) == 0) {
497
0
    if (S_ISLNK(st.st_mode)) {
498
0
      scoreboard_fd = -1;
499
0
      errno = EPERM;
500
0
      return -1;
501
0
    }
502
0
  }
503
504
0
  if (lstat(scoreboard_mutex, &st) == 0) {
505
0
    if (S_ISLNK(st.st_mode)) {
506
0
      errno = EPERM;
507
0
      return -1;
508
0
    }
509
0
  }
510
511
0
  pr_log_debug(DEBUG7, "opening scoreboard '%s'", scoreboard_file);
512
513
0
  scoreboard_fd = open(scoreboard_file, flags|O_CREAT, PR_SCOREBOARD_MODE);
514
0
  while (scoreboard_fd < 0) {
515
0
    if (errno == EINTR) {
516
0
      pr_signals_handle();
517
0
      scoreboard_fd = open(scoreboard_file, flags|O_CREAT, PR_SCOREBOARD_MODE);
518
0
      continue;
519
0
    }
520
521
0
    return -1;
522
0
  }
523
524
  /* Find a usable fd for the just-opened scoreboard fd. */
525
0
  if (pr_fs_get_usable_fd2(&scoreboard_fd) < 0) {
526
0
    pr_log_debug(DEBUG0, "warning: unable to find good fd for ScoreboardFile "
527
0
      "fd %d: %s", scoreboard_fd, strerror(errno));
528
0
  }
529
530
  /* Make certain that the scoreboard mode will be read-only for everyone
531
   * except the user owner (this allows for non-root-running daemons to
532
   * still modify the scoreboard).
533
   */
534
0
  while (fchmod(scoreboard_fd, 0644) < 0) {
535
0
    if (errno == EINTR) {
536
0
      pr_signals_handle();
537
0
      continue;
538
0
    }
539
540
0
    break;
541
0
  }
542
543
  /* Make sure the ScoreboardMutex file exists.  We keep a descriptor to the
544
   * ScoreboardMutex open just as we do for the ScoreboardFile, for the same
545
   * reasons: we need to able to use the descriptor throughout the lifetime of
546
   * the session despite any possible chroot, and we get a minor system call
547
   * saving by not calling open(2)/close(2) repeatedly to get the descriptor
548
   * (at the cost of having another open fd for the lifetime of the session
549
   * process).
550
   */
551
0
  if (scoreboard_mutex_fd == -1) {
552
0
    scoreboard_mutex_fd = open(scoreboard_mutex, flags|O_CREAT,
553
0
      PR_SCOREBOARD_MODE);
554
0
    while (scoreboard_mutex_fd < 0) {
555
0
      int xerrno = errno;
556
557
0
      if (errno == EINTR) {
558
0
        pr_signals_handle();
559
0
        scoreboard_mutex_fd = open(scoreboard_mutex, flags|O_CREAT,
560
0
          PR_SCOREBOARD_MODE);
561
0
        continue;
562
0
      }
563
564
0
      (void) close(scoreboard_fd);
565
0
      scoreboard_fd = -1;
566
567
0
      pr_trace_msg(trace_channel, 9, "error opening ScoreboardMutex '%s': %s",
568
0
        scoreboard_mutex, strerror(xerrno));
569
570
0
      errno = xerrno;
571
0
      return -1;
572
0
    }
573
574
    /* Find a usable fd for the just-opened mutex fd. */
575
0
    if (pr_fs_get_usable_fd2(&scoreboard_mutex_fd) < 0) {
576
0
      pr_log_debug(DEBUG0, "warning: unable to find good fd for "
577
0
        "ScoreboardMutex fd %d: %s", scoreboard_mutex_fd, strerror(errno));
578
0
    }
579
580
0
  } else {
581
0
    pr_trace_msg(trace_channel, 9, "using already-open scoreboard mutex fd %d",
582
0
      scoreboard_mutex_fd);
583
0
  }
584
585
0
  scoreboard_opener = getpid();
586
587
  /* Check the header of this scoreboard file. */
588
0
  res = read_scoreboard_header(&header);
589
0
  if (res == -1) {
590
    /* If this file is newly created, it needs to have the header
591
     * written.
592
     */
593
0
    header.sch_magic = PR_SCOREBOARD_MAGIC;
594
0
    header.sch_version = PR_SCOREBOARD_VERSION;
595
596
0
    if (ServerType == SERVER_STANDALONE) {
597
0
      header.sch_pid = getpid();
598
0
      header.sch_uptime = time(NULL);
599
600
0
    } else {
601
0
      header.sch_pid = 0;
602
0
      header.sch_uptime = 0;
603
0
    }
604
605
    /* Write-lock the scoreboard file. */
606
0
    PR_DEVEL_CLOCK(res = wlock_scoreboard());
607
0
    if (res < 0) {
608
0
      int xerrno = errno;
609
610
0
      (void) close(scoreboard_mutex_fd);
611
0
      scoreboard_mutex_fd = -1;
612
613
0
      (void) close(scoreboard_fd);
614
0
      scoreboard_fd = -1;
615
616
0
      errno = xerrno;
617
0
      return -1;
618
0
    }
619
620
0
    pr_trace_msg(trace_channel, 7, "writing scoreboard header");
621
622
0
    while (write(scoreboard_fd, &header, sizeof(header)) != sizeof(header)) {
623
0
      int xerrno = errno;
624
625
0
      if (errno == EINTR) {
626
0
        pr_signals_handle();
627
0
        continue;
628
0
      }
629
630
0
      unlock_scoreboard();
631
632
0
      (void) close(scoreboard_mutex_fd);
633
0
      scoreboard_mutex_fd = -1;
634
635
0
      (void) close(scoreboard_fd);
636
0
      scoreboard_fd = -1;
637
638
0
      errno = xerrno;
639
0
      return -1;
640
0
    }
641
642
0
    unlock_scoreboard();
643
0
    return 0;
644
0
  }
645
646
0
  return res;
647
0
}
648
649
0
int pr_restore_scoreboard(void) {
650
0
  if (scoreboard_engine == FALSE) {
651
0
    return 0;
652
0
  }
653
654
0
  if (scoreboard_fd < 0) {
655
0
    errno = EINVAL;
656
0
    return -1;
657
0
  }
658
659
0
  if (current_pos == 0) {
660
    /* This can happen if pr_restore_scoreboard() is called BEFORE
661
     * pr_rewind_scoreboard() has been called.
662
     */
663
0
    errno = EPERM;
664
0
    return -1;
665
0
  }
666
667
  /* Position the file position pointer of the scoreboard back to
668
   * where it was, prior to the last pr_rewind_scoreboard() call.
669
   */
670
0
  if (lseek(scoreboard_fd, current_pos, SEEK_SET) == (off_t) -1) {
671
0
    return -1;
672
0
  }
673
674
0
  return 0;
675
0
}
676
677
0
int pr_rewind_scoreboard(void) {
678
0
  off_t res;
679
680
0
  if (scoreboard_engine == FALSE) {
681
0
    return 0;
682
0
  }
683
684
0
  if (scoreboard_fd < 0) {
685
0
    errno = EINVAL;
686
0
    return -1;
687
0
  }
688
689
0
  res = lseek(scoreboard_fd, (off_t) 0, SEEK_CUR);
690
0
  if (res == (off_t) -1) {
691
0
    return -1;
692
0
  }
693
694
0
  current_pos = res;
695
696
  /* Position the file position pointer of the scoreboard at the
697
   * start of the scoreboard (past the header).
698
   */
699
0
  if (lseek(scoreboard_fd, (off_t) sizeof(pr_scoreboard_header_t),
700
0
      SEEK_SET) == (off_t) -1) {
701
0
    return -1;
702
0
  }
703
704
0
  return 0;
705
0
}
706
707
0
static int set_scoreboard_path(const char *path) {
708
0
  char dir[PR_TUNABLE_PATH_MAX] = {'\0'};
709
0
  struct stat st;
710
0
  char *ptr = NULL;
711
712
0
  if (*path != '/') {
713
0
    errno = EINVAL;
714
0
    return -1;
715
0
  }
716
717
0
  sstrncpy(dir, path, sizeof(dir));
718
719
0
  ptr = strrchr(dir + 1, '/');
720
0
  if (ptr == NULL) {
721
0
    errno = EINVAL;
722
0
    return -1;
723
0
  }
724
725
0
  *ptr = '\0';
726
727
  /* Check for the possibility that the '/' just found is at the end
728
   * of the given string.
729
   */
730
0
  if (*(ptr + 1) == '\0') {
731
0
    *ptr = '/';
732
0
    errno = EINVAL;
733
0
    return -1;
734
0
  }
735
736
  /* Parent directory must not be world-writable */
737
738
0
  if (stat(dir, &st) < 0) {
739
0
    return -1;
740
0
  }
741
742
0
  if (!S_ISDIR(st.st_mode)) {
743
0
    errno = ENOTDIR;
744
0
    return -1;
745
0
  }
746
747
0
  if (st.st_mode & S_IWOTH) {
748
0
    errno = EPERM;
749
0
    return -1;
750
0
  }
751
752
0
  return 0;
753
0
}
754
755
0
int pr_set_scoreboard(const char *path) {
756
757
  /* By default, scoreboarding is enabled. */
758
0
  scoreboard_engine = TRUE;
759
760
0
  if (path == NULL) {
761
0
    errno = EINVAL;
762
0
    return -1;
763
0
  }
764
765
  /* Check to see if the given path is "off" or something related, i.e. is
766
   * telling us to disable scoreboarding.  Other ways of disabling
767
   * scoreboarding are to configure a path of "none", or "/dev/null".
768
   */
769
0
  if (pr_str_is_boolean(path) == FALSE) {
770
0
    pr_trace_msg(trace_channel, 3,
771
0
      "ScoreboardFile set to '%s', disabling scoreboarding", path);
772
0
    scoreboard_engine = FALSE;
773
0
    return 0;
774
0
  }
775
776
0
  if (strcasecmp(path, "none") == 0) {
777
0
    pr_trace_msg(trace_channel, 3,
778
0
      "ScoreboardFile set to '%s', disabling scoreboarding", path);
779
0
    scoreboard_engine = FALSE;
780
0
    return 0;
781
0
  }
782
783
0
  if (strcmp(path, "/dev/null") == 0) {
784
0
    pr_trace_msg(trace_channel, 3,
785
0
      "ScoreboardFile set to '%s', disabling scoreboarding", path);
786
0
    scoreboard_engine = FALSE;
787
0
    return 0;
788
0
  }
789
790
0
  if (set_scoreboard_path(path) < 0) {
791
0
    return -1;
792
0
  }
793
794
0
  sstrncpy(scoreboard_file, path, sizeof(scoreboard_file));
795
796
  /* For best operability, automatically set the ScoreboardMutex file to
797
   * be the same as the ScoreboardFile with a ".lck" suffix.
798
   */
799
0
  sstrncpy(scoreboard_mutex, path, sizeof(scoreboard_file));
800
0
  strncat(scoreboard_mutex, ".lck", sizeof(scoreboard_mutex)-strlen(path)-1);
801
802
0
  return 0;
803
0
}
804
805
0
int pr_set_scoreboard_mutex(const char *path) {
806
0
  if (path == NULL) {
807
0
    errno = EINVAL;
808
0
    return -1;
809
0
  }
810
811
0
  sstrncpy(scoreboard_mutex, path, sizeof(scoreboard_mutex));
812
0
  return 0;
813
0
}
814
815
0
int pr_scoreboard_entry_add(void) {
816
0
  int res;
817
0
  unsigned char found_slot = FALSE;
818
819
0
  if (scoreboard_engine == FALSE) {
820
0
    return 0;
821
0
  }
822
823
0
  if (scoreboard_fd < 0) {
824
0
    errno = EINVAL;
825
0
    return -1;
826
0
  }
827
828
0
  if (have_entry) {
829
0
    pr_trace_msg(trace_channel, 9,
830
0
      "unable to add scoreboard entry: already have entry");
831
0
    errno = EPERM;
832
0
    return -1;
833
0
  }
834
835
0
  pr_trace_msg(trace_channel, 3, "adding new scoreboard entry");
836
837
  /* Write-lock the scoreboard file. */
838
0
  PR_DEVEL_CLOCK(res = wlock_scoreboard());
839
0
  if (res < 0) {
840
0
    return -1;
841
0
  }
842
843
  /* No interruptions, please. */
844
0
  pr_signals_block();
845
846
  /* If the scoreboard is open, the file position is already past the
847
   * header.
848
   */
849
0
  while (TRUE) {
850
0
    while ((res = read(scoreboard_fd, &entry, sizeof(entry))) ==
851
0
        sizeof(entry)) {
852
853
      /* If this entry's PID is marked as zero, it means this slot can be
854
       * reused.
855
       */
856
0
      if (!entry.sce_pid) {
857
0
        entry_lock.l_start = lseek(scoreboard_fd, (off_t) 0, SEEK_CUR) - sizeof(entry);
858
0
        found_slot = TRUE;
859
0
        break;
860
0
      }
861
0
    }
862
863
0
    if (res == 0) {
864
0
      entry_lock.l_start = lseek(scoreboard_fd, (off_t) 0, SEEK_CUR);
865
0
      found_slot = TRUE;
866
0
    }
867
868
0
    if (found_slot) {
869
0
      break;
870
0
    }
871
0
  }
872
873
0
  memset(&entry, '\0', sizeof(entry));
874
875
0
  entry.sce_pid = session.pid ? session.pid : getpid();
876
0
  entry.sce_uid = geteuid();
877
0
  entry.sce_gid = getegid();
878
879
0
  res = write_entry(scoreboard_fd);
880
0
  if (res < 0) {
881
0
    pr_log_pri(PR_LOG_NOTICE, "error writing scoreboard entry: %s",
882
0
      strerror(errno));
883
884
0
  } else {
885
0
    have_entry = TRUE;
886
0
  }
887
888
0
  pr_signals_unblock();
889
890
  /* We can unlock the scoreboard now. */
891
0
  unlock_scoreboard();
892
893
0
  return res;
894
0
}
895
896
0
int pr_scoreboard_entry_del(unsigned char verbose) {
897
0
  if (scoreboard_engine == FALSE) {
898
0
    return 0;
899
0
  }
900
901
0
  if (scoreboard_fd < 0) {
902
0
    errno = EINVAL;
903
0
    return -1;
904
0
  }
905
906
0
  if (!have_entry) {
907
0
    errno = ENOENT;
908
0
    return -1;
909
0
  }
910
911
0
  pr_trace_msg(trace_channel, 3, "deleting scoreboard entry");
912
913
0
  memset(&entry, '\0', sizeof(entry));
914
915
  /* Write-lock this entry */
916
0
  wlock_entry(scoreboard_fd);
917
918
  /* Write-lock the scoreboard (using the ScoreboardMutex), since new
919
   * connections might try to use the slot being opened up here.
920
   */
921
0
  wlock_scoreboard();
922
923
0
  if (write_entry(scoreboard_fd) < 0 &&
924
0
      verbose) {
925
0
    pr_log_pri(PR_LOG_NOTICE, "error deleting scoreboard entry: %s",
926
0
      strerror(errno));
927
0
  }
928
929
0
  have_entry = FALSE;
930
0
  unlock_scoreboard();
931
0
  unlock_entry(scoreboard_fd);
932
933
0
  return 0;
934
0
}
935
936
0
pid_t pr_scoreboard_get_daemon_pid(void) {
937
0
  if (scoreboard_engine == FALSE) {
938
0
    return 0;
939
0
  }
940
941
0
  return header.sch_pid;
942
0
}
943
944
0
time_t pr_scoreboard_get_daemon_uptime(void) {
945
0
  if (scoreboard_engine == FALSE) {
946
0
    return 0;
947
0
  }
948
949
0
  return header.sch_uptime;
950
0
}
951
952
0
pr_scoreboard_entry_t *pr_scoreboard_entry_read(void) {
953
0
  static pr_scoreboard_entry_t scan_entry;
954
0
  int res = 0;
955
956
0
  if (scoreboard_engine == FALSE) {
957
0
    return NULL;
958
0
  }
959
960
0
  if (scoreboard_fd < 0) {
961
0
    errno = EINVAL;
962
0
    return NULL;
963
0
  }
964
965
  /* Make sure the scoreboard file is read-locked. */
966
0
  if (!scoreboard_read_locked) {
967
968
    /* Do not proceed if we cannot lock the scoreboard. */
969
0
    res = rlock_scoreboard();
970
0
    if (res < 0) {
971
0
      return NULL;
972
0
    }
973
0
  }
974
975
0
  pr_trace_msg(trace_channel, 5, "reading scoreboard entry");
976
977
0
  memset(&scan_entry, '\0', sizeof(scan_entry));
978
979
  /* NOTE: use readv(2), pread(2)? */
980
0
  while (TRUE) {
981
0
    while ((res = read(scoreboard_fd, &scan_entry, sizeof(scan_entry))) <= 0) {
982
0
      int xerrno = errno;
983
984
0
      if (res < 0 &&
985
0
          xerrno == EINTR) {
986
0
        pr_signals_handle();
987
0
        continue;
988
0
      }
989
990
0
      unlock_scoreboard();
991
0
      errno = xerrno;
992
0
      return NULL;
993
0
    }
994
995
0
    if (scan_entry.sce_pid) {
996
0
      unlock_scoreboard();
997
0
      return &scan_entry;
998
0
    }
999
0
  }
1000
1001
  /* Technically we never reach this. */
1002
0
  return NULL;
1003
0
}
1004
1005
/* We get clever with the next functions, so that they can be used for
1006
 * various entry attributes.
1007
 */
1008
1009
0
const char *pr_scoreboard_entry_get(int field) {
1010
0
  if (scoreboard_engine == FALSE) {
1011
0
    errno = ENOENT;
1012
0
    return NULL;
1013
0
  }
1014
1015
0
  if (scoreboard_fd < 0) {
1016
0
    errno = EINVAL;
1017
0
    return NULL;
1018
0
  }
1019
1020
0
  if (!have_entry) {
1021
0
    errno = EPERM;
1022
0
    return NULL;
1023
0
  }
1024
1025
0
  switch (field) {
1026
0
    case PR_SCORE_USER:
1027
0
      return entry.sce_user;
1028
1029
0
    case PR_SCORE_CLIENT_ADDR:
1030
0
      return entry.sce_client_addr;
1031
1032
0
    case PR_SCORE_CLIENT_NAME:
1033
0
      return entry.sce_client_name;
1034
1035
0
    case PR_SCORE_CLASS:
1036
0
      return entry.sce_class;
1037
1038
0
    case PR_SCORE_CWD:
1039
0
      return entry.sce_cwd;
1040
1041
0
    case PR_SCORE_CMD:
1042
0
      return entry.sce_cmd;
1043
1044
0
    case PR_SCORE_CMD_ARG:
1045
0
      return entry.sce_cmd_arg;
1046
1047
0
    case PR_SCORE_PROTOCOL:
1048
0
      return entry.sce_protocol;
1049
0
  }
1050
1051
0
  errno = ENOENT;
1052
0
  return NULL;
1053
0
}
1054
1055
0
int pr_scoreboard_entry_kill(pr_scoreboard_entry_t *sce, int signo) {
1056
0
  int res;
1057
1058
0
  if (scoreboard_engine == FALSE) {
1059
0
    return 0;
1060
0
  }
1061
1062
0
  if (sce == NULL) {
1063
0
    errno = EINVAL;
1064
0
    return -1;
1065
0
  }
1066
1067
0
  if (ServerType == SERVER_STANDALONE) {
1068
0
#ifdef HAVE_GETPGID
1069
0
    pid_t curr_pgrp;
1070
1071
0
# ifdef HAVE_GETPGRP
1072
0
    curr_pgrp = getpgrp();
1073
# else
1074
    curr_pgrp = getpgid(0);
1075
# endif /* HAVE_GETPGRP */
1076
1077
0
    if (getpgid(sce->sce_pid) != curr_pgrp) {
1078
0
      pr_trace_msg(trace_channel, 1, "scoreboard entry PID %lu process group "
1079
0
        "does not match current process group, refusing to send signal",
1080
0
        (unsigned long) sce->sce_pid);
1081
0
      errno = EPERM;
1082
0
      return -1;
1083
0
    }
1084
0
#endif /* HAVE_GETPGID */
1085
0
  }
1086
1087
0
  res = kill(sce->sce_pid, signo);
1088
0
  return res;
1089
0
}
1090
1091
/* Given a NUL-terminated string -- possibly UTF8-encoded -- and a maximum
1092
 * buffer length, return the number of bytes in the string which can fit in
1093
 * that buffer without truncating a character.  This is needed since UTF8
1094
 * characters are variable-width.
1095
 */
1096
0
static size_t str_getlen(const char *str, size_t maxsz) {
1097
#ifdef PR_USE_NLS
1098
  register unsigned int i = 0;
1099
1100
  while (i < maxsz &&
1101
         str[i] > 0) {
1102
ascii:
1103
    pr_signals_handle();
1104
    i++;
1105
  }
1106
1107
  while (i < maxsz &&
1108
         str[i]) {
1109
    size_t len;
1110
1111
    if (str[i] > 0) {
1112
      goto ascii;
1113
    }
1114
1115
    pr_signals_handle();
1116
1117
    len = 0;
1118
1119
    switch (str[i] & 0xF0) {
1120
      case 0xE0:
1121
        len = 3;
1122
        break;
1123
1124
      case 0xF0:
1125
        len = 4;
1126
        break;
1127
1128
      default:
1129
        len = 2;
1130
        break;
1131
    }
1132
1133
    if ((i + len) < maxsz) {
1134
      i += len;
1135
1136
    } else {
1137
      break;
1138
    }
1139
  }
1140
1141
  return i;
1142
#else
1143
  /* No UTF8 support in this proftpd build; just return the max size. */
1144
0
  return maxsz;
1145
0
#endif /* !PR_USE_NLS */
1146
0
}
1147
1148
0
int pr_scoreboard_entry_update(pid_t pid, ...) {
1149
0
  va_list ap;
1150
0
  char *tmp = NULL;
1151
0
  int entry_tag = 0;
1152
1153
0
  if (scoreboard_engine == FALSE) {
1154
0
    return 0;
1155
0
  }
1156
1157
0
  if (scoreboard_fd < 0) {
1158
0
    errno = EINVAL;
1159
0
    return -1;
1160
0
  }
1161
1162
0
  if (!have_entry) {
1163
0
    errno = EPERM;
1164
0
    return -1;
1165
0
  }
1166
1167
0
  pr_trace_msg(trace_channel, 3, "updating scoreboard entry");
1168
1169
0
  va_start(ap, pid);
1170
1171
0
  while ((entry_tag = va_arg(ap, int)) != 0) {
1172
0
    pr_signals_handle();
1173
1174
0
    switch (entry_tag) {
1175
0
      case PR_SCORE_USER:
1176
0
        tmp = va_arg(ap, char *);
1177
0
        memset(entry.sce_user, '\0', sizeof(entry.sce_user));
1178
0
        sstrncpy(entry.sce_user, tmp,
1179
0
          str_getlen(tmp, sizeof(entry.sce_user)-1) + 1);
1180
1181
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry user to '%s'",
1182
0
          entry.sce_user);
1183
0
        break;
1184
1185
0
      case PR_SCORE_CLIENT_ADDR: {
1186
0
          pr_netaddr_t *remote_addr = va_arg(ap, pr_netaddr_t *);
1187
1188
0
          pr_snprintf(entry.sce_client_addr, sizeof(entry.sce_client_addr),
1189
0
            "%s", remote_addr ? pr_netaddr_get_ipstr(remote_addr) :
1190
0
            "(unknown)");
1191
0
          entry.sce_client_addr[sizeof(entry.sce_client_addr) - 1] = '\0';
1192
1193
0
          pr_trace_msg(trace_channel, 15, "updated scoreboard entry client "
1194
0
            "address to '%s'", entry.sce_client_addr);
1195
0
        }
1196
0
        break;
1197
1198
0
      case PR_SCORE_CLIENT_NAME: {
1199
0
          char *remote_name = va_arg(ap, char *);
1200
1201
0
          if (remote_name == NULL) {
1202
0
            remote_name = "(unknown)";
1203
0
          }
1204
1205
0
          memset(entry.sce_client_name, '\0', sizeof(entry.sce_client_name));
1206
1207
0
          snprintf(entry.sce_client_name,
1208
0
            str_getlen(remote_name, sizeof(entry.sce_client_name)-1) + 1,
1209
0
            "%s", remote_name);
1210
0
          entry.sce_client_name[sizeof(entry.sce_client_name)-1] = '\0';
1211
1212
0
          pr_trace_msg(trace_channel, 15, "updated scoreboard entry client "
1213
0
            "name to '%s'", entry.sce_client_name);
1214
0
        }
1215
0
        break;
1216
1217
0
      case PR_SCORE_CLASS:
1218
0
        tmp = va_arg(ap, char *);
1219
0
        memset(entry.sce_class, '\0', sizeof(entry.sce_class));
1220
0
        sstrncpy(entry.sce_class, tmp, sizeof(entry.sce_class));
1221
1222
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry class to "
1223
0
          "'%s'", entry.sce_class);
1224
0
        break;
1225
1226
0
      case PR_SCORE_CWD:
1227
0
        tmp = va_arg(ap, char *);
1228
0
        memset(entry.sce_cwd, '\0', sizeof(entry.sce_cwd));
1229
0
        sstrncpy(entry.sce_cwd, tmp,
1230
0
          str_getlen(tmp, sizeof(entry.sce_cwd)-1) + 1);
1231
1232
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry cwd to '%s'",
1233
0
          entry.sce_cwd);
1234
0
        break;
1235
1236
0
      case PR_SCORE_CMD: {
1237
0
        char *cmdstr = NULL;
1238
0
        tmp = va_arg(ap, char *);
1239
0
        cmdstr = handle_score_str(tmp, ap);
1240
1241
0
        memset(entry.sce_cmd, '\0', sizeof(entry.sce_cmd));
1242
0
        sstrncpy(entry.sce_cmd, cmdstr, sizeof(entry.sce_cmd));
1243
0
        (void) va_arg(ap, void *);
1244
1245
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry "
1246
0
          "command to '%s'", entry.sce_cmd);
1247
0
        break;
1248
0
      }
1249
1250
0
      case PR_SCORE_CMD_ARG: {
1251
0
        char *argstr = NULL;
1252
0
        tmp = va_arg(ap, char *);
1253
0
        argstr = handle_score_str(tmp, ap);
1254
1255
0
        memset(entry.sce_cmd_arg, '\0', sizeof(entry.sce_cmd_arg));
1256
0
        sstrncpy(entry.sce_cmd_arg, argstr,
1257
0
          str_getlen(argstr, sizeof(entry.sce_cmd_arg)-1) + 1);
1258
0
        (void) va_arg(ap, void *);
1259
1260
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry "
1261
0
          "command args to '%s'", entry.sce_cmd_arg);
1262
0
        break;
1263
0
      }
1264
1265
0
      case PR_SCORE_SERVER_PORT:
1266
0
        entry.sce_server_port = va_arg(ap, int);
1267
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry "
1268
0
          "server port to %d", entry.sce_server_port);
1269
0
        break;
1270
1271
0
      case PR_SCORE_SERVER_ADDR: {
1272
0
        pr_netaddr_t *server_addr = va_arg(ap, pr_netaddr_t *);
1273
0
        int server_port = va_arg(ap, int);
1274
1275
0
        pr_snprintf(entry.sce_server_addr, sizeof(entry.sce_server_addr),
1276
0
          "%s:%d", server_addr ? pr_netaddr_get_ipstr(server_addr) :
1277
0
          "(unknown)", server_port);
1278
0
        entry.sce_server_addr[sizeof(entry.sce_server_addr)-1] = '\0';
1279
1280
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry server "
1281
0
          "address to '%s'", entry.sce_server_addr);
1282
0
        break;
1283
0
      }
1284
1285
0
      case PR_SCORE_SERVER_LABEL:
1286
0
        tmp = va_arg(ap, char *);
1287
0
        memset(entry.sce_server_label, '\0', sizeof(entry.sce_server_label));
1288
0
        sstrncpy(entry.sce_server_label, tmp, sizeof(entry.sce_server_label));
1289
1290
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry server "
1291
0
          "label to '%s'", entry.sce_server_label);
1292
0
        break;
1293
1294
0
      case PR_SCORE_BEGIN_IDLE:
1295
        /* Ignore this */
1296
0
        (void) va_arg(ap, time_t);
1297
1298
0
        time(&entry.sce_begin_idle);
1299
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry idle "
1300
0
          "start time to %lu", (unsigned long) entry.sce_begin_idle);
1301
0
        break;
1302
1303
0
      case PR_SCORE_BEGIN_SESSION:
1304
        /* Ignore this */
1305
0
        (void) va_arg(ap, time_t);
1306
1307
0
        time(&entry.sce_begin_session);
1308
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry session "
1309
0
          "start time to %lu", (unsigned long) entry.sce_begin_session);
1310
0
        break;
1311
1312
0
      case PR_SCORE_XFER_DONE:
1313
0
        entry.sce_xfer_done = va_arg(ap, off_t);
1314
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry transfer "
1315
0
          "bytes done to %" PR_LU " bytes", (pr_off_t) entry.sce_xfer_done);
1316
0
        break;
1317
1318
0
      case PR_SCORE_XFER_SIZE:
1319
0
        entry.sce_xfer_size = va_arg(ap, off_t);
1320
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry transfer "
1321
0
          "size to %" PR_LU " bytes", (pr_off_t) entry.sce_xfer_size);
1322
0
        break;
1323
1324
0
      case PR_SCORE_XFER_LEN:
1325
0
        entry.sce_xfer_len = va_arg(ap, off_t);
1326
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry transfer "
1327
0
          "length to %" PR_LU " bytes", (pr_off_t) entry.sce_xfer_len);
1328
0
        break;
1329
1330
0
      case PR_SCORE_XFER_ELAPSED:
1331
0
        entry.sce_xfer_elapsed = va_arg(ap, unsigned long);
1332
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry transfer "
1333
0
          "elapsed to %lu ms", (unsigned long) entry.sce_xfer_elapsed);
1334
0
        break;
1335
1336
0
      case PR_SCORE_PROTOCOL:
1337
0
        tmp = va_arg(ap, char *);
1338
0
        memset(entry.sce_protocol, '\0', sizeof(entry.sce_protocol));
1339
0
        sstrncpy(entry.sce_protocol, tmp, sizeof(entry.sce_protocol));
1340
0
        pr_trace_msg(trace_channel, 15, "updated scoreboard entry protocol to "
1341
0
          "'%s'", entry.sce_protocol);
1342
0
        break;
1343
1344
0
      default:
1345
0
        va_end(ap);
1346
0
        errno = ENOENT;
1347
0
        return -1;
1348
0
    }
1349
0
  }
1350
1351
0
  va_end(ap);
1352
1353
  /* Write-lock this entry */
1354
0
  wlock_entry(scoreboard_fd);
1355
0
  if (write_entry(scoreboard_fd) < 0) {
1356
0
    pr_log_pri(PR_LOG_NOTICE, "error writing scoreboard entry: %s",
1357
0
      strerror(errno));
1358
0
  }
1359
0
  unlock_entry(scoreboard_fd);
1360
1361
0
  pr_trace_msg(trace_channel, 3, "finished updating scoreboard entry");
1362
0
  return 0;
1363
0
}
1364
1365
/* Validate the PID in a scoreboard entry.  A PID can be invalid in a couple
1366
 * of ways:
1367
 *
1368
 *  1.  The PID refers to a process no longer present on the system.
1369
 *  2.  The PID refers to a process not in the daemon process group
1370
 *      (for "ServerType standalone" servers only).
1371
 */
1372
0
static int scoreboard_valid_pid(pid_t pid, pid_t curr_pgrp) {
1373
0
  int res;
1374
1375
0
  res = kill(pid, 0);
1376
0
  if (res < 0 &&
1377
0
      errno == ESRCH) {
1378
0
    return -1;
1379
0
  }
1380
1381
0
  if (ServerType == SERVER_STANDALONE &&
1382
0
      curr_pgrp > 0) {
1383
0
#if defined(HAVE_GETPGID)
1384
0
    if (getpgid(pid) != curr_pgrp) {
1385
0
      pr_trace_msg(trace_channel, 1, "scoreboard entry PID %lu process group "
1386
0
        "does not match current process group, removing entry",
1387
0
        (unsigned long) pid);
1388
0
      errno = EPERM;
1389
0
      return -1;
1390
0
    }
1391
0
#endif /* HAVE_GETPGID */
1392
0
  }
1393
1394
0
  return 0;
1395
0
}
1396
1397
0
int pr_scoreboard_scrub(void) {
1398
0
  int fd = -1, res, xerrno;
1399
0
  off_t curr_offset = 0;
1400
0
  pid_t curr_pgrp = 0;
1401
0
  pr_scoreboard_entry_t sce;
1402
1403
0
  if (scoreboard_engine == FALSE) {
1404
0
    return 0;
1405
0
  }
1406
1407
0
  pr_log_debug(DEBUG9, "scrubbing scoreboard");
1408
0
  pr_trace_msg(trace_channel, 9, "%s", "scrubbing scoreboard");
1409
1410
  /* Manually open the scoreboard.  It won't hurt if the process already
1411
   * has a descriptor opened on the scoreboard file.
1412
   */
1413
0
  PRIVS_ROOT
1414
0
  fd = open(pr_get_scoreboard(), O_RDWR);
1415
0
  xerrno = errno;
1416
0
  PRIVS_RELINQUISH
1417
1418
0
  if (fd < 0) {
1419
0
    pr_log_debug(DEBUG1, "unable to scrub ScoreboardFile '%s': %s",
1420
0
      pr_get_scoreboard(), strerror(xerrno));
1421
1422
0
    errno = xerrno;
1423
0
    return -1;
1424
0
  }
1425
1426
  /* Write-lock the scoreboard file. */
1427
0
  PR_DEVEL_CLOCK(res = wlock_scoreboard());
1428
0
  if (res < 0) {
1429
0
    xerrno = errno;
1430
1431
0
    (void) close(fd);
1432
1433
0
    errno = xerrno;
1434
0
    return -1;
1435
0
  }
1436
1437
0
#if defined(HAVE_GETPGRP)
1438
0
  curr_pgrp = getpgrp();
1439
#elif HAVE_GETPGID
1440
  curr_pgrp = getpgid(0);
1441
#endif /* !HAVE_GETPGRP and !HAVE_GETPGID */
1442
1443
  /* Skip past the scoreboard header. */
1444
0
  curr_offset = lseek(fd, (off_t) sizeof(pr_scoreboard_header_t), SEEK_SET);
1445
0
  if (curr_offset < 0) {
1446
0
    xerrno = errno;
1447
1448
0
    unlock_scoreboard();
1449
0
    (void) close(fd);
1450
1451
0
    errno = xerrno;
1452
0
    return -1;
1453
0
  }
1454
1455
0
  entry_lock.l_start = curr_offset;
1456
1457
0
  PRIVS_ROOT
1458
1459
0
  while (TRUE) {
1460
0
    pr_signals_handle();
1461
1462
    /* First, lock the scoreboard entry/slot about to be checked.  If we can't
1463
     * (e.g. because the session process has it locked), then just move on.
1464
     * If another process has it locked, then it is presumed to be valid.
1465
     */
1466
0
    if (wlock_entry(fd) < 0) {
1467
      /* Seek to the next entry/slot.  If it fails for any reason, just
1468
       * be done with the scrubbing.
1469
       */
1470
0
      curr_offset = lseek(fd, sizeof(sce), SEEK_CUR);
1471
0
      entry_lock.l_start = curr_offset;
1472
1473
0
      if (curr_offset < 0) {
1474
0
        pr_trace_msg(trace_channel, 3,
1475
0
          "error seeking to next scoreboard entry (fd %d): %s", fd,
1476
0
          strerror(xerrno));
1477
0
        break;
1478
0
      }
1479
1480
0
      continue;
1481
0
    }
1482
1483
0
    memset(&sce, 0, sizeof(sce));
1484
0
    res = read(fd, &sce, sizeof(sce));
1485
0
    if (res == 0) {
1486
      /* EOF */
1487
0
      unlock_entry(fd);
1488
0
      break;
1489
0
    }
1490
1491
0
    if (res == sizeof(sce)) {
1492
1493
      /* Check to see if the PID in this entry is valid.  If not, erase
1494
       * the slot.
1495
       */
1496
0
      if (sce.sce_pid &&
1497
0
          scoreboard_valid_pid(sce.sce_pid, curr_pgrp) < 0) {
1498
0
        pid_t slot_pid;
1499
1500
0
        slot_pid = sce.sce_pid;
1501
1502
        /* OK, the recorded PID is no longer valid. */
1503
0
        pr_log_debug(DEBUG9, "scrubbing scoreboard entry for PID %lu",
1504
0
          (unsigned long) slot_pid);
1505
1506
        /* Rewind to the start of this slot. */
1507
0
        if (lseek(fd, curr_offset, SEEK_SET) < 0) {
1508
0
          xerrno = errno;
1509
1510
0
          pr_log_debug(DEBUG0, "error seeking to scoreboard entry to scrub: %s",
1511
0
            strerror(xerrno));
1512
1513
0
          pr_trace_msg(trace_channel, 3,
1514
0
            "error seeking to scoreboard entry for PID %lu (offset %" PR_LU ") "
1515
0
            "to scrub: %s", (unsigned long) slot_pid, (pr_off_t) curr_offset,
1516
0
            strerror(xerrno));
1517
0
        }
1518
1519
0
        memset(&sce, 0, sizeof(sce));
1520
1521
        /* Note: It does not matter that we only have a read-lock on this
1522
         * slot; we can safely write over the byte range here, since we know
1523
         * that the process for this slot is not around anymore, and there
1524
         * are no incoming processes to use take it.
1525
         */
1526
1527
0
        res = write(fd, &sce, sizeof(sce));
1528
0
        while (res != sizeof(sce)) {
1529
0
          if (res < 0) {
1530
0
            xerrno = errno;
1531
1532
0
            if (xerrno == EINTR) {
1533
0
              pr_signals_handle();
1534
0
              res = write(fd, &sce, sizeof(sce));
1535
0
              continue;
1536
0
            }
1537
1538
0
            pr_log_debug(DEBUG0, "error scrubbing scoreboard: %s",
1539
0
              strerror(xerrno));
1540
0
            pr_trace_msg(trace_channel, 3,
1541
0
              "error writing out scrubbed scoreboard entry for PID %lu: %s",
1542
0
              (unsigned long) slot_pid, strerror(xerrno));
1543
1544
0
          } else {
1545
            /* Watch out for short writes here. */
1546
0
            pr_log_pri(PR_LOG_NOTICE,
1547
0
              "error scrubbing scoreboard entry: only wrote %d of %lu bytes",
1548
0
              res, (unsigned long) sizeof(sce));
1549
0
          }
1550
0
        }
1551
0
      }
1552
1553
      /* Unlock the slot, and move to the next one. */
1554
0
      unlock_entry(fd);
1555
1556
      /* Mark the current offset. */
1557
0
      curr_offset = lseek(fd, (off_t) 0, SEEK_CUR);
1558
0
      if (curr_offset < 0) {
1559
0
        break;
1560
0
      }
1561
1562
0
      entry_lock.l_start = curr_offset;
1563
0
    }
1564
0
  }
1565
1566
0
  PRIVS_RELINQUISH
1567
1568
  /* Release the scoreboard. */
1569
0
  unlock_scoreboard();
1570
1571
  /* Don't need the descriptor anymore. */
1572
0
  (void) close(fd);
1573
1574
0
  pr_log_debug(DEBUG9, "finished scrubbing scoreboard");
1575
0
  pr_trace_msg(trace_channel, 9, "%s", "finished scrubbing scoreboard");
1576
1577
0
  return 0;
1578
0
}