aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common.nim70
-rw-r--r--src/config.nim6
-rw-r--r--src/feature/syncinstall.nim38
-rw-r--r--src/main.nim10
-rw-r--r--src/pacman.nim5
-rw-r--r--src/utils.nim116
6 files changed, 191 insertions, 54 deletions
diff --git a/src/common.nim b/src/common.nim
index 045f1f6..c2f7053 100644
--- a/src/common.nim
+++ b/src/common.nim
@@ -207,7 +207,9 @@ proc formatArgument*(target: PackageTarget): string =
proc ensureTmpOrError*(config: Config): Option[string] =
let tmpRootExists = try:
+ let user = initialUser.get(currentUser)
discard config.tmpRoot.existsOrCreateDir()
+ discard chown(config.tmpRoot, (Uid) user.uid, (Gid) user.gid)
true
except:
false
@@ -226,15 +228,22 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string],
discard close(1)
discard close(2)
+ dropPrivileges()
execResult(args)))
let (workFirstCommit, checkFirst) = if firstCommit.isSome:
(firstCommit, false)
else:
- (runProgram(gitCmd, "-C", repoPath,
- "rev-list", "--max-parents=0", "@").optLast, true)
- let realLastThreeCommits = runProgram(gitCmd, "-C", repoPath,
- "rev-list", "--max-count=3", "@")
+ (forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath,
+ "rev-list", "--max-parents=0", "@")))
+ .output.optLast, true)
+
+ let (realLastThreeCommits, _) = forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath,
+ "rev-list", "--max-count=3", "@")))
let index = workFirstCommit.map(c => realLastThreeCommits.find(c)).get(-1)
let lastThreeCommits = if index >= 0:
realLastThreeCommits[0 .. index]
@@ -248,8 +257,12 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string],
if checkout1Code != 0:
none(string)
else:
- let foundVersion = runProgram(pkgLibDir & "/bisect",
- compareMethod, repoPath & "/" & relativePath, version).optFirst
+ let foundVersion = forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(pkgLibDir & "/bisect",
+ compareMethod, repoPath & "/" & relativePath, version)))
+ .output.optFirst
+
let checkout2Code = forkExecWithoutOutput(gitCmd, "-C", repoPath,
"checkout", lastThreeCommits[0])
@@ -286,8 +299,11 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string],
discard forkExecWithoutOutput(gitCmd, "-C", repoPath,
"bisect", "run", pkgLibDir & "/bisect", compareMethod, relativePath, version)
- let commit = runProgram(gitCmd, "-C", repoPath,
- "rev-list", "--max-count=1", "refs/bisect/bad").optFirst
+ let commit = forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath,
+ "rev-list", "--max-count=1", "refs/bisect/bad")))
+ .output.optFirst
discard forkExecWithoutOutput(gitCmd, "-C", repoPath,
"bisect", "reset")
@@ -305,8 +321,15 @@ proc bisectVersion(repoPath: string, debug: bool, firstCommit: Option[string],
none(string)
proc obtainSrcInfo*(path: string): string =
- execProcess(bashCmd, ["-c", """cd "$2" && "$1" --printsrcinfo""",
- "bash", makePkgCmd, path], options = {})
+ let (output, code) = forkWaitRedirect(() => (block:
+ discard chdir(path)
+ dropPrivileges()
+ execResult(makePkgCmd, "--printsrcinfo")))
+
+ if code == 0:
+ output.foldl(a & b & "\n", "")
+ else:
+ ""
proc reloadPkgInfos*(config: Config, path: string, pkgInfos: seq[PackageInfo]): seq[PackageInfo] =
let srcInfo = obtainSrcInfo(path)
@@ -337,17 +360,22 @@ proc obtainBuildPkgInfosInternal(config: Config, bases: seq[LookupBaseGroup],
removeDirQuiet(repoPath)
try:
- if forkWait(() => execResult(gitCmd, "-C", config.tmpRoot,
- "clone", "-q", git.url, "-b", git.branch,
- "--single-branch", base)) == 0:
+ if forkWait(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", config.tmpRoot,
+ "clone", "-q", git.url, "-b", git.branch,
+ "--single-branch", base))) == 0:
let commit = bisectVersion(repoPath, config.debug, none(string),
"source", git.path, version)
if commit.isNone:
@[]
else:
- discard forkWait(() => execResult(gitCmd, "-C", repoPath,
- "checkout", "-q", commit.unsafeGet))
+ discard forkWait(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath,
+ "checkout", "-q", commit.unsafeGet)))
+
let srcInfo = obtainSrcInfo(repoPath & "/" & git.path)
parseSrcInfo(repo, srcInfo, config.arch,
git.url, some(git.branch), commit, some(git.path))
@@ -399,13 +427,17 @@ proc cloneRepo*(config: Config, basePackages: seq[PackageInfo]): (int, Option[st
let aur = basePackages[0].repo == "aur"
let branch = gitBranch.get("master")
- let cloneCode = forkWait(() => execResult(gitCmd, "-C", config.tmpRoot,
- "clone", "-q", gitUrl, "-b", branch, "--single-branch", base))
+ let cloneCode = forkWait(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", config.tmpRoot,
+ "clone", "-q", gitUrl, "-b", branch, "--single-branch", base)))
if cloneCode == 0:
if gitCommit.isSome:
- let code = forkWait(() => execResult(gitCmd, "-C", repoPath,
- "reset", "-q", "--hard", gitCommit.unsafeGet))
+ let code = forkWait(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath,
+ "reset", "-q", "--hard", gitCommit.unsafeGet)))
(code, none(string))
elif aur:
(0, none(string))
diff --git a/src/config.nim b/src/config.nim
index 25ab011..8b0e0f5 100644
--- a/src/config.nim
+++ b/src/config.nim
@@ -111,10 +111,10 @@ proc obtainConfig*(config: PacmanConfig): Config =
let db = config.db
let color = config.colorMode.get
- let (userId, userName) = getUser()
+ let user = initialUser.get(currentUser)
let tmpRoot = options.opt("TmpDir").get("/tmp/pakku-${USER}")
- .replace("${UID}", $userId)
- .replace("${USER}", userName)
+ .replace("${UID}", $user.uid)
+ .replace("${USER}", user.name)
let aurComments = options.hasKey("AurComments")
let checkIgnored = options.hasKey("CheckIgnored")
let printAurNotFound = options.hasKey("PrintAurNotFound")
diff --git a/src/feature/syncinstall.nim b/src/feature/syncinstall.nim
index 30dc170..88512a5 100644
--- a/src/feature/syncinstall.nim
+++ b/src/feature/syncinstall.nim
@@ -260,16 +260,24 @@ proc editLoop(config: Config, base: string, repoPath: string, gitPath: Option[st
else:
discard forkWait(proc: int =
discard chdir(buildPath(repoPath, gitPath))
+ dropPrivileges()
execResult(bashCmd, "-c", """$1 "$2"""", "bash", editor, file))
editFileLoop(file)
else:
res
let rawFiles = if gitPath.isSome:
- runProgram(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@",
- gitPath.unsafeGet & "/").map(s => s[gitPath.unsafeGet.len + 1 .. ^1])
+ forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@",
+ gitPath.unsafeGet & "/")))
+ .output
+ .map(s => s[gitPath.unsafeGet.len + 1 .. ^1])
else:
- runProgram(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@")
+ forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(gitCmd, "-C", repoPath, "ls-tree", "-r", "--name-only", "@")))
+ .output
let files = ("PKGBUILD" & rawFiles.filter(x => x != ".SRCINFO")).deduplicate
@@ -321,15 +329,19 @@ proc buildLoop(config: Config, pkgInfos: seq[PackageInfo], noconfirm: bool,
else:
let envExt = getenv("PKGEXT")
let confExt = if envExt == nil or envExt.len == 0:
- runProgram(bashCmd, "-c",
- "source \"$@\" && echo \"$PKGEXT\"",
- "bash", workConfFile).optFirst.get("")
+ forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(bashCmd, "-c",
+ "source \"$@\" && echo \"$PKGEXT\"",
+ "bash", workConfFile)))
+ .output.optFirst.get("")
else:
$envExt
let buildCode = forkWait(proc: int =
if chdir(buildPath) == 0:
discard unsetenv("MAKEPKG_CONF")
+ dropPrivileges()
if not noextract:
removeDirQuiet(buildPath & "src")
@@ -432,6 +444,7 @@ proc buildFromSources(config: Config, commonArgs: seq[Argument],
let code = forkWait(() => (block:
discard chdir(buildPath(repoPath, gitPath))
+ dropPrivileges()
execResult(bashCmd, "-c", config.preBuildCommand.unsafeGet)))
if code != 0 and printColonUserChoice(config.color,
@@ -491,11 +504,9 @@ proc installGroupFromSources(config: Config, commonArgs: seq[Argument],
handleTmpRoot(true)
(newSeq[(string, string)](), buildCode)
else:
- let res = printColonUserChoice(config.color,
+ if currentUser.uid != 0 and printColonUserChoice(config.color,
tr"Continue installing?", ['y', 'n'], 'y', 'n',
- noconfirm, 'y')
-
- if res != 'y':
+ noconfirm, 'y') != 'y':
handleTmpRoot(false)
(newSeq[(string, string)](), 1)
else:
@@ -632,6 +643,7 @@ proc handleInstall(args: seq[Argument], config: Config, upgradeCount: int,
discard open("/dev/null")
discard close(2)
discard open("/dev/null")
+ dropPrivileges()
execResult(gpgCmd, "--list-keys", pgpKeys[index]))) == 0:
keysLoop(index + 1, skipKeys)
else:
@@ -649,13 +661,15 @@ proc handleInstall(args: seq[Argument], config: Config, upgradeCount: int,
('a', tr"abort operation"))
keysLoop(index, newSkipKeys)
elif res == 'y' or newSkipKeys:
- let importCode = if config.pgpKeyserver.isSome:
+ let importCode = forkWait(() => (block:
+ dropPrivileges()
+ if config.pgpKeyserver.isSome:
forkWait(() => execResult(gpgCmd,
"--keyserver", config.pgpKeyserver.unsafeGet,
"--recv-keys", pgpKeys[index]))
else:
forkWait(() => execResult(gpgCmd,
- "--recv-keys", pgpKeys[index]))
+ "--recv-keys", pgpKeys[index]))))
if importCode == 0 or newSkipKeys or noconfirm:
keysLoop(index + 1, newSkipKeys)
diff --git a/src/main.nim b/src/main.nim
index b5d700b..26e2908 100644
--- a/src/main.nim
+++ b/src/main.nim
@@ -87,12 +87,12 @@ proc handleSync(args: seq[Argument], config: Config): int =
let isNonDefaultRoot = not config.isRootDefault
let isDowngrade = args.count((some("u"), "sysupgrade")) >= 2
let isSkipDeps = args.check((some("d"), "nodeps"))
- let isRoot = getuid() == 0
+ let isRootNoDrop = currentUser.uid == 0 and not canDropPrivileges()
let build = args.check((none(string), "build"))
let noaur = args.check((none(string), "noaur"))
- let noBuild = isNonDefaultRoot or isDowngrade or isSkipDeps or isRoot
+ let noBuild = isNonDefaultRoot or isDowngrade or isSkipDeps or isRootNoDrop
if build and noBuild:
if isNonDefaultRoot:
@@ -104,7 +104,7 @@ proc handleSync(args: seq[Argument], config: Config): int =
elif isSkipDeps:
printError(config.color, tr"dependency check is skipped" & " -- " &
tr"building is not allowed")
- elif isRoot:
+ elif isRootNoDrop:
printError(config.color, tr"running as root" & " -- " &
tr"building is not allowed")
1
@@ -121,7 +121,7 @@ proc handleSync(args: seq[Argument], config: Config): int =
elif isSkipDeps:
printWarning(config.color, tr"dependency check is skipped" & " -- " &
tr"'$#' is assumed" % ["--noaur"])
- elif isRoot:
+ elif isRootNoDrop:
printWarning(config.color, tr"running as root" & " -- " &
tr"'$#' is assumed" % ["--noaur"])
@@ -171,7 +171,7 @@ proc handleHelp(operation: OperationType) =
.map(o => @["-" & o.pair.short.get])
.optFirst.get(@[]) & @["-h"]
- let lines = runProgram(pacmanCmd & operationArgs)
+ let (lines, _) = forkWaitRedirect(() => execResult(pacmanCmd & operationArgs))
for line in lines:
echo(line.replace(re"\bpacman\b", "pakku"))
diff --git a/src/pacman.nim b/src/pacman.nim
index 5315df1..ecec5dc 100644
--- a/src/pacman.nim
+++ b/src/pacman.nim
@@ -352,7 +352,10 @@ proc obtainPacmanConfig*(args: seq[Argument]): PacmanConfig =
let ignorePkgs = getAll((none(string), "ignore")).toSet
let ignoreGroups = getAll((none(string), "ignoregroups")).toSet
- let hasKeyserver = runProgram(gpgConfCmd, "--list-options", "gpg")
+ let hasKeyserver = forkWaitRedirect(() => (block:
+ dropPrivileges()
+ execResult(gpgConfCmd, "--list-options", "gpg")))
+ .output
.filter(s => s.len > 10 and s[0 .. 9] == "keyserver:" and not (s[^2] == ':'))
.len > 0
diff --git a/src/utils.nim b/src/utils.nim
index 986a4a0..662168a 100644
--- a/src/utils.nim
+++ b/src/utils.nim
@@ -1,5 +1,5 @@
import
- future, hashes, options, os, osproc, posix, strutils, tables
+ future, hashes, options, os, osproc, posix, sequtils, strutils, tables
type
HaltError* = object of Exception
@@ -9,6 +9,15 @@ type
color*: Option[bool]
error*: bool
+ User* = tuple[
+ name: string,
+ uid: int,
+ gid: int,
+ groups: seq[int],
+ home: string,
+ shell: string
+ ]
+
const
pkgLibDir* = getenv("PROG_PKGLIBDIR")
localStateDir* = getenv("PROG_LOCALSTATEDIR")
@@ -133,13 +142,14 @@ template blockSignals*(signals: openArray[cint],
finally:
unblock()
-proc forkWait*(call: () -> int): int =
+proc forkWaitInternal(call: () -> int, beforeWait: () -> void): int =
blockSignals(interruptSignals, unblock):
let pid = fork()
if pid == 0:
unblock()
quit(call())
else:
+ beforeWait()
var status: cint = 1
discard waitpid(pid, status, 0)
if WIFEXITED(status):
@@ -148,14 +158,46 @@ proc forkWait*(call: () -> int): int =
discard kill(getpid(), status)
return 1
-proc runProgram*(args: varargs[string]): seq[string] =
- let output = execProcess(args[0], @args[1 .. ^1], options = {})
- if output.len == 0:
- @[]
- elif output.len > 0 and $output[^1] == "\n":
- output[0 .. ^2].split("\n")
- else:
- output.split("\n")
+proc forkWait*(call: () -> int): int =
+ forkWaitInternal(call, proc = discard)
+
+proc forkWaitRedirect*(call: () -> int): tuple[output: seq[string], code: int] =
+ var fd: array[2, cint]
+ discard pipe(fd)
+
+ var data = newSeq[char]()
+
+ let code = forkWaitInternal(() => (block:
+ discard close(fd[0])
+ discard close(1)
+ discard dup(fd[1])
+ discard close(fd[1])
+ discard close(0)
+ discard open("/dev/null")
+ discard close(2)
+ discard open("/dev/null")
+ call()), () => (block:
+ discard close(fd[1])
+ var buffer: array[80, char]
+ while true:
+ let count = read(fd[0], addr(buffer[0]), buffer.len)
+ if count <= 0:
+ break
+ data &= buffer[0 .. count - 1]
+ discard close(fd[0])))
+
+ var output = newStringOfCap(data.len)
+ for c in data:
+ output &= c
+
+ let lines = if output.len == 0:
+ @[]
+ elif output.len > 0 and $output[^1] == "\n":
+ output[0 .. ^2].split("\n")
+ else:
+ output.split("\n")
+
+ (lines, code)
proc setenv*(name: cstring, value: cstring, override: cint): cint
{.importc, header: "<stdlib.h>".}
@@ -163,14 +205,60 @@ proc setenv*(name: cstring, value: cstring, override: cint): cint
proc unsetenv*(name: cstring): cint
{.importc, header: "<stdlib.h>".}
-proc getUser*: (int, string) =
- let uid = getuid()
+proc getgrouplist*(user: cstring, group: Gid, groups: ptr cint, ngroups: var cint): cint
+ {.importc, header: "<grp.h>".}
+
+proc setgroups*(size: csize, groups: ptr cint): cint
+ {.importc, header: "<grp.h>".}
+
+proc getUser(uid: int): User =
while true:
var pw = getpwent()
if pw == nil:
+ endpwent()
raise newException(SystemError, "")
- if pw.pw_uid == uid:
- return (uid.int, $pw.pw_name)
+ if pw.pw_uid.int == uid:
+ var groups: array[100, cint]
+ var ngroups: cint = 100
+ if getgrouplist(pw.pw_name, pw.pw_gid, addr(groups[0]), ngroups) < 0:
+ raise newException(SystemError, "")
+ else:
+ let groupsSeq = groups[0 .. ngroups - 1].map(x => x.int)
+ let res = ($pw.pw_name, pw.pw_uid.int, pw.pw_gid.int, groupsSeq,
+ $pw.pw_dir, $pw.pw_shell)
+ endpwent()
+ return res
+
+let currentUser* = getUser(getuid().int)
+
+let initialUser* = try:
+ let sudoUid = getenv("SUDO_UID")
+ let polkitUid = getenv("PKEXEC_UID")
+
+ let uidString = if sudoUid != nil and sudoUid.len > 0:
+ some($sudoUid)
+ elif polkitUid != nil and polkitUid.len > 0:
+ some($polkitUid)
+ else:
+ none(string)
+
+ let uid = uidString.get.parseInt
+ if uid == 0: none(User) else: some(getUser(uid))
+except:
+ none(User)
+
+proc canDropPrivileges*(): bool =
+ initialUser.isSome
+
+proc dropPrivileges*() =
+ if initialUser.isSome:
+ let user = initialUser.unsafeGet
+ var groups = user.groups.map(x => x.cint)
+ discard setgroups(user.groups.len, addr(groups[0]));
+ discard setgid((Gid) user.gid)
+ discard setuid((Uid) user.uid)
+ discard setenv("HOME", user.home, 1)
+ discard setenv("SHELL", user.shell, 1)
proc toString*[T](arr: array[T, char], length: Option[int]): string =
var workLength = length.get(T.high + 1)