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