/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 | } |