#include <errno.h>
#include <fcntl.h>
#include <fts.h>
#include <libgen.h>
#include <limits.h>
#include <sys/mount.h>
#include <sys/param.h> // MAXBSIZE
#include <sys/stat.h>
#include <string.h>
#include <stdio.h> // rename(2)?
#include <stdlib.h> // malloc(3)
#include <sys/types.h>
#include <unistd.h>
#include <sys/ucred.h>
#include <IOKit/kext/kextmanager_types.h>
#include <IOKit/kext/OSKextPrivate.h>
#ifndef kOSKextLogCacheFlag
#define kOSKextLogCacheFlag kOSKextLogArchiveFlag
#endif // no kOSKextLogCacheFlag
#define STRICT_SAFETY 0 // since our wrappers need to call the real calls
#include "safecalls.h" // w/o STRICT_SAFETY, will #define mkdir, etc
#include "kext_tools_util.h"
#define RESTOREDIR(savedir) do { if (savedir != -1 && restoredir(savedir)) \
OSKextLog( NULL, \
kOSKextLogErrorLevel | kOSKextLogCacheFlag, \
"%s: ALERT: couldn't restore CWD", __func__); \
} while(0)
static int findmnt(dev_t devid, char mntpt[MNAMELEN])
{
int rval = ELAST + 1;
int i, nmnts = getfsstat(NULL, 0, MNT_NOWAIT);
size_t bufsz;
struct statfs *mounts = NULL;
if (nmnts <= 0) goto finish;
bufsz = nmnts * sizeof(struct statfs);
if (!(mounts = malloc(bufsz))) goto finish;
if (-1 == getfsstat(mounts, bufsz, MNT_NOWAIT)) goto finish;
for (i = 0; i < nmnts; i++) {
struct statfs *sfs = &mounts[i];
if (sfs->f_fsid.val[0] == devid) {
if (strlcpy(mntpt, sfs->f_mntonname, MNAMELEN) >= MNAMELEN) {
goto finish;
}
rval = 0;
break;
}
}
finish:
if (mounts) free(mounts);
return rval;
}
static int spolicy(int scopefd, int candfd)
{
int bsderr = -1;
struct stat candsb, scopesb;
char path[PATH_MAX] = "<unknown>";
if ((bsderr = fstat(candfd, &candsb))) goto finish; if ((bsderr = fstat(scopefd, &scopesb))) goto finish;
if (candsb.st_dev != scopesb.st_dev ) {
bsderr = -1;
errno = EPERM;
char scopemnt[MNAMELEN];
if (findmnt(scopesb.st_dev, scopemnt) == 0) {
(void)fcntl(candfd, F_GETPATH, path);
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogCacheFlag | kOSKextLogFileAccessFlag,
"ALERT: %s does not appear to be on %s.", path, scopemnt);
} else {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"ALERT: dev_t mismatch (%d != %d).",
candsb.st_dev, scopesb.st_dev);
}
goto finish;
}
if (candsb.st_uid != 0) {
(void)fcntl(candfd, F_GETPATH, path);
OSKextLog(NULL, kOSKextLogErrorLevel | kOSKextLogFileAccessFlag,
"WARNING: %s: owner not root!", path);
}
finish:
return bsderr;
}
int schdirparent(int fdvol, const char *path, int *olddir, char child[PATH_MAX])
{
int bsderr = -1;
int dirfd = -1, savedir = -1;
char parent[PATH_MAX];
if (olddir) *olddir = -1;
if (!path) goto finish;
if (strlcpy(parent, path, PATH_MAX) >= PATH_MAX) goto finish;
if (strlcpy(parent, dirname(parent), PATH_MAX) >= PATH_MAX) goto finish;
if (-1 == (dirfd = open(parent, O_RDONLY, 0))) goto finish;
errno = 0;
if (spolicy(fdvol, dirfd)) {
if (errno == EPERM)
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"Policy violation opening %s.", parent);
goto finish;
}
if (child) {
if (strlcpy(child, path, PATH_MAX) >= PATH_MAX) goto finish;
if (strlcpy(child, basename(child), PATH_MAX) >= PATH_MAX)
goto finish;
}
if (olddir) {
if (-1 == (savedir = open(".", O_RDONLY))) goto finish;
*olddir = savedir;
}
if ((bsderr = fchdir(dirfd))) goto finish;
finish:
if (bsderr) {
if (savedir != -1) close(savedir);
if (olddir && *olddir != -1) close(*olddir);
}
if (dirfd != -1) close(dirfd);
return bsderr;
}
int sopen(int fdvol, const char *path, int flags, mode_t mode )
{
int rfd = -1;
int candfd = -1;
char child[PATH_MAX];
int savedir = -1;
if (flags & O_CREAT)
flags |= O_EXCL | O_NOFOLLOW;
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
if (-1 == (candfd = open(child, flags, mode))) goto finish;
if (spolicy(fdvol, candfd)) goto finish;
rfd = candfd;
finish:
if (candfd != -1 && rfd != candfd) {
close(candfd);
}
RESTOREDIR(savedir);
return rfd;
}
int schdir(int fdvol, const char *path, int *savedir)
{
char cpath[PATH_MAX];
if (strlcpy(cpath, path, PATH_MAX) >= PATH_MAX ||
strlcat(cpath, "/.", PATH_MAX) >= PATH_MAX) return -1;
return schdirparent(fdvol, cpath, savedir, NULL);
}
int restoredir(int savedir)
{
int cherr = -1, clerr = -1;
if (savedir != -1) {
cherr = fchdir(savedir);
clerr = close(savedir);
}
return cherr ? cherr : clerr;
}
int smkdir(int fdvol, const char *path, mode_t mode)
{
int bsderr = -1;
int savedir = -1;
char child[PATH_MAX];
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
if ((bsderr = mkdir(child, mode))) goto finish;
finish:
RESTOREDIR(savedir);
return bsderr;
}
int srmdir(int fdvol, const char *path)
{
int bsderr = -1;
char child[PATH_MAX];
int savedir = -1;
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
bsderr = rmdir(child);
finish:
RESTOREDIR(savedir);
return bsderr;
}
int sunlink(int fdvol, const char *path)
{
int bsderr = -1;
char child[PATH_MAX];
int savedir = -1;
if (schdirparent(fdvol, path, &savedir, child)) goto finish;
bsderr = unlink(child);
finish:
RESTOREDIR(savedir);
return bsderr;
}
int srename(int fdvol, const char *oldpath, const char *newpath)
{
int bsderr = -1;
int savedir = -1;
char oldname[PATH_MAX];
char newname[PATH_MAX];
if (strlcpy(newname, newpath, PATH_MAX) >= PATH_MAX) goto finish;
if (strlcpy(newname, basename(newname), PATH_MAX) >= PATH_MAX)goto finish;
if (schdirparent(fdvol, oldpath, &savedir, oldname)) goto finish;
bsderr = rename(oldname, newname);
finish:
RESTOREDIR(savedir);
return bsderr;
}
int sdeepunlink(int fdvol, char *path)
{
int rval = ELAST + 1;
char * const pathv[2] = { path, NULL };
int ftsoptions = 0;
FTS * fts;
FTSENT * fent;
ftsoptions |= FTS_PHYSICAL; ftsoptions |= FTS_XDEV; ftsoptions |= FTS_NOSTAT;
if ((fts = fts_open(pathv, ftsoptions, NULL)) == NULL) goto finish;
rval = 0;
while ((fent = fts_read(fts)) ) {
switch (fent->fts_info) {
case FTS_DC: case FTS_D: case FTS_DOT: break;
case FTS_DNR: case FTS_ERR: case FTS_NS: rval |= fent->fts_errno;
break;
case FTS_SL: case FTS_SLNONE: case FTS_DEFAULT: case FTS_F: case FTS_NSOK: default: rval |= sunlink(fdvol, fent->fts_accpath);
break;
case FTS_DP: rval |= srmdir(fdvol, fent->fts_accpath);
break;
} }
if (!rval) rval = errno;
if (fts_close(fts) < 0) {
OSKextLog( NULL,
kOSKextLogErrorLevel | kOSKextLogGeneralFlag | kOSKextLogFileAccessFlag,
"fts_close failed - %s.", strerror(errno));
}
finish:
return rval;
}
int sdeepmkdir(int fdvol, const char *path, mode_t mode)
{
int bsderr = -1;
struct stat sb;
char parent[PATH_MAX];
if (strlen(path) == 0) goto finish;
if (0 == stat(path, &sb)) {
if (sb.st_mode & S_IFDIR == 0) {
bsderr = ENOTDIR;
goto finish;
} else {
bsderr = 0; goto finish;
}
} else if (errno != ENOENT) {
goto finish; } else {
if (strlcpy(parent, path, PATH_MAX) >= PATH_MAX) goto finish;
if (strlcpy(parent, dirname(parent), PATH_MAX) >= PATH_MAX) goto finish;
if ((bsderr = sdeepmkdir(fdvol, parent, mode))) goto finish;
}
bsderr = smkdir(fdvol, path, mode);
finish:
return bsderr;
}
#define min(a,b) ((a) < (b) ? (a) : (b))
int scopyfile(int srcfdvol, const char *srcpath, int dstfdvol, const char *dstpath)
{
int bsderr = -1;
int srcfd = -1, dstfd = -1;
struct stat srcsb;
char dstparent[PATH_MAX];
mode_t dirmode;
void *buf = NULL; off_t bytesLeft, thisTime;
if (-1 == (srcfd = sopen(srcfdvol, srcpath, O_RDONLY, 0))) goto finish;
if (fstat(srcfd, &srcsb)) goto finish;
dirmode = ((srcsb.st_mode&~S_IFMT) | S_IWUSR | S_IXUSR );
if (dirmode & S_IRGRP) dirmode |= S_IXGRP; if (dirmode & S_IROTH) dirmode |= S_IXOTH;
if (strlcpy(dstparent, dstpath, PATH_MAX) >= PATH_MAX) goto finish;
if (strlcpy(dstparent, dirname(dstparent), PATH_MAX)>=PATH_MAX) goto finish;
if ((sdeepmkdir(dstfdvol, dstparent, dirmode))) goto finish;
(void)sunlink(dstfdvol, dstpath);
dstfd = sopen(dstfdvol, dstpath, O_CREAT|O_WRONLY, srcsb.st_mode | S_IWUSR);
if (dstfd == -1) goto finish;
if (!(buf = malloc(MAXBSIZE))) goto finish;;
for (bytesLeft = srcsb.st_size; bytesLeft > 0; bytesLeft -= thisTime) {
thisTime = min(bytesLeft, MAXBSIZE);
if (read(srcfd, buf, thisTime) != thisTime) goto finish;
if (write(dstfd, buf, thisTime) != thisTime) goto finish;
}
if (bsderr = fchmod(dstfd, srcsb.st_mode)) goto finish;
finish:
if (srcfd != -1) close(srcfd);
if (dstfd != -1) close(dstfd);
if (buf) free(buf);
return bsderr;
}