int fsync_main(int argc UNUSED_PARAM, char **argv) { int status; int opts; opts = getopt32(argv, "d"); /* fdatasync */ argv += optind; if (!*argv) { bb_show_usage(); } status = EXIT_SUCCESS; do { int fd = open3_or_warn(*argv, O_NOATIME | O_NOCTTY | O_RDONLY, 0); if (fd == -1) { status = EXIT_FAILURE; continue; } if ((opts ? fdatasync(fd) : fsync(fd))) { //status = EXIT_FAILURE; - do we want this? bb_simple_perror_msg(*argv); } close(fd); } while (*++argv); return status; }
static int open_to_or_warn(int to_fd, const char *filename, int flags, int mode) { int fd = open3_or_warn(filename, flags, mode); if (fd < 0) { return 1; } xmove_fd(fd, to_fd); return 0; }
static void crondlog(const char *ctl, ...) { va_list va; int level = (ctl[0] & 0x1f); va_start(va, ctl); if (level >= (int)LogLevel) { /* Debug mode: all to (non-redirected) stderr, */ /* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ if (!DebugOpt && LogFile) { /* Otherwise (log to file): we reopen log file at every write: */ int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); if (logfd >= 0) xmove_fd(logfd, STDERR_FILENO); } // TODO: ERR -> error, WARN -> warning, LVL -> info bb_verror_msg(ctl + 1, va, /* strerr: */ NULL); } va_end(va); if (ctl[0] & 0x80) exit(20); }
static void crondlog(const char *ctl, ...) { va_list va; const char *fmt; int level = (int) (ctl[0] & 0xf); int type = level == 20 ? LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); va_start(va, ctl); fmt = ctl + 1; if (level >= LogLevel) { #if ENABLE_DEBUG_CROND_OPTION if (DebugOpt) { vfprintf(stderr, fmt, va); } else #endif if (LogFile == 0) { vsyslog(type, fmt, va); } else { #if !ENABLE_DEBUG_CROND_OPTION int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); #else int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); #endif if (logfd >= 0) { vdprintf(logfd, fmt, va); close(logfd); } } } va_end(va); if (ctl[0] & 0200) { exit(20); } }
/* Return: * -1 error, copy not made * 0 copy is made or user answered "no" in interactive mode * (failures to preserve mode/owner/times are not reported in exit code) */ int FAST_FUNC copy_file(const char *source, const char *dest, int flags) { /* This is a recursive function, try to minimize stack usage */ /* NB: each struct stat is ~100 bytes */ struct stat source_stat; struct stat dest_stat; smallint retval = 0; smallint dest_exists = 0; smallint ovr; /* Inverse of cp -d ("cp without -d") */ #define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0)) if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { /* This may be a dangling symlink. * Making [sym]links to dangling symlinks works, so... */ if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) goto make_links; bb_perror_msg("can't stat '%s'", source); return -1; } if (lstat(dest, &dest_stat) < 0) { if (errno != ENOENT) { bb_perror_msg("can't stat '%s'", dest); return -1; } } else { if (source_stat.st_dev == dest_stat.st_dev && source_stat.st_ino == dest_stat.st_ino ) { bb_error_msg("'%s' and '%s' are the same file", source, dest); return -1; } dest_exists = 1; } #if ENABLE_SELINUX if ((flags & FILEUTILS_PRESERVE_SECURITY_CONTEXT) && is_selinux_enabled() > 0) { security_context_t con; if (lgetfilecon(source, &con) >= 0) { if (setfscreatecon(con) < 0) { bb_perror_msg("can't set setfscreatecon %s", con); freecon(con); return -1; } } else if (errno == ENOTSUP || errno == ENODATA) { setfscreatecon_or_die(NULL); } else { bb_perror_msg("can't lgetfilecon %s", source); return -1; } } #endif if (S_ISDIR(source_stat.st_mode)) { DIR *dp; const char *tp; struct dirent *d; mode_t saved_umask = 0; if (!(flags & FILEUTILS_RECUR)) { bb_error_msg("omitting directory '%s'", source); return -1; } /* Did we ever create source ourself before? */ tp = is_in_ino_dev_hashtable(&source_stat); if (tp) { /* We did! it's a recursion! man the lifeboats... */ bb_error_msg("recursion detected, omitting directory '%s'", source); return -1; } if (dest_exists) { if (!S_ISDIR(dest_stat.st_mode)) { bb_error_msg("target '%s' is not a directory", dest); return -1; } /* race here: user can substitute a symlink between * this check and actual creation of files inside dest */ } else { /* Create DEST */ mode_t mode; saved_umask = umask(0); mode = source_stat.st_mode; if (!(flags & FILEUTILS_PRESERVE_STATUS)) mode = source_stat.st_mode & ~saved_umask; /* Allow owner to access new dir (at least for now) */ mode |= S_IRWXU; if (mkdir(dest, mode) < 0) { umask(saved_umask); bb_perror_msg("can't create directory '%s'", dest); return -1; } umask(saved_umask); /* need stat info for add_to_ino_dev_hashtable */ if (lstat(dest, &dest_stat) < 0) { bb_perror_msg("can't stat '%s'", dest); return -1; } } /* remember (dev,inode) of each created dir. * NULL: name is not remembered */ add_to_ino_dev_hashtable(&dest_stat, NULL); /* Recursively copy files in SOURCE */ dp = opendir(source); if (dp == NULL) { retval = -1; goto preserve_mode_ugid_time; } while ((d = readdir(dp)) != NULL) { char *new_source, *new_dest; new_source = concat_subpath_file(source, d->d_name); if (new_source == NULL) continue; new_dest = concat_path_file(dest, d->d_name); if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0) retval = -1; free(new_source); free(new_dest); } closedir(dp); if (!dest_exists && chmod(dest, source_stat.st_mode & ~saved_umask) < 0 ) { bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); /* retval = -1; - WRONG! copy *WAS* made */ } goto preserve_mode_ugid_time; } if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { int (*lf)(const char *oldpath, const char *newpath); make_links: /* Hmm... maybe * if (DEREF && MAKE_SOFTLINK) source = realpath(source) ? * (but realpath returns NULL on dangling symlinks...) */ lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link; if (lf(source, dest) < 0) { ovr = ask_and_unlink(dest, flags); if (ovr <= 0) return ovr; if (lf(source, dest) < 0) { bb_perror_msg("can't create link '%s'", dest); return -1; } } /* _Not_ jumping to preserve_mode_ugid_time: * (sym)links don't have those */ return 0; } if (/* "cp thing1 thing2" without -R: just open and read() from thing1 */ !(flags & FILEUTILS_RECUR) /* "cp [-opts] regular_file thing2" */ || S_ISREG(source_stat.st_mode) /* DEREF uses stat, which never returns S_ISLNK() == true. * So the below is never true: */ /* || (FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) */ ) { int src_fd; int dst_fd; mode_t new_mode; if (!FLAGS_DEREF && S_ISLNK(source_stat.st_mode)) { /* "cp -d symlink dst": create a link */ goto dont_cat; } if (ENABLE_FEATURE_PRESERVE_HARDLINKS && !FLAGS_DEREF) { const char *link_target; link_target = is_in_ino_dev_hashtable(&source_stat); if (link_target) { if (link(link_target, dest) < 0) { ovr = ask_and_unlink(dest, flags); if (ovr <= 0) return ovr; if (link(link_target, dest) < 0) { bb_perror_msg("can't create link '%s'", dest); return -1; } } return 0; } add_to_ino_dev_hashtable(&source_stat, dest); } src_fd = open_or_warn(source, O_RDONLY); if (src_fd < 0) return -1; /* Do not try to open with weird mode fields */ new_mode = source_stat.st_mode; if (!S_ISREG(source_stat.st_mode)) new_mode = 0666; // POSIX way is a security problem versus (sym)link attacks if (!ENABLE_FEATURE_NON_POSIX_CP) { dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); } else { /* safe way: */ dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); } if (dst_fd == -1) { ovr = ask_and_unlink(dest, flags); if (ovr <= 0) { close(src_fd); return ovr; } /* It shouldn't exist. If it exists, do not open (symlink attack?) */ dst_fd = open3_or_warn(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); if (dst_fd < 0) { close(src_fd); return -1; } } #if ENABLE_SELINUX if ((flags & (FILEUTILS_PRESERVE_SECURITY_CONTEXT|FILEUTILS_SET_SECURITY_CONTEXT)) && is_selinux_enabled() > 0 ) { security_context_t con; if (getfscreatecon(&con) == -1) { bb_perror_msg("getfscreatecon"); return -1; } if (con) { if (setfilecon(dest, con) == -1) { bb_perror_msg("setfilecon:%s,%s", dest, con); freecon(con); return -1; } freecon(con); } } #endif if (bb_copyfd_eof(src_fd, dst_fd) == -1) retval = -1; /* Careful with writing... */ if (close(dst_fd) < 0) { bb_perror_msg("error writing to '%s'", dest); retval = -1; } /* ...but read size is already checked by bb_copyfd_eof */ close(src_fd); /* "cp /dev/something new_file" should not * copy mode of /dev/something */ if (!S_ISREG(source_stat.st_mode)) return retval; goto preserve_mode_ugid_time; } dont_cat: /* Source is a symlink or a special file */ /* We are lazy here, a bit lax with races... */ if (dest_exists) { errno = EEXIST; ovr = ask_and_unlink(dest, flags); if (ovr <= 0) return ovr; } if (S_ISLNK(source_stat.st_mode)) { char *lpath = xmalloc_readlink_or_warn(source); if (lpath) { int r = symlink(lpath, dest); free(lpath); if (r < 0) { bb_perror_msg("can't create symlink '%s'", dest); return -1; } if (flags & FILEUTILS_PRESERVE_STATUS) if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) bb_perror_msg("can't preserve %s of '%s'", "ownership", dest); } /* _Not_ jumping to preserve_mode_ugid_time: * symlinks don't have those */ return 0; } if (S_ISBLK(source_stat.st_mode) || S_ISCHR(source_stat.st_mode) || S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) ) { if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { bb_perror_msg("can't create '%s'", dest); return -1; } } else { bb_error_msg("unrecognized file '%s' with mode %x", source, source_stat.st_mode); return -1; } preserve_mode_ugid_time: if (flags & FILEUTILS_PRESERVE_STATUS /* Cannot happen: */ /* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ ) { struct timeval times[2]; times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime; times[1].tv_usec = times[0].tv_usec = 0; /* BTW, utimes sets usec-precision time - just FYI */ if (utimes(dest, times) < 0) bb_perror_msg("can't preserve %s of '%s'", "times", dest); if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { source_stat.st_mode &= ~(S_ISUID | S_ISGID); bb_perror_msg("can't preserve %s of '%s'", "ownership", dest); } #if ENABLE_XATTR /* Preserve extended attributes. We must copy it after chown() * because it resets capabilities. */ if (copy_file_attr(source, dest) == -1) bb_perror_msg("can't preserve %s of '%s'", "extended attributes", dest); #endif if (chmod(dest, source_stat.st_mode) < 0) bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); } return retval; }
// Warn if we can't open a file and return a fd. int FAST_FUNC open_or_warn(const char *pathname, int flags) { return open3_or_warn(pathname, flags, 0666); }
int lpd_main(int argc UNUSED_PARAM, char *argv[]) { int spooling = spooling; // for compiler char *s, *queue; char *filenames[2]; // goto spool directory if (*++argv) xchdir(*argv++); // error messages of xfuncs will be sent over network xdup2(STDOUT_FILENO, STDERR_FILENO); // nullify ctrl/data filenames memset(filenames, 0, sizeof(filenames)); // read command s = queue = xmalloc_read_stdin(); // we understand only "receive job" command if (2 != *queue) { unsupported_cmd: printf("Command %02x %s\n", (unsigned char)s[0], "is not supported"); goto err_exit; } // parse command: "2 | QUEUE_NAME | '\n'" queue++; // protect against "/../" attacks // *strchrnul(queue, '\n') = '\0'; - redundant, sane() will do if (!*sane(queue)) return EXIT_FAILURE; // queue is a directory -> chdir to it and enter spooling mode spooling = chdir(queue) + 1; // 0: cannot chdir, 1: done // we don't free(s), we might need "queue" var later while (1) { char *fname; int fd; // int is easier than ssize_t: can use xatoi_u, // and can correctly display error returns (-1) int expected_len, real_len; // signal OK safe_write(STDOUT_FILENO, "", 1); // get subcommand // valid s must be of form: "SUBCMD | LEN | space | FNAME" // N.B. we bail out on any error s = xmalloc_read_stdin(); if (!s) { // (probably) EOF char *p, *q, var[2]; // non-spooling mode or no spool helper specified if (!spooling || !*argv) return EXIT_SUCCESS; // the only non-error exit // spooling mode but we didn't see both ctrlfile & datafile if (spooling != 7) goto err_exit; // reject job // spooling mode and spool helper specified -> exec spool helper // (we exit 127 if helper cannot be executed) var[1] = '\0'; // read and delete ctrlfile q = xmalloc_xopen_read_close(filenames[0], NULL); unlink(filenames[0]); // provide datafile name // we can use leaky setenv since we are about to exec or exit xsetenv("DATAFILE", filenames[1]); // parse control file by "\n" while ((p = strchr(q, '\n')) != NULL && isalpha(*q)) { *p++ = '\0'; // q is a line of <SYM><VALUE>, // we are setting environment string <SYM>=<VALUE>. // Ignoring "l<datafile>", exporting others: if (*q != 'l') { var[0] = *q++; xsetenv(var, q); } q = p; // next line } // helper should not talk over network. // this call reopens stdio fds to "/dev/null" // (no daemonization is done) bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO | DAEMON_ONLY_SANITIZE, NULL); BB_EXECVP(*argv, argv); exit(127); } // validate input. // we understand only "control file" or "data file" cmds if (2 != s[0] && 3 != s[0]) goto unsupported_cmd; if (spooling & (1 << (s[0]-1))) { printf("Duplicated subcommand\n"); goto err_exit; } // get filename *strchrnul(s, '\n') = '\0'; fname = strchr(s, ' '); if (!fname) { // bad_fname: printf("No or bad filename\n"); goto err_exit; } *fname++ = '\0'; // // s[0]==2: ctrlfile, must start with 'c' // // s[0]==3: datafile, must start with 'd' // if (fname[0] != s[0] + ('c'-2)) // goto bad_fname; // get length expected_len = bb_strtou(s + 1, NULL, 10); if (errno || expected_len < 0) { printf("Bad length\n"); goto err_exit; } if (2 == s[0] && expected_len > 16 * 1024) { // SECURITY: // ctrlfile can't be big (we want to read it back later!) printf("File is too big\n"); goto err_exit; } // open the file if (spooling) { // spooling mode: dump both files // job in flight has mode 0200 "only writable" sane(fname); fd = open3_or_warn(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200); if (fd < 0) goto err_exit; filenames[s[0] - 2] = xstrdup(fname); } else { // non-spooling mode: // 2: control file (ignoring), 3: data file fd = -1; if (3 == s[0]) fd = xopen(queue, O_RDWR | O_APPEND); } // signal OK safe_write(STDOUT_FILENO, "", 1); // copy the file real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len); if (real_len != expected_len) { printf("Expected %d but got %d bytes\n", expected_len, real_len); goto err_exit; } // get EOF indicator, see whether it is NUL (ok) // (and don't trash s[0]!) if (safe_read(STDIN_FILENO, &s[1], 1) != 1 || s[1] != 0) { // don't send error msg to peer - it obviously // doesn't follow the protocol, so probably // it can't understand us either goto err_exit; } if (spooling) { // chmod completely downloaded file as "readable+writable" fchmod(fd, 0600); // accumulate dump state // N.B. after all files are dumped spooling should be 1+2+4==7 spooling |= (1 << (s[0]-1)); // bit 1: ctrlfile; bit 2: datafile } free(s); close(fd); // NB: can do close(-1). Who cares? // NB: don't do "signal OK" write here, it will be done // at the top of the loop } // while (1) err_exit: // don't keep corrupted files if (spooling) { #define i spooling for (i = 2; --i >= 0; ) if (filenames[i]) unlink(filenames[i]); } return EXIT_FAILURE; }