Skip to content

Commit

Permalink
Meticulous permission setting in cache.
Browse files Browse the repository at this point in the history
Never ever end up with root-owned crud in a user-owned home directory.
Hat-tip: http://honnix.com/blog/archives/711
  • Loading branch information
isaacs committed Apr 20, 2011
1 parent 9673c9e commit ebd0b32
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 11 deletions.
68 changes: 65 additions & 3 deletions lib/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -384,17 +384,67 @@ function addLocalTarball (p, name, cb) {
})
}

// to maintain the cache dir's permissions consistently.
var cacheStat = null
function getCacheStat (cb) {
if (cacheStat) return cb(null, cacheStat)
fs.stat(npm.cache, function (er, st) {
if (er) return makeCacheDir(cb)
if (!st.isDirectory()) {
return log.er(cb, "invalid cache directory: "+npm.cache)(er)
}
return cb(null, cacheStat = st)
})
}

function makeCacheDir (cb) {
if (!process.getuid) return mkdir(npm.cache, DMODE, cb)

var uid = +process.getuid()
, gid = +process.getgid()

if (uid === 0) {
if (process.env.SUDO_UID) uid = +process.env.SUDO_UID
if (process.env.SUDO_GID) gid = +process.env.SUDO_GID
}
if (uid !== 0 || !process.env.HOME) {
cacheStat = {uid: uid, gid: gid}
return mkdir(npm.cache, DMODE, uid, gid, function (er) {
return cb(er, cacheStat)
})
}
fs.stat(process.env.HOME, function (er, st) {
if (er) return log.er(cb, "homeless?")(er)
cacheStat = st
log.silly([st.uid, st.gid], "uid, gid for cache dir")
return mkdir(npm.cache, DMODE, st.uid, st.gid, function (er) {
return cb(er, cacheStat)
})
})
}




function addPlacedTarball (p, name, cb) {
if (!cb) cb = name, name = ""
getCacheStat(function (er, cs) {
if (er) return cb(er)
return addPlacedTarball_(p, name, cs.uid, cs.gid, cb)
})
}

function addPlacedTarball_ (p, name, uid, gid, cb) {
// now we know it's in place already as .cache/name/ver/package.tgz
// unpack to .cache/name/ver/package/, read the package.json,
// and fire cb with the json data.
var target = path.dirname(p)
, folder = path.join(target, "package")
, json = path.join(target, "package.json")

rm(folder, function (er) {
if (er) return log.er(cb, "Could not remove "+folder)(er)
tar.unpack(p, folder, function (er) {
tar.unpack(p, folder, null, null, uid, gid, function (er) {
if (er) return log.er(cb, "Could not unpack "+p+" to "+target)(er)
// calculate the sha of the file that we just unpacked.
// this is so that the data is available when publishing.
Expand All @@ -410,6 +460,8 @@ function addPlacedTarball (p, name, cb) {
asyncMap([json, p], function (f, cb) {
log.verbose(FMODE.toString(8), "chmod "+f)
fs.chmod(f, FMODE, cb)
}, function (f, cb) {
fs.chown(f, uid, gid, cb)
}, function (er) {
cb(er, data)
})
Expand Down Expand Up @@ -447,8 +499,18 @@ function addLocalDirectory (p, name, cb) {

function addTmpTarball (tgz, name, cb) {
if (!cb) cb = name, name = ""
var contents = path.join(path.dirname(tgz),"contents")
tar.unpack(tgz, path.resolve(contents, "package"), function (er) {
getCacheStat(function (er, cs) {
if (er) return cb(er)
return addTmpTarball_(tgz, name, cs.uid, cs.gid, cb)
})
}

function addTmpTarball_ (tgz, name, uid, gid, cb) {
var contents = path.resolve(path.dirname(tgz), "contents")
tar.unpack( tgz, path.resolve(contents, "package")
, null, null
, uid, gid
, function (er) {
if (er) return log.er(cb, "couldn't unpack "+tgz+" to "+contents)(er)
fs.readdir(contents, function (er, folder) {
if (er) return log.er(cb, "couldn't readdir "+contents)(er)
Expand Down
25 changes: 22 additions & 3 deletions lib/utils/mkdir-p.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ function mkdir (ensure, mode, uid, gid, cb_) {
cbs.forEach(function (c) { c(er) })
}

if (uid === null && gid === null) {
return mkdir_(ensure, mode, uid, gid, cb)
}

uidNumber(uid, gid, function (er, uid, gid) {
if (er) return cb(er)
mkdir_(ensure, mode, uid, gid, cb)
Expand All @@ -56,6 +60,7 @@ function mkdir_ (ensure, mode, uid, gid, cb) {
return walkDirs(ensure, mode, uid, gid, cb)
})
}

function done (ensure, mode, uid, gid, cb) {
// now the directory has been created.
// chown it to the desired uid/gid
Expand All @@ -67,9 +72,12 @@ function done (ensure, mode, uid, gid, cb) {
fs.chown(ensure, uid, gid, cb)
})
}

function walkDirs (ensure, mode, uid, gid, cb) {
var dirs = ensure.split("/")
, walker = []
, foundUID = null
, foundGID = null
walker.push(dirs.shift()) // gobble the "/" first
;(function S (d) {
if (d === undefined) {
Expand All @@ -79,17 +87,28 @@ function walkDirs (ensure, mode, uid, gid, cb) {
var dir = walker.join("/")
fs.stat(dir, function STATCB (er, s) {
if (er) {
if (foundUID) uid = foundUID
if (foundGID) gid = foundGID
fs.mkdir(dir, mode, function MKDIRCB (er, s) {
if (er && er.message.indexOf("EEXIST") === 0) {
// handle stat race
return fs.stat(dir, STATCB)
}
if (er) return cb(er)
S(dirs.shift())
if (uid && gid) {
fs.chown(dir, uid, gid, function (er) {
S(dirs.shift())
})
} else {
S(dirs.shift())
}
})
} else {
if (s.isDirectory()) S(dirs.shift())
else cb(new Error("Failed to mkdir "+dir+": File exists"))
if (s.isDirectory()) {
if (uid === null && typeof s.uid === "number") foundUID = s.uid
if (gid === null && typeof s.gid === "number") foundGID = s.gid
S(dirs.shift())
} else cb(new Error("Failed to mkdir "+dir+": File exists"))
}
})
})(dirs.shift())
Expand Down
34 changes: 31 additions & 3 deletions lib/utils/npm-registry-client/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var GET = require("./request").GET
, path = require("path")
, log = require("../log")
, mkdir = require("../mkdir-p")
, cacheStat = null

function get (project, version, timeout, nofollow, cb) {
if (typeof cb !== "function") cb = nofollow, nofollow = false
Expand Down Expand Up @@ -66,9 +67,36 @@ function get_ (uri, timeout, cache, stat, data, nofollow, cb) {
cb(er, data, raw, response)
}

mkdir(path.dirname(cache), 0755, function (er) {
if (er) return saved()
fs.writeFile(cache, JSON.stringify(data), saved)
saveToCache(cache, data, saved)
})
}

function saveToCache (cache, data, saved) {
if (cacheStat) {
return saveToCache_(cache, data, cacheStat.uid, cacheStat.gid, saved)
}
fs.stat(npm.cache, function (er, st) {
if (er) {
return fs.stat(process.env.HOME, function (er, st) {
// if this fails, oh well.
if (er) return saved()
cacheStat = st
return saveToCache(cache, data, saved)
})
}
cacheStat = st || { uid: null, gid: null }
return saveToCache(cache, data, saved)
})
}

function saveToCache_ (cache, data, uid, gid, saved) {
mkdir(path.dirname(cache), 0755, uid, gid, function (er) {
if (er) return saved()
fs.writeFile(cache, JSON.stringify(data), function (er) {
if (er || uid === null || gid === null) {
return saved()
}
fs.chown(cache, uid, gid, saved)
})
})
}
6 changes: 4 additions & 2 deletions lib/utils/tar.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ function unpack_ ( tarball, unpackTarget, dMode, fMode, uid, gid, cb ) {
// cp the gzip of the tarball, pipe the stdout into tar's stdin
// gzip {tarball} --decompress --stdout \
// | tar -mvxpf - --strip-components=1 -C {unpackTarget}
gunzTarPerm(tarball, tmp, dMode, fMode, uid, gid, function (er, folder) {
gunzTarPerm( tarball, tmp
, dMode, fMode
, uid, gid
, function (er, folder) {
if (er) return cb(er)
log.verbose(folder, "gunzed")
rm(unpackTarget, function (er) {
Expand All @@ -161,7 +164,6 @@ function gunzTarPerm (tarball, tmp, dMode, fMode, uid, gid, cb) {
if (!dMode) dMode = DMODE
if (!fMode) fMode = FMODE
log.silly([dMode.toString(8), fMode.toString(8)], "gunzTarPerm modes")
//log.warn("gunzTarPerm")
//console.error(npm.config.get("gzipbin")+" --decompress --stdout "
// +tarball+" | "+npm.config.get("tar")+" -mvxpf - --no-same-owner -C "
// +tmp)
Expand Down

0 comments on commit ebd0b32

Please sign in to comment.