trail.c revision 256281
1/*- 2 * Copyright (c) 2012 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Pawel Jakub Dawidek under sponsorship from 6 * the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $P4: //depot/projects/trustedbsd/openbsm/bin/auditdistd/trail.c#3 $ 30 */ 31 32#include <config/config.h> 33 34#include <sys/param.h> 35#include <sys/stat.h> 36 37#include <dirent.h> 38#include <errno.h> 39#include <fcntl.h> 40#include <stdbool.h> 41#include <stdint.h> 42#include <stdlib.h> 43#include <string.h> 44#include <unistd.h> 45 46#include <compat/compat.h> 47#ifndef HAVE_STRLCPY 48#include <compat/strlcpy.h> 49#endif 50#ifndef HAVE_FACCESSAT 51#include "faccessat.h" 52#endif 53#ifndef HAVE_FSTATAT 54#include "fstatat.h" 55#endif 56#ifndef HAVE_OPENAT 57#include "openat.h" 58#endif 59#ifndef HAVE_UNLINKAT 60#include "unlinkat.h" 61#endif 62 63#include "pjdlog.h" 64#include "trail.h" 65 66#define TRAIL_MAGIC 0x79a11 67struct trail { 68 int tr_magic; 69 /* Path usually to /var/audit/dist/ directory. */ 70 char tr_dirname[PATH_MAX]; 71 /* Descriptor to td_dirname directory. */ 72 DIR *tr_dirfp; 73 /* Path to audit trail file. */ 74 char tr_filename[PATH_MAX]; 75 /* Descriptor to audit trail file. */ 76 int tr_filefd; 77}; 78 79#define HALF_LEN 14 80 81bool 82trail_is_not_terminated(const char *filename) 83{ 84 85 return (strcmp(filename + HALF_LEN, ".not_terminated") == 0); 86} 87 88bool 89trail_is_crash_recovery(const char *filename) 90{ 91 92 return (strcmp(filename + HALF_LEN, ".crash_recovery") == 0); 93} 94 95struct trail * 96trail_new(const char *dirname, bool create) 97{ 98 struct trail *trail; 99 100 trail = calloc(1, sizeof(*trail)); 101 102 if (strlcpy(trail->tr_dirname, dirname, sizeof(trail->tr_dirname)) >= 103 sizeof(trail->tr_dirname)) { 104 free(trail); 105 pjdlog_error("Directory name too long (\"%s\").", dirname); 106 errno = ENAMETOOLONG; 107 return (NULL); 108 } 109 trail->tr_dirfp = opendir(dirname); 110 if (trail->tr_dirfp == NULL) { 111 if (create && errno == ENOENT) { 112 if (mkdir(dirname, 0700) == -1) { 113 pjdlog_errno(LOG_ERR, 114 "Unable to create directory \"%s\"", 115 dirname); 116 free(trail); 117 return (NULL); 118 } 119 /* TODO: Set directory ownership. */ 120 } else { 121 pjdlog_errno(LOG_ERR, 122 "Unable to open directory \"%s\"", 123 dirname); 124 free(trail); 125 return (NULL); 126 } 127 trail->tr_dirfp = opendir(dirname); 128 if (trail->tr_dirfp == NULL) { 129 pjdlog_errno(LOG_ERR, 130 "Unable to open directory \"%s\"", 131 dirname); 132 free(trail); 133 return (NULL); 134 } 135 } 136 trail->tr_filefd = -1; 137 trail->tr_magic = TRAIL_MAGIC; 138 return (trail); 139} 140 141void 142trail_free(struct trail *trail) 143{ 144 145 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 146 147 if (trail->tr_filefd != -1) 148 trail_close(trail); 149 closedir(trail->tr_dirfp); 150 bzero(trail, sizeof(*trail)); 151 trail->tr_magic = 0; 152 trail->tr_filefd = -1; 153 free(trail); 154} 155 156static uint8_t 157trail_type(DIR *dirfp, const char *filename) 158{ 159 struct stat sb; 160 int dfd; 161 162 PJDLOG_ASSERT(dirfp != NULL); 163 164 dfd = dirfd(dirfp); 165 PJDLOG_ASSERT(dfd >= 0); 166 if (fstatat(dfd, filename, &sb, AT_SYMLINK_NOFOLLOW) == -1) { 167 pjdlog_errno(LOG_ERR, "Unable to stat \"%s\"", filename); 168 return (DT_UNKNOWN); 169 } 170 return (IFTODT(sb.st_mode)); 171} 172 173/* 174 * Find trail file by first part of the name in case it was renamed. 175 * First part of the trail file name never changes, but trail file 176 * can be renamed when hosts are disconnected from .not_terminated 177 * to .[0-9]{14} or to .crash_recovery. 178 */ 179static bool 180trail_find(struct trail *trail) 181{ 182 struct dirent *dp; 183 184 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 185 PJDLOG_ASSERT(trail_is_not_terminated(trail->tr_filename)); 186 187 rewinddir(trail->tr_dirfp); 188 while ((dp = readdir(trail->tr_dirfp)) != NULL) { 189 if (strncmp(dp->d_name, trail->tr_filename, HALF_LEN + 1) == 0) 190 break; 191 } 192 if (dp == NULL) 193 return (false); 194 PJDLOG_VERIFY(strlcpy(trail->tr_filename, dp->d_name, 195 sizeof(trail->tr_filename)) < sizeof(trail->tr_filename)); 196 return (true); 197} 198 199/* 200 * Open the given trail file and move pointer at the given offset, as this is 201 * where receiver finished the last time. 202 * If the file doesn't exist or the given offset is equal to the file size, 203 * move to the next trail file. 204 */ 205void 206trail_start(struct trail *trail, const char *filename, off_t offset) 207{ 208 struct stat sb; 209 int dfd, fd; 210 211 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 212 213 PJDLOG_VERIFY(strlcpy(trail->tr_filename, filename, 214 sizeof(trail->tr_filename)) < sizeof(trail->tr_filename)); 215 trail->tr_filefd = -1; 216 217 if (trail->tr_filename[0] == '\0') { 218 PJDLOG_ASSERT(offset == 0); 219 trail_next(trail); 220 return; 221 } 222 223 dfd = dirfd(trail->tr_dirfp); 224 PJDLOG_ASSERT(dfd >= 0); 225again: 226 fd = openat(dfd, trail->tr_filename, O_RDONLY); 227 if (fd == -1) { 228 if (errno == ENOENT && 229 trail_is_not_terminated(trail->tr_filename) && 230 trail_find(trail)) { 231 /* File was renamed. Retry with new name. */ 232 pjdlog_debug(1, 233 "Trail file was renamed since last connection to \"%s/%s\".", 234 trail->tr_dirname, trail->tr_filename); 235 goto again; 236 } else if (errno == ENOENT) { 237 /* File disappeared. */ 238 pjdlog_debug(1, "File \"%s/%s\" doesn't exist.", 239 trail->tr_dirname, trail->tr_filename); 240 } else { 241 pjdlog_errno(LOG_ERR, 242 "Unable to open file \"%s/%s\", skipping", 243 trail->tr_dirname, trail->tr_filename); 244 } 245 trail_next(trail); 246 return; 247 } 248 if (fstat(fd, &sb) == -1) { 249 pjdlog_errno(LOG_ERR, 250 "Unable to stat file \"%s/%s\", skipping", 251 trail->tr_dirname, trail->tr_filename); 252 close(fd); 253 trail_next(trail); 254 return; 255 } 256 if (!S_ISREG(sb.st_mode)) { 257 pjdlog_warning("File \"%s/%s\" is not a regular file, skipping.", 258 trail->tr_dirname, trail->tr_filename); 259 close(fd); 260 trail_next(trail); 261 return; 262 } 263 /* 264 * We continue sending requested file if: 265 * 1. It is not fully sent yet, or 266 * 2. It is fully sent, but is not terminated, so new data can be 267 * appended still, or 268 * 3. It is fully sent but file name has changed. 269 * 270 * Note that we are fine if our .not_terminated or .crash_recovery file 271 * is smaller than the one on the receiver side, as it is possible that 272 * more data was send to the receiver than was safely stored on disk. 273 * We accept .not_terminated only because auditdistd can start before 274 * auditd manage to rename it to .crash_recovery. 275 */ 276 if (offset < sb.st_size || 277 (offset >= sb.st_size && 278 trail_is_not_terminated(trail->tr_filename)) || 279 (offset >= sb.st_size && trail_is_not_terminated(filename) && 280 trail_is_crash_recovery(trail->tr_filename))) { 281 /* File was not fully send. Let's finish it. */ 282 if (lseek(fd, offset, SEEK_SET) == -1) { 283 pjdlog_errno(LOG_ERR, 284 "Unable to move to offset %jd within file \"%s/%s\", skipping", 285 (intmax_t)offset, trail->tr_dirname, 286 trail->tr_filename); 287 close(fd); 288 trail_next(trail); 289 return; 290 } 291 if (!trail_is_crash_recovery(trail->tr_filename)) { 292 pjdlog_debug(1, 293 "Restarting file \"%s/%s\" at offset %jd.", 294 trail->tr_dirname, trail->tr_filename, 295 (intmax_t)offset); 296 } 297 trail->tr_filefd = fd; 298 return; 299 } 300 close(fd); 301 if (offset > sb.st_size) { 302 pjdlog_warning("File \"%s/%s\" shrinked, removing it.", 303 trail->tr_dirname, trail->tr_filename); 304 } else { 305 pjdlog_debug(1, "File \"%s/%s\" is already sent, removing it.", 306 trail->tr_dirname, trail->tr_filename); 307 } 308 /* Entire file is already sent or it shirnked, we can remove it. */ 309 if (unlinkat(dfd, trail->tr_filename, 0) == -1) { 310 pjdlog_errno(LOG_WARNING, "Unable to remove file \"%s/%s\"", 311 trail->tr_dirname, trail->tr_filename); 312 } 313 trail_next(trail); 314} 315 316/* 317 * Set next file in the trail->tr_dirname directory and open it for reading. 318 */ 319void 320trail_next(struct trail *trail) 321{ 322 char curfile[PATH_MAX]; 323 struct dirent *dp; 324 int dfd; 325 326 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 327 PJDLOG_ASSERT(trail->tr_filefd == -1); 328 329again: 330 curfile[0] = '\0'; 331 332 rewinddir(trail->tr_dirfp); 333 while ((dp = readdir(trail->tr_dirfp)) != NULL) { 334 if (dp->d_name[0] < '0' || dp->d_name[0] > '9') 335 continue; 336 if (dp->d_type == DT_UNKNOWN) 337 dp->d_type = trail_type(trail->tr_dirfp, dp->d_name); 338 /* We are only interested in regular files, skip the rest. */ 339 if (dp->d_type != DT_REG) { 340 pjdlog_debug(1, 341 "File \"%s/%s\" is not a regular file, skipping.", 342 trail->tr_dirname, dp->d_name); 343 continue; 344 } 345 /* Skip all files "greater" than curfile. */ 346 if (curfile[0] != '\0' && strcmp(dp->d_name, curfile) > 0) 347 continue; 348 /* Skip all files "smaller" than the current trail_filename. */ 349 if (trail->tr_filename[0] != '\0' && 350 strcmp(dp->d_name, trail->tr_filename) <= 0) { 351 continue; 352 } 353 PJDLOG_VERIFY(strlcpy(curfile, dp->d_name, sizeof(curfile)) < 354 sizeof(curfile)); 355 } 356 if (curfile[0] == '\0') { 357 /* 358 * There are no new trail files, so we return. 359 * We don't clear trail_filename string, to know where to 360 * start when new file appears. 361 */ 362 PJDLOG_ASSERT(trail->tr_filefd == -1); 363 pjdlog_debug(1, "No new trail files."); 364 return; 365 } 366 PJDLOG_VERIFY(strlcpy(trail->tr_filename, curfile, 367 sizeof(trail->tr_filename)) < sizeof(trail->tr_filename)); 368 dfd = dirfd(trail->tr_dirfp); 369 PJDLOG_ASSERT(dfd >= 0); 370 trail->tr_filefd = openat(dfd, trail->tr_filename, O_RDONLY); 371 if (trail->tr_filefd == -1) { 372 pjdlog_errno(LOG_ERR, 373 "Unable to open file \"%s/%s\", skipping", 374 trail->tr_dirname, trail->tr_filename); 375 goto again; 376 } 377 pjdlog_debug(1, "Found next trail file: \"%s/%s\".", trail->tr_dirname, 378 trail->tr_filename); 379} 380 381/* 382 * Close current trial file. 383 */ 384void 385trail_close(struct trail *trail) 386{ 387 388 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 389 PJDLOG_ASSERT(trail->tr_filefd >= 0); 390 PJDLOG_ASSERT(trail->tr_filename[0] != '\0'); 391 392 PJDLOG_VERIFY(close(trail->tr_filefd) == 0); 393 trail->tr_filefd = -1; 394} 395 396/* 397 * Reset trail state. Used when connection is disconnected and we will 398 * need to start over after reconnect. Trail needs to be already closed. 399 */ 400void 401trail_reset(struct trail *trail) 402{ 403 404 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 405 PJDLOG_ASSERT(trail->tr_filefd == -1); 406 407 trail->tr_filename[0] = '\0'; 408} 409 410/* 411 * Unlink current trial file. 412 */ 413void 414trail_unlink(struct trail *trail, const char *filename) 415{ 416 int dfd; 417 418 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 419 PJDLOG_ASSERT(filename != NULL); 420 PJDLOG_ASSERT(filename[0] != '\0'); 421 422 dfd = dirfd(trail->tr_dirfp); 423 PJDLOG_ASSERT(dfd >= 0); 424 425 if (unlinkat(dfd, filename, 0) == -1) { 426 pjdlog_errno(LOG_ERR, "Unable to remove \"%s/%s\"", 427 trail->tr_dirname, filename); 428 } else { 429 pjdlog_debug(1, "Trail file \"%s/%s\" removed.", 430 trail->tr_dirname, filename); 431 } 432} 433 434/* 435 * Return true if we should switch to next trail file. 436 * We don't switch if our file name ends with ".not_terminated" and it 437 * exists (ie. wasn't renamed). 438 */ 439bool 440trail_switch(struct trail *trail) 441{ 442 char filename[PATH_MAX]; 443 int fd; 444 445 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 446 PJDLOG_ASSERT(trail->tr_filefd >= 0); 447 448 if (!trail_is_not_terminated(trail->tr_filename)) 449 return (true); 450 fd = dirfd(trail->tr_dirfp); 451 PJDLOG_ASSERT(fd >= 0); 452 if (faccessat(fd, trail->tr_filename, F_OK, 0) == 0) 453 return (false); 454 if (errno != ENOENT) { 455 pjdlog_errno(LOG_ERR, "Unable to access file \"%s/%s\"", 456 trail->tr_dirname, trail->tr_filename); 457 } 458 strlcpy(filename, trail->tr_filename, sizeof(filename)); 459 if (!trail_find(trail)) { 460 pjdlog_error("Trail file \"%s/%s\" disappeared.", 461 trail->tr_dirname, trail->tr_filename); 462 return (true); 463 } 464 pjdlog_debug(1, "Trail file \"%s/%s\" was renamed to \"%s/%s\".", 465 trail->tr_dirname, filename, trail->tr_dirname, 466 trail->tr_filename); 467 return (true); 468} 469 470const char * 471trail_filename(const struct trail *trail) 472{ 473 474 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 475 476 return (trail->tr_filename); 477} 478 479int 480trail_filefd(const struct trail *trail) 481{ 482 483 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 484 485 return (trail->tr_filefd); 486} 487 488int 489trail_dirfd(const struct trail *trail) 490{ 491 492 PJDLOG_ASSERT(trail->tr_magic == TRAIL_MAGIC); 493 494 return (dirfd(trail->tr_dirfp)); 495} 496 497/* 498 * Find the last file in the directory opened under dirfp. 499 */ 500void 501trail_last(DIR *dirfp, char *filename, size_t filenamesize) 502{ 503 char curfile[PATH_MAX]; 504 struct dirent *dp; 505 506 PJDLOG_ASSERT(dirfp != NULL); 507 508 curfile[0] = '\0'; 509 510 rewinddir(dirfp); 511 while ((dp = readdir(dirfp)) != NULL) { 512 if (dp->d_name[0] < '0' || dp->d_name[0] > '9') 513 continue; 514 if (dp->d_type == DT_UNKNOWN) 515 dp->d_type = trail_type(dirfp, dp->d_name); 516 /* We are only interested in regular files, skip the rest. */ 517 if (dp->d_type != DT_REG) 518 continue; 519 /* Skip all files "greater" than curfile. */ 520 if (curfile[0] != '\0' && strcmp(dp->d_name, curfile) < 0) 521 continue; 522 PJDLOG_VERIFY(strlcpy(curfile, dp->d_name, sizeof(curfile)) < 523 sizeof(curfile)); 524 } 525 if (curfile[0] == '\0') { 526 /* 527 * There are no trail files, so we return. 528 */ 529 pjdlog_debug(1, "No trail files."); 530 bzero(filename, filenamesize); 531 return; 532 } 533 PJDLOG_VERIFY(strlcpy(filename, curfile, filenamesize) < filenamesize); 534 pjdlog_debug(1, "Found the most recent trail file: \"%s\".", filename); 535} 536 537/* 538 * Check if the given file name is a valid audit trail file name. 539 * Possible names: 540 * 20120106132657.20120106132805 541 * 20120106132657.not_terminated 542 * 20120106132657.crash_recovery 543 * If two names are given, check if the first name can be renamed 544 * to the second name. When renaming, first part of the name has 545 * to be identical and only the following renames are valid: 546 * 20120106132657.not_terminated -> 20120106132657.20120106132805 547 * 20120106132657.not_terminated -> 20120106132657.crash_recovery 548 */ 549bool 550trail_validate_name(const char *srcname, const char *dstname) 551{ 552 int i; 553 554 PJDLOG_ASSERT(srcname != NULL); 555 556 if (strlen(srcname) != 2 * HALF_LEN + 1) 557 return (false); 558 if (srcname[HALF_LEN] != '.') 559 return (false); 560 for (i = 0; i < HALF_LEN; i++) { 561 if (srcname[i] < '0' || srcname[i] > '9') 562 return (false); 563 } 564 for (i = HALF_LEN + 1; i < 2 * HALF_LEN - 1; i++) { 565 if (srcname[i] < '0' || srcname[i] > '9') 566 break; 567 } 568 if (i < 2 * HALF_LEN - 1 && 569 strcmp(srcname + HALF_LEN + 1, "not_terminated") != 0 && 570 strcmp(srcname + HALF_LEN + 1, "crash_recovery") != 0) { 571 return (false); 572 } 573 574 if (dstname == NULL) 575 return (true); 576 577 /* We tolarate if both names are identical. */ 578 if (strcmp(srcname, dstname) == 0) 579 return (true); 580 581 /* We can only rename not_terminated files. */ 582 if (strcmp(srcname + HALF_LEN + 1, "not_terminated") != 0) 583 return (false); 584 if (strlen(dstname) != 2 * HALF_LEN + 1) 585 return (false); 586 if (strncmp(srcname, dstname, HALF_LEN + 1) != 0) 587 return (false); 588 for (i = HALF_LEN + 1; i < 2 * HALF_LEN - 1; i++) { 589 if (dstname[i] < '0' || dstname[i] > '9') 590 break; 591 } 592 if (i < 2 * HALF_LEN - 1 && 593 strcmp(dstname + HALF_LEN + 1, "crash_recovery") != 0) { 594 return (false); 595 } 596 597 return (true); 598} 599 600int 601trail_name_compare(const char *name0, const char *name1) 602{ 603 int ret; 604 605 ret = strcmp(name0, name1); 606 if (ret == 0) 607 return (TRAIL_IDENTICAL); 608 if (strncmp(name0, name1, HALF_LEN + 1) == 0) 609 return (TRAIL_RENAMED); 610 return (ret < 0 ? TRAIL_OLDER : TRAIL_NEWER); 611} 612