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