From 72b8161f2f37a0a6b091e300810a40e9a775c64b Mon Sep 17 00:00:00 2001 From: kitsunyan Date: Sun, 8 Apr 2018 23:54:18 +0300 Subject: Allow running via sudo --- src/common.nim | 70 ++++++++++++++++++-------- src/config.nim | 6 +-- src/feature/syncinstall.nim | 38 ++++++++++----- src/main.nim | 10 ++-- src/pacman.nim | 5 +- src/utils.nim | 116 ++++++++++++++++++++++++++++++++++++++------ 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: "".} @@ -163,14 +205,60 @@ proc setenv*(name: cstring, value: cstring, override: cint): cint proc unsetenv*(name: cstring): cint {.importc, header: "".} -proc getUser*: (int, string) = - let uid = getuid() +proc getgrouplist*(user: cstring, group: Gid, groups: ptr cint, ngroups: var cint): cint + {.importc, header: "".} + +proc setgroups*(size: csize, groups: ptr cint): cint + {.importc, header: "".} + +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) -- cgit v1.2.3-70-g09d2