[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#988071: unblock: rust-pleaser/0.4.1



Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock
X-Debbugs-Cc: ed-debian@s5h.net

Please unblock package rust-pleaser

[ Reason ]
Unblock is needed to migrate rust-pleaser 0.4.1 to testing. The SUSE
Security Team (primarily Matthias Gerstner) located CVE bugs during a
review.

This has been discussed with team@security.debian.org, their opinion was
to work towards early publish with the advantage that it could be fixed
before the upcoming stable.

[ Impact ]
Without migration the bugs (CVE-2021-31153, CVE-2021-31154 and
CVE-2021-31155) will remain. They have been semi-published in order to
update the rust crate.

[ Tests ]
Changes have been tested by myself (upstream) and reviewed by SUSE. New
tests added to upstream test scripts.

[ Risks ]
The changes are minimal, no new features were introduced with the fixes.

[ Checklist ]
  [X] all changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in testing

[ Other info ]
Early unblock would be appreciated so the CVE can be published by
Matthias Gerstner.

unblock rust-pleaser/0.4.1
diff -Nru rust-pleaser-0.3.25/Cargo.lock rust-pleaser-0.4.1/Cargo.lock
--- rust-pleaser-0.3.25/Cargo.lock	2021-03-10 21:45:38.000000000 +0000
+++ rust-pleaser-0.4.1/Cargo.lock	2021-04-25 15:33:06.000000000 +0100
@@ -1,21 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
 [[package]]
-name = "addr2line"
-version = "0.14.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "a55f82cfe485775d02112886f4169bde0c5894d75e79ead7eafe7e40a25e45f7"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
-
-[[package]]
 name = "aho-corasick"
 version = "0.7.15"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -31,20 +16,6 @@
 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
 
 [[package]]
-name = "backtrace"
-version = "0.3.56"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
-dependencies = [
- "addr2line",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
-]
-
-[[package]]
 name = "bitflags"
 version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -77,11 +48,11 @@
 
 [[package]]
 name = "error-chain"
-version = "0.11.0"
+version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3"
+checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
 dependencies = [
- "backtrace",
+ "version_check",
 ]
 
 [[package]]
@@ -94,10 +65,15 @@
 ]
 
 [[package]]
-name = "gimli"
-version = "0.23.0"
+name = "getrandom"
+version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
+checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
 
 [[package]]
 name = "libc"
@@ -121,16 +97,6 @@
 checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
 
 [[package]]
-name = "miniz_oxide"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
-dependencies = [
- "adler",
- "autocfg",
-]
-
-[[package]]
 name = "nix"
 version = "0.19.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -162,12 +128,6 @@
 ]
 
 [[package]]
-name = "object"
-version = "0.23.0"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
-
-[[package]]
 name = "once_cell"
 version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -195,13 +155,14 @@
 
 [[package]]
 name = "pleaser"
-version = "0.3.25"
+version = "0.4.1"
 dependencies = [
  "chrono",
  "getopts",
  "libc",
  "nix",
  "pam",
+ "rand",
  "regex",
  "rpassword",
  "syslog",
@@ -209,6 +170,53 @@
 ]
 
 [[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
 name = "regex"
 version = "1.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -237,16 +245,10 @@
 ]
 
 [[package]]
-name = "rustc-demangle"
-version = "0.1.18"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
-
-[[package]]
 name = "syslog"
-version = "4.0.1"
+version = "5.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "a0641142b4081d3d44beffa4eefd7346a228cdf91ed70186db2ca2cef762d327"
+checksum = "9a5d8ef1b679c07976f3ee336a436453760c470f54b5e7237556728b8589515d"
 dependencies = [
  "error-chain",
  "libc",
@@ -270,7 +272,7 @@
 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
 dependencies = [
  "libc",
- "wasi",
+ "wasi 0.10.0+wasi-snapshot-preview1",
  "winapi",
 ]
 
@@ -300,6 +302,18 @@
 ]
 
 [[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
 name = "wasi"
 version = "0.10.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
diff -Nru rust-pleaser-0.3.25/Cargo.toml rust-pleaser-0.4.1/Cargo.toml
--- rust-pleaser-0.3.25/Cargo.toml	2021-03-10 21:45:38.000000000 +0000
+++ rust-pleaser-0.4.1/Cargo.toml	2021-04-25 15:33:06.000000000 +0100
@@ -13,7 +13,7 @@
 [package]
 edition = "2018"
 name = "pleaser"
-version = "0.3.25"
+version = "0.4.1"
 authors = ["ed neville <ed@s5h.net>"]
 description = "please, a polite regex-first sudo alternative"
 homepage = "https://www.usenix.org.uk/content/please.html";
@@ -38,6 +38,9 @@
 [dependencies.pam]
 version = "0.7"
 
+[dependencies.rand]
+version = "0.7"
+
 [dependencies.regex]
 version = "1.3"
 
@@ -45,7 +48,7 @@
 version = "5.0"
 
 [dependencies.syslog]
-version = "4.0"
+version = ">= 4.0"
 
 [dependencies.users]
 version = "0.10"
diff -Nru rust-pleaser-0.3.25/Cargo.toml.orig rust-pleaser-0.4.1/Cargo.toml.orig
--- rust-pleaser-0.3.25/Cargo.toml.orig	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/Cargo.toml.orig	2021-04-25 15:25:19.000000000 +0100
@@ -1,6 +1,6 @@
 [package]
 name = "pleaser"
-version = "0.3.25"
+version = "0.4.1"
 authors = ["ed neville <ed@s5h.net>"]
 edition = "2018"
 description = "please, a polite regex-first sudo alternative"
@@ -20,5 +20,7 @@
 pam = "0.7"
 users = "0.10"
 rpassword = "5.0"
-syslog= "4.0"
+syslog= ">= 4.0"
 libc = "0.2"
+rand = "0.7"
+
diff -Nru rust-pleaser-0.3.25/.cargo_vcs_info.json rust-pleaser-0.4.1/.cargo_vcs_info.json
--- rust-pleaser-0.3.25/.cargo_vcs_info.json	2021-03-10 21:45:38.000000000 +0000
+++ rust-pleaser-0.4.1/.cargo_vcs_info.json	2021-04-25 15:33:06.000000000 +0100
@@ -1,5 +1,5 @@
 {
   "git": {
-    "sha1": "b9e56e70908b194f494cdd88c59c20baba2e604d"
+    "sha1": "e92c8bc4e37064699315db73ae965f39be36601d"
   }
 }
diff -Nru rust-pleaser-0.3.25/debian/cargo-checksum.json rust-pleaser-0.4.1/debian/cargo-checksum.json
--- rust-pleaser-0.3.25/debian/cargo-checksum.json	2021-03-12 08:30:07.000000000 +0000
+++ rust-pleaser-0.4.1/debian/cargo-checksum.json	2021-05-03 20:33:11.000000000 +0100
@@ -1 +1 @@
-{"package":"f78169cedbd57da749da98c09d8a4c1fb9394a02203006984b7e0b72bfc98091","files":{}}
+{"package":"b3e9eac861270bb10d87d06367b683b6fa70ebf69e1180c68579d832a2b54fac","files":{}}
diff -Nru rust-pleaser-0.3.25/debian/changelog rust-pleaser-0.4.1/debian/changelog
--- rust-pleaser-0.3.25/debian/changelog	2021-03-12 08:30:07.000000000 +0000
+++ rust-pleaser-0.4.1/debian/changelog	2021-05-03 20:33:11.000000000 +0100
@@ -1,9 +1,9 @@
-rust-pleaser (0.3.25-1) unstable; urgency=medium
+rust-pleaser (0.4.1-1) unstable; urgency=medium
 
-  * Package pleaser 0.3.25 from crates.io using debcargo 2.4.3
-  * Upstream fixing issues with manpages
+  * Package pleaser 0.4.1 from crates.io using debcargo 2.4.3
+  * Upstream fixing advisories and 32bit build error
 
- -- Ed Neville <ed-debian@s5h.net>  Fri, 12 Mar 2021 09:30:07 +0100
+ -- Ed Neville <ed-debian@s5h.net>  Mon, 03 May 2021 21:33:11 +0200
 
 rust-pleaser (0.3.22-1) unstable; urgency=medium
 
diff -Nru rust-pleaser-0.3.25/debian/control rust-pleaser-0.4.1/debian/control
--- rust-pleaser-0.3.25/debian/control	2021-03-12 08:30:07.000000000 +0000
+++ rust-pleaser-0.4.1/debian/control	2021-05-03 20:33:11.000000000 +0100
@@ -11,9 +11,10 @@
  librust-libc-0.2+default-dev,
  librust-nix+default-dev (>= 0.16-~~),
  librust-pam-0.7+default-dev,
+ librust-rand-0.7+default-dev,
  librust-regex-1+default-dev (>= 1.3-~~),
  librust-rpassword-5+default-dev,
- librust-syslog-4+default-dev,
+ librust-syslog+default-dev (>= 4.0-~~),
  librust-users-0.10+default-dev
 Maintainer: Debian Rust Maintainers <pkg-rust-maintainers@alioth-lists.debian.net>
 Uploaders:
@@ -34,18 +35,19 @@
  librust-libc-0.2+default-dev,
  librust-nix+default-dev (>= 0.16-~~),
  librust-pam-0.7+default-dev,
+ librust-rand-0.7+default-dev,
  librust-regex-1+default-dev (>= 1.3-~~),
  librust-rpassword-5+default-dev,
- librust-syslog-4+default-dev,
+ librust-syslog+default-dev (>= 4.0-~~),
  librust-users-0.10+default-dev
 Provides:
  librust-pleaser+default-dev (= ${binary:Version}),
  librust-pleaser-0-dev (= ${binary:Version}),
  librust-pleaser-0+default-dev (= ${binary:Version}),
- librust-pleaser-0.3-dev (= ${binary:Version}),
- librust-pleaser-0.3+default-dev (= ${binary:Version}),
- librust-pleaser-0.3.25-dev (= ${binary:Version}),
- librust-pleaser-0.3.25+default-dev (= ${binary:Version})
+ librust-pleaser-0.4-dev (= ${binary:Version}),
+ librust-pleaser-0.4+default-dev (= ${binary:Version}),
+ librust-pleaser-0.4.1-dev (= ${binary:Version}),
+ librust-pleaser-0.4.1+default-dev (= ${binary:Version})
 Description: Please, a polite regex-first sudo alternative - Rust source code
  This package contains the source for the Rust pleaser crate, packaged by
  debcargo for use with cargo and dh-cargo.
diff -Nru rust-pleaser-0.3.25/debian/tests/control rust-pleaser-0.4.1/debian/tests/control
--- rust-pleaser-0.3.25/debian/tests/control	2021-03-12 08:30:07.000000000 +0000
+++ rust-pleaser-0.4.1/debian/tests/control	2021-05-03 20:33:11.000000000 +0100
@@ -1,14 +1,14 @@
-Test-Command: /usr/share/cargo/bin/cargo-auto-test pleaser 0.3.25 --all-targets --all-features
+Test-Command: /usr/share/cargo/bin/cargo-auto-test pleaser 0.4.1 --all-targets --all-features
 Features: test-name=rust-pleaser:@
 Depends: dh-cargo (>= 18), @
 Restrictions: allow-stderr, skip-not-installable
 
-Test-Command: /usr/share/cargo/bin/cargo-auto-test pleaser 0.3.25 --all-targets 
+Test-Command: /usr/share/cargo/bin/cargo-auto-test pleaser 0.4.1 --all-targets 
 Features: test-name=librust-pleaser-dev:default
 Depends: dh-cargo (>= 18), @
 Restrictions: allow-stderr, skip-not-installable
 
-Test-Command: /usr/share/cargo/bin/cargo-auto-test pleaser 0.3.25 --all-targets --no-default-features
+Test-Command: /usr/share/cargo/bin/cargo-auto-test pleaser 0.4.1 --all-targets --no-default-features
 Features: test-name=librust-pleaser-dev:
 Depends: dh-cargo (>= 18), @
 Restrictions: allow-stderr, skip-not-installable
diff -Nru rust-pleaser-0.3.25/man/please.1 rust-pleaser-0.4.1/man/please.1
--- rust-pleaser-0.3.25/man/please.1	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/man/please.1	2021-04-25 15:25:19.000000000 +0100
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.2.1
 .\"
-.TH "please" "1" "10 March 2021" "please 0.3.25" "User Manual"
+.TH "please" "1" "25 April 2021" "please 0.4.1" "User Manual"
 .hy
 .SH NAME
 .PP
diff -Nru rust-pleaser-0.3.25/man/please.ini.5 rust-pleaser-0.4.1/man/please.ini.5
--- rust-pleaser-0.3.25/man/please.ini.5	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/man/please.ini.5	2021-04-25 15:25:19.000000000 +0100
@@ -1,6 +1,6 @@
 .\" Automatically generated by Pandoc 2.2.1
 .\"
-.TH "please.ini" "5" "10 March 2021" "please 0.3.25" "User Manual"
+.TH "please.ini" "5" "25 April 2021" "please 0.4.1" "User Manual"
 .hy
 .SH NAME
 .PP
@@ -144,8 +144,8 @@
 .RE
 .TP
 .B \f[B]exitcmd=[program]\f[]
-(\f[B]type=edit\f[]) run program after editor exits as the root user, if
-exit is zero, continue with file replacement.
+(\f[B]type=edit\f[]) run program after editor exits as the target user,
+if exit is zero, continue with file replacement.
 \f[B]%{NEW}\f[] and \f[B]%{OLD}\f[] placeholders expand to new and old
 edit files
 .RS
@@ -295,7 +295,8 @@
 .SH EXITCMD
 .PP
 When the user completes their edit, and the editor exits cleanly, if
-\f[B]exitcmd\f[] is included then this program will run as root.
+\f[B]exitcmd\f[] is included then this program will run as the target
+user.
 If the program also exits cleanly then the temporary edit will be copied
 to the destination.
 .PP
@@ -420,7 +421,8 @@
 directories.
 The directory is specified with the \f[B]\-d\f[] argument to
 \f[B]please\f[].
-For example, a program may output to the current working directory.
+For example, a program may output to the current working directory,
+which may only be desirable in certain locations.
 .IP
 .nf
 \f[C]
diff -Nru rust-pleaser-0.3.25/please.ini.md rust-pleaser-0.4.1/please.ini.md
--- rust-pleaser-0.3.25/please.ini.md	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/please.ini.md	2021-04-25 15:25:19.000000000 +0100
@@ -2,9 +2,9 @@
 title: please.ini
 section: 5
 header: User Manual
-footer: please 0.3.25
+footer: please 0.4.1
 author: Ed Neville (ed-please@s5h.net)
-date: 10 March 2021
+date: 25 April 2021
 ---
 
 # NAME
@@ -89,7 +89,7 @@
 : (**type=edit**) set the file mode bits on replacement file to octal mode, defaults to 0600
 
 **exitcmd=[program]**
-: (**type=edit**) run program after editor exits as the root user, if exit is zero, continue with file replacement. **%{NEW}** and **%{OLD}** placeholders expand to new and old edit files
+: (**type=edit**) run program after editor exits as the target user, if exit is zero, continue with file replacement. **%{NEW}** and **%{OLD}** placeholders expand to new and old edit files
 
 # EXAMPLES
 
@@ -197,7 +197,7 @@
 
 # EXITCMD
 
-When the user completes their edit, and the editor exits cleanly, if **exitcmd** is included then this program will run as root. If the program also exits cleanly then the temporary edit will be copied to the destination.
+When the user completes their edit, and the editor exits cleanly, if **exitcmd** is included then this program will run as the target user. If the program also exits cleanly then the temporary edit will be copied to the destination.
 
 **%{OLD}** and **%{NEW}** will expand to the old (existing source) file and edit candidate, respectively. To verify a file edit, **ben**'s entry to check **/etc/hosts** after clean exit could look like this:
 
@@ -279,7 +279,7 @@
 
 # DIR
 
-In some situations you may only want a command to run within a set of directories. The directory is specified with the **-d** argument to **please**. For example, a program may output to the current working directory.
+In some situations you may only want a command to run within a set of directories. The directory is specified with the **-d** argument to **please**. For example, a program may output to the current working directory, which may only be desirable in certain locations.
 
 ```
 [eng_build_aliases]
diff -Nru rust-pleaser-0.3.25/please.md rust-pleaser-0.4.1/please.md
--- rust-pleaser-0.3.25/please.md	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/please.md	2021-04-25 15:25:19.000000000 +0100
@@ -2,9 +2,9 @@
 title: please
 section: 1
 header: User Manual
-footer: please 0.3.25
+footer: please 0.4.1
 author: Ed Neville (ed-please@s5h.net)
-date: 10 March 2021
+date: 25 April 2021
 ---
 
 # NAME
diff -Nru rust-pleaser-0.3.25/README.md rust-pleaser-0.4.1/README.md
--- rust-pleaser-0.3.25/README.md	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/README.md	2021-04-25 15:25:19.000000000 +0100
@@ -1,4 +1,4 @@
-# Please, a sudo clone with regex support
+# Please, a sudo alternative with regex support
 
 Great! This is what I need.
 
@@ -6,6 +6,10 @@
 
 The idea is to help you admin your box without giving users full root, just because that is easier. Most admins have experience of regex in one form or another, so lets configure access that way.
 
+I saw regex but don't like regex. No problem, you can still use please without regex, just treat each field/property as plain text, and escape control characters `?(){}[]+` etc.
+
+Please is written with memory safe rust. Traditional C memory unsafety is avoided, logic problems may still exist. Logic problems would exist in both systems, but I choose the smaller problem set.
+
 # How do I install it
 
 It might already be in the repo that you're using:
@@ -25,7 +29,7 @@
 
 ```
 pacman -Syu git openssh fakeroot devtools make gcc rust
-git clone ssh://aur@aur.archlinux.org/pleaser.git
+git clone https://aur@aur.archlinux.org/pleaser.git
 cd pleaser && makepkg -isr
 ```
 
@@ -281,10 +285,3 @@
 
 This project is named "please". In some places that project name was used by others for other things. Some packages will be named pleaser, some will be named please. The only important thing is if you wish someone to make you a sandwich, just say "please" first.
 
-# Todo
-
-```
-[ ] docker image for testing
-[ ] packages
-```
-
diff -Nru rust-pleaser-0.3.25/src/bin/pleaseedit.rs rust-pleaser-0.4.1/src/bin/pleaseedit.rs
--- rust-pleaser-0.3.25/src/bin/pleaseedit.rs	2021-03-10 21:38:09.000000000 +0000
+++ rust-pleaser-0.4.1/src/bin/pleaseedit.rs	2021-04-25 15:25:19.000000000 +0100
@@ -18,8 +18,11 @@
 
 use pleaser::util::*;
 
-use std::fs::*;
+use std::fs::OpenOptions;
+use std::os::unix::fs::OpenOptionsExt;
+
 use std::io::{self, Write};
+use std::os::unix::io::AsRawFd;
 use std::os::unix::process::CommandExt;
 use std::path::Path;
 use std::process::{Command, Stdio};
@@ -28,18 +31,19 @@
 
 use getopts::Options;
 
-use nix::sys::stat::fchmodat;
+use nix::sys::stat::fchmod;
 use nix::sys::wait::WaitStatus::Exited;
-use nix::unistd::{chown, fork, gethostname, ForkResult};
+use nix::unistd::{fchown, fork, gethostname, ForkResult};
 
 use users::*;
 
 /// return a path string to work on in /tmp
 fn tmp_edit_file_name(source_file: &Path, service: &str, original_user: &str) -> String {
     format!(
-        "/tmp/{}.{}.{}",
+        "/tmp/{}.{}.{}.{}",
         service,
         original_user,
+        prng_alpha_num_string(8),
         source_file.to_str().unwrap().replace('/', "_"),
     )
 }
@@ -47,8 +51,9 @@
 /// return a path string that exitcmd can use adjacent in the source location
 fn source_tmp_file_name(source_file: &Path, service: &str, original_user: &str) -> String {
     format!(
-        "{}.{}.{}",
+        "{}.{}.{}.{}",
         source_file.to_str().unwrap(),
+        prng_alpha_num_string(8),
         service,
         original_user,
     )
@@ -58,11 +63,15 @@
 fn setup_temp_edit_file(
     service: &str,
     source_file: &Path,
-    original_uid: u32,
-    original_gid: u32,
-    original_user: &str,
+    ro: &RunOptions,
+    target_uid: nix::unistd::Uid,
+    target_gid: nix::unistd::Gid,
 ) -> String {
-    let tmp_edit_file = tmp_edit_file_name(&source_file, &service, &original_user);
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+
+    let tmp_edit_file = tmp_edit_file_name(&source_file, &service, &ro.name);
     let tmp_edit_file_path = Path::new(&tmp_edit_file);
 
     if tmp_edit_file_path.exists() && std::fs::remove_file(tmp_edit_file_path).is_err() {
@@ -70,36 +79,52 @@
         std::process::exit(1);
     }
 
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+    if !set_eprivs(target_uid, target_gid) {
+        std::process::exit(1);
+    }
+    let mut file_data: Result<String, std::io::Error> = Ok("".to_string());
+
     if source_file.exists() {
-        if std::fs::copy(source_file, tmp_edit_file_path).is_err() {
+        file_data = std::fs::read_to_string(source_file);
+        if file_data.is_err() {
             println!(
-                "Could not copy {} to {}",
+                "Could not read source file {}",
                 source_file.to_str().unwrap(),
-                tmp_edit_file_path.to_str().unwrap()
             );
             std::process::exit(1);
         }
-    } else if File::create(tmp_edit_file_path).is_err() {
+    }
+
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+
+    let mut options = OpenOptions::new();
+    options.write(true).create_new(true);
+    options.custom_flags(libc::O_NOFOLLOW);
+    let file = options.open(tmp_edit_file_path);
+
+    if file.is_err() {
         println!("Could not create {}", tmp_edit_file_path.to_str().unwrap());
         std::process::exit(1);
     }
 
-    if chown(
-        tmp_edit_file_path,
-        Some(nix::unistd::Uid::from_raw(original_uid)),
-        Some(nix::unistd::Gid::from_raw(original_gid)),
+    if fchown(
+        file.as_ref().unwrap().as_raw_fd(),
+        Some(ro.original_uid),
+        Some(ro.original_gid),
     )
     .is_err()
     {
         println!("Could not chown {}", tmp_edit_file_path.to_str().unwrap());
-        std::process::exit(1);
     }
 
-    if fchmodat(
-        None,
-        tmp_edit_file_path,
+    if fchmod(
+        file.as_ref().unwrap().as_raw_fd(),
         nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR,
-        nix::sys::stat::FchmodatFlags::FollowSymlink,
     )
     .is_err()
     {
@@ -107,6 +132,16 @@
         std::process::exit(1);
     }
 
+    if file_data.is_ok()
+        && file
+            .unwrap()
+            .write(file_data.as_ref().unwrap().as_bytes())
+            .is_err()
+    {
+        println!("Could not write data to {}", &tmp_edit_file);
+        std::process::exit(1);
+    }
+
     tmp_edit_file
 }
 
@@ -170,6 +205,126 @@
     }
 }
 
+fn write_target_tmp_file(
+    dir_parent_tmp: &str,
+    file_data: &Result<String, std::io::Error>,
+    target_uid: nix::unistd::Uid,
+    target_gid: nix::unistd::Gid,
+) -> std::fs::File {
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+    if !set_eprivs(target_uid, target_gid) {
+        std::process::exit(1);
+    }
+
+    let mut options = OpenOptions::new();
+    options.write(true).create_new(true);
+    options.custom_flags(libc::O_NOFOLLOW);
+
+    let file = options.open(dir_parent_tmp);
+    if file.is_err()
+        || file
+            .as_ref()
+            .unwrap()
+            .write(&file_data.as_ref().unwrap().as_bytes())
+            .is_err()
+    {
+        println!("Could not write data to {}", &dir_parent_tmp);
+        std::process::exit(1);
+    }
+    file.unwrap()
+}
+
+fn remove_tmp_edit(ro: &RunOptions, edit_file: &str) {
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+    if std::fs::remove_file(edit_file).is_err() {
+        println!("Could not remove {}", edit_file);
+        std::process::exit(1);
+    }
+}
+
+/// rename the edit in the source directory, return false if exitcmd failed
+fn rename_to_source(
+    dir_parent_tmp: &str,
+    source_file: &Path,
+    entry: &Result<EnvOptions, ()>,
+    lookup_name: &User,
+    dir_parent_tmp_file: &std::fs::File,
+    target_uid: nix::unistd::Uid,
+    target_gid: nix::unistd::Gid,
+) -> bool {
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+
+    if !set_eprivs(target_uid, target_gid) {
+        std::process::exit(1);
+    }
+
+    fchown(
+        dir_parent_tmp_file.as_raw_fd(),
+        Some(nix::unistd::Uid::from_raw(lookup_name.uid())),
+        Some(nix::unistd::Gid::from_raw(lookup_name.primary_group_id())),
+    )
+    .unwrap();
+
+    fchmod(
+        dir_parent_tmp_file.as_raw_fd(),
+        if entry.as_ref().unwrap().edit_mode.is_some() {
+            nix::sys::stat::Mode::from_bits(entry.as_ref().unwrap().edit_mode.unwrap() as u32)
+                .unwrap()
+        } else {
+            nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR
+        },
+    )
+    .unwrap();
+
+    if entry.as_ref().unwrap().exitcmd.is_some() {
+        let mut cmd = build_exitcmd(
+            &entry.clone().unwrap(),
+            &source_file.to_str().unwrap(),
+            &dir_parent_tmp,
+        );
+        let out = cmd.output().expect("could not execute");
+        io::stdout().write_all(&out.clone().stdout).unwrap();
+        io::stderr().write_all(&out.clone().stderr).unwrap();
+        if !out.status.success() {
+            println!("Aborting as exitcmd was non-zero, removing tmp file");
+            if nix::unistd::unlink(dir_parent_tmp).is_err() {
+                println!("Could not remove tmp file either, giving up");
+            }
+            std::process::exit(1);
+        }
+    }
+
+    if std::fs::rename(&dir_parent_tmp, source_file).is_err() {
+        println!(
+            "Could not rename {} to {}",
+            &dir_parent_tmp,
+            source_file.to_str().unwrap()
+        );
+        std::process::exit(1);
+    }
+    true
+}
+
+/// read edit file into memory or exit 1
+fn edit_file_to_memory(source_file: &Path, edit_file: &str) -> Result<String, std::io::Error> {
+    let file_data = std::fs::read_to_string(edit_file);
+    if file_data.is_err() {
+        println!(
+            "Could not read {}: {}",
+            source_file.to_str().unwrap(),
+            file_data.err().unwrap()
+        );
+        std::process::exit(1);
+    }
+    file_data
+}
+
 /// entry point
 fn main() {
     let args: Vec<String> = std::env::args().collect();
@@ -184,12 +339,16 @@
     ro.syslog = true;
     let mut vec_eo: Vec<EnvOptions> = vec![];
 
-    if !set_privs(
-        "root",
-        nix::unistd::Uid::from_raw(0),
-        nix::unistd::Gid::from_raw(0),
-    ) {
-        println!("I cannot set privs. Exiting as not installed correctly.");
+    let root_uid = nix::unistd::Uid::from_raw(0);
+    let root_gid = nix::unistd::Gid::from_raw(0);
+
+    clean_environment(&mut ro);
+
+    if !set_privs("root", root_uid, root_gid) {
+        std::process::exit(1);
+    }
+
+    if !drop_privs(&ro) {
         std::process::exit(1);
     }
 
@@ -200,12 +359,19 @@
     ro.command = ro.new_args.join(" ");
 
     ro.groups = group_hash(original_user.groups().unwrap());
-
-    if read_ini_config_file("/etc/please.ini", &mut vec_eo, &ro.name, true) {
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+    let mut bytes = 0;
+    if read_ini_config_file("/etc/please.ini", &mut vec_eo, &ro.name, true, &mut bytes) {
         println!("Exiting due to error, cannot fully process /etc/please.ini");
         std::process::exit(1);
     }
 
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+
     let mut buf = [0u8; 64];
     ro.hostname = gethostname(&mut buf)
         .expect("Failed getting hostname")
@@ -245,12 +411,7 @@
         }
     }
 
-    if std::fs::read_link(&ro.command).is_ok() {
-        println!("You may not edit \"{}\" as it links elsewhere", &ro.command);
-        std::process::exit(1);
-    }
-
-    if !challenge_password(&ro.name, entry.clone().unwrap(), &service, ro.prompt) {
+    if !challenge_password(&ro, entry.clone().unwrap(), &service) {
         log_action(&service, "deny", &ro, &original_command.join(" "));
         std::process::exit(1);
     }
@@ -262,21 +423,27 @@
     }
     let lookup_name = lookup_name.unwrap();
 
+    let target_uid = nix::unistd::Uid::from_raw(lookup_name.uid());
+    let target_gid = nix::unistd::Gid::from_raw(lookup_name.primary_group_id());
+
     let source_file = Path::new(&ro.new_args[0]);
 
-    let edit_file =
-        &setup_temp_edit_file(&service, source_file, original_uid, original_gid, &ro.name);
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+    let edit_file = &setup_temp_edit_file(&service, source_file, &ro, target_uid, target_gid);
+
+    set_environment(&ro, &original_user, original_uid, &lookup_name);
 
-    std::env::set_var("PLEASE_USER", original_user.name());
-    std::env::set_var("PLEASE_UID", original_uid.to_string());
-    std::env::set_var("PLEASE_GID", original_uid.to_string());
     std::env::set_var("PLEASE_EDIT_FILE", edit_file.to_string());
     std::env::set_var("PLEASE_SOURCE_FILE", source_file.to_str().unwrap());
-    std::env::set_var("SUDO_USER", original_user.name());
-    std::env::set_var("SUDO_UID", original_uid.to_string());
-    std::env::set_var("SUDO_GID", original_user.primary_group_id().to_string());
+
+    if !esc_privs() {
+        std::process::exit(1);
+    }
 
     let mut good_edit = false;
+
     match unsafe { fork() } {
         Ok(ForkResult::Parent { .. }) => match nix::sys::wait::wait() {
             Ok(Exited(_pid, ret)) if ret == 0 => {
@@ -298,6 +465,8 @@
                 std::process::exit(1);
             }
 
+            nix::sys::stat::umask(ro.old_umask.unwrap());
+
             let args: Vec<&str> = editor.as_str().split(' ').collect();
             if args.len() == 1 {
                 Command::new(editor.as_str()).arg(&edit_file).exec();
@@ -313,73 +482,38 @@
         Err(_) => println!("Fork failed"),
     }
 
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+
     if !good_edit {
         println!("Exiting as editor or child did not close cleanly.");
         std::process::exit(1);
     }
 
+    // drop privs to original user and read into memory
     log_action(&service, "permit", &ro, &original_command.join(" "));
-
     let dir_parent_tmp =
         source_tmp_file_name(&source_file, format!("{}.copy", service).as_str(), &ro.name);
-    if let Err(x) = std::fs::copy(edit_file, dir_parent_tmp.as_str()) {
-        println!(
-            "Could not copy {} to {}: {}",
-            edit_file,
-            dir_parent_tmp.as_str(),
-            x
-        );
-        std::process::exit(1);
-    }
-
-    if std::fs::remove_file(edit_file).is_err() {
-        println!("Could not remove {}", edit_file);
-        std::process::exit(1);
-    }
+    let file_data = edit_file_to_memory(&source_file, &edit_file);
 
-    chown(
-        dir_parent_tmp.as_str(),
-        Some(nix::unistd::Uid::from_raw(lookup_name.uid())),
-        Some(nix::unistd::Gid::from_raw(lookup_name.primary_group_id())),
-    )
-    .unwrap();
-
-    fchmodat(
-        None,
-        dir_parent_tmp.as_str(),
-        if entry.as_ref().unwrap().edit_mode.is_some() {
-            nix::sys::stat::Mode::from_bits(entry.as_ref().unwrap().edit_mode.unwrap() as u32)
-                .unwrap()
-        } else {
-            nix::sys::stat::Mode::S_IRUSR | nix::sys::stat::Mode::S_IWUSR
-        },
-        nix::sys::stat::FchmodatFlags::FollowSymlink,
-    )
-    .unwrap();
-
-    if entry.as_ref().unwrap().exitcmd.is_some() {
-        let mut cmd = build_exitcmd(
-            &entry.unwrap(),
-            &source_file.to_str().unwrap(),
-            &dir_parent_tmp.as_str(),
-        );
-        let out = cmd.output().expect("could not execute");
-        io::stdout().write_all(&out.clone().stdout).unwrap();
-        io::stderr().write_all(&out.clone().stderr).unwrap();
-        if !out.status.success() {
-            println!("Aborting as exitcmd was non-zero");
-            std::process::exit(out.status.code().unwrap());
-        }
-    }
-
-    if std::fs::rename(&dir_parent_tmp.as_str(), source_file).is_err() {
-        println!(
-            "Could not rename {} to {}",
-            &dir_parent_tmp.as_str(),
-            source_file.to_str().unwrap()
-        );
-        std::process::exit(1);
-    }
+    // become the target user and create file
+    let dir_parent_tmp_file =
+        write_target_tmp_file(&dir_parent_tmp, &file_data, target_uid, target_gid);
+
+    // original user, remove tmp edit file
+    remove_tmp_edit(&ro, edit_file);
+
+    // rename file to source if exitcmd is clean
+    rename_to_source(
+        &dir_parent_tmp,
+        &source_file,
+        &entry,
+        &lookup_name,
+        &dir_parent_tmp_file,
+        target_uid,
+        target_gid,
+    );
 }
 
 #[cfg(test)]
diff -Nru rust-pleaser-0.3.25/src/bin/please.rs rust-pleaser-0.4.1/src/bin/please.rs
--- rust-pleaser-0.3.25/src/bin/please.rs	2021-03-10 21:38:09.000000000 +0000
+++ rust-pleaser-0.4.1/src/bin/please.rs	2021-04-25 15:25:19.000000000 +0100
@@ -25,7 +25,6 @@
 
 use nix::unistd::gethostname;
 
-use users::os::unix::UserExt;
 use users::*;
 
 /// walk through user ACL
@@ -64,7 +63,7 @@
         }
 
         // check if a password is required
-        if !challenge_password(&ro.name, can_do, &service, ro.prompt) {
+        if !challenge_password(&ro, can_do, &service) {
             log_action(&service, "deny", &ro, &ro.original_command.join(" "));
             std::process::exit(1);
         }
@@ -95,57 +94,20 @@
 }
 
 /// navigate to directory or exit 1
-fn do_dir_changes(ro: &RunOptions) {
-    if ro.directory != "" {
-        if let Err(x) = std::env::set_current_dir(&ro.directory) {
-            println!("Cannot cd into {}: {}", &ro.directory, x);
+fn do_dir_changes(ro: &RunOptions, service: &str) {
+    if ro.directory.is_some() {
+        if let Err(x) = std::env::set_current_dir(&ro.directory.as_ref().unwrap()) {
+            println!(
+                "[{}] cannot cd into {}: {}",
+                &service,
+                &ro.directory.as_ref().unwrap(),
+                x
+            );
             std::process::exit(1);
         }
     }
 }
 
-/// clean environment aside from ~half a dozen vars and set some environments for helper scripts
-fn do_environment(
-    ro: &mut RunOptions,
-    original_user: &User,
-    original_uid: u32,
-    lookup_name: &User,
-) {
-    for (key, _) in std::env::vars() {
-        if key == "LANGUAGE"
-            || key == "XAUTHORITY"
-            || key == "LANG"
-            || key == "LS_COLORS"
-            || key == "TERM"
-            || key == "DISPLAY"
-            || key == "LOGNAME"
-        {
-            continue;
-        }
-        std::env::remove_var(key);
-    }
-
-    std::env::set_var("PLEASE_USER", original_user.name());
-    std::env::set_var("PLEASE_UID", original_uid.to_string());
-    std::env::set_var("PLEASE_GID", original_user.primary_group_id().to_string());
-    std::env::set_var("PLEASE_COMMAND", &ro.command);
-
-    std::env::set_var("SUDO_USER", original_user.name());
-    std::env::set_var("SUDO_UID", original_uid.to_string());
-    std::env::set_var("SUDO_GID", original_user.primary_group_id().to_string());
-    std::env::set_var("SUDO_COMMAND", &ro.command);
-
-    std::env::set_var(
-        "PATH",
-        "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_string(),
-    );
-    std::env::set_var("HOME", lookup_name.home_dir().as_os_str());
-    std::env::set_var("MAIL", format!("/var/mail/{}", ro.target));
-    std::env::set_var("SHELL", lookup_name.shell());
-    std::env::set_var("USER", &ro.target);
-    std::env::set_var("LOGNAME", &ro.target);
-}
-
 /// setup getopts for argument parsing and help output
 fn general_options(
     mut ro: &mut RunOptions,
@@ -176,16 +138,18 @@
     };
 
     if matches.opt_present("c") {
+        let mut bytes = 0;
         std::process::exit(read_ini_config_file(
             &matches.opt_str("c").unwrap(),
             &mut vec_eo,
             &ro.name,
             true,
+            &mut bytes,
         ) as i32);
     }
 
     if matches.opt_present("d") {
-        ro.directory = matches.opt_str("d").unwrap();
+        ro.directory = Some(matches.opt_str("d").unwrap());
     }
     if matches.opt_present("l") {
         ro.acl_type = ACLTYPE::LIST;
@@ -214,24 +178,36 @@
     ro.syslog = true;
     let mut vec_eo: Vec<EnvOptions> = vec![];
 
-    if !set_privs(
-        "root",
-        nix::unistd::Uid::from_raw(0),
-        nix::unistd::Gid::from_raw(0),
-    ) {
-        println!("I cannot set privs. Exiting as not installed correctly.");
+    let root_uid = nix::unistd::Uid::from_raw(0);
+    let root_gid = nix::unistd::Gid::from_raw(0);
+
+    clean_environment(&mut ro);
+
+    if !set_privs("root", root_uid, root_gid) {
+        std::process::exit(1);
+    }
+
+    if !drop_privs(&ro) {
         std::process::exit(1);
     }
 
     general_options(&mut ro, args, &service, &mut vec_eo);
 
     ro.groups = group_hash(original_user.groups().unwrap());
+    if !esc_privs() {
+        std::process::exit(1);
+    }
 
-    if read_ini_config_file("/etc/please.ini", &mut vec_eo, &ro.name, true) {
+    let mut bytes = 0;
+    if read_ini_config_file("/etc/please.ini", &mut vec_eo, &ro.name, true, &mut bytes) {
         println!("Exiting due to error, cannot fully process /etc/please.ini");
         std::process::exit(1);
     }
 
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+
     let mut buf = [0u8; 64];
     ro.hostname = gethostname(&mut buf)
         .expect("Failed getting hostname")
@@ -251,6 +227,8 @@
         ro.target = "root".to_string();
     }
 
+    ro.command = replace_new_args(ro.new_args.clone());
+
     match search_path(&ro.new_args[0]) {
         None => {
             println!("[{}]: command not found", service);
@@ -258,29 +236,23 @@
         }
         Some(x) => {
             ro.new_args[0] = x;
+            ro.command = replace_new_args(ro.new_args.clone());
         }
     }
 
-    ro.command = replace_new_args(ro.new_args.clone());
     let entry = can(&vec_eo, &ro);
 
     match &entry {
         Err(_) => {
             log_action(&service, "deny", &ro, &original_command.join(" "));
-            println!(
-                "You may not execute \"{}\" on {} as {}",
-                &ro.command, &ro.hostname, &ro.target
-            );
+            print_may_not(&ro);
             std::process::exit(1);
         }
         Ok(x) => {
             ro.syslog = x.syslog;
             if !x.permit {
                 log_action(&service, "deny", &ro, &original_command.join(" "));
-                println!(
-                    "You may not execute \"{}\" on {} as {}",
-                    &ro.command, &ro.hostname, &ro.target
-                );
+                print_may_not(&ro);
                 std::process::exit(1);
             }
             // check if a reason was given
@@ -295,14 +267,15 @@
         }
     }
 
-    if !challenge_password(&ro.name, entry.unwrap(), &service, ro.prompt) {
+    if !challenge_password(&ro, entry.unwrap(), &service) {
         log_action(&service, "deny", &ro, &original_command.join(" "));
         std::process::exit(1);
     }
 
-    do_dir_changes(&ro);
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
 
-    log_action(&service, "permit", &ro, &original_command.join(" "));
     let lookup_name = get_user_by_name(&ro.target);
     if lookup_name.is_none() {
         println!("Could not lookup {}", &ro.target);
@@ -312,21 +285,39 @@
     let target_uid = nix::unistd::Uid::from_raw(lookup_name.uid());
     let target_gid = nix::unistd::Gid::from_raw(lookup_name.primary_group_id());
 
-    do_environment(&mut ro, &original_user, original_uid, &lookup_name);
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+    if !set_eprivs(target_uid, target_gid) {
+        std::process::exit(1);
+    }
+
+    do_dir_changes(&ro, &service);
+
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+
+    log_action(&service, "permit", &ro, &original_command.join(" "));
+
+    set_environment(&ro, &original_user, original_uid, &lookup_name);
+
+    if !esc_privs() {
+        std::process::exit(1);
+    }
 
     if !set_privs(&ro.target.to_string(), target_uid, target_gid) {
-        println!("I cannot set privs. Exiting as not installed correctly.");
         std::process::exit(1);
     }
 
+    nix::sys::stat::umask(ro.old_umask.unwrap());
+
     if ro.new_args.len() > 1 {
         Command::new(&ro.new_args[0])
             .args(ro.new_args.clone().split_off(1))
             .exec();
-        Command::new(&"/bin/sh").args(ro.new_args).exec();
     } else {
         Command::new(&ro.new_args[0]).exec();
-        Command::new("/bin/sh").args(ro.new_args).exec();
     }
 }
 
diff -Nru rust-pleaser-0.3.25/src/util.rs rust-pleaser-0.4.1/src/util.rs
--- rust-pleaser-0.3.25/src/util.rs	2021-03-10 21:44:26.000000000 +0000
+++ rust-pleaser-0.4.1/src/util.rs	2021-04-25 15:25:19.000000000 +0100
@@ -21,7 +21,6 @@
 use std::ffi::{CStr, CString};
 use std::path::Path;
 use std::process;
-use std::time::SystemTime;
 use syslog::{Facility, Formatter3164};
 
 use chrono::{NaiveDate, NaiveDateTime, Utc};
@@ -29,10 +28,17 @@
 use std::fs;
 use std::fs::File;
 use std::io::prelude::*;
+use std::io::BufReader;
+use std::time::SystemTime;
+use users::os::unix::UserExt;
 use users::*;
 
 use getopts::{Matches, Options};
-use nix::unistd::{initgroups, setgid, setuid};
+use nix::unistd::{initgroups, setegid, seteuid, setgid, setuid};
+use pam::Authenticator;
+
+use rand::distributions::Alphanumeric;
+use rand::{thread_rng, Rng};
 
 #[derive(Clone)]
 pub struct EnvOptions {
@@ -102,11 +108,13 @@
 #[derive(Clone)]
 pub struct RunOptions {
     pub name: String,
+    pub original_uid: nix::unistd::Uid,
+    pub original_gid: nix::unistd::Gid,
     pub target: String,
     pub command: String,
     pub original_command: Vec<String>,
     pub hostname: String,
-    pub directory: String,
+    pub directory: Option<String>,
     pub groups: HashMap<String, u32>,
     pub date: NaiveDateTime,
     pub acl_type: ACLTYPE,
@@ -116,19 +124,22 @@
     pub purge_token: bool,
     pub warm_token: bool,
     pub new_args: Vec<String>,
+    pub old_umask: Option<nix::sys::stat::Mode>,
 }
 
 impl RunOptions {
     pub fn new() -> RunOptions {
         RunOptions {
             name: "root".to_string(),
+            original_uid: nix::unistd::Uid::from_raw(get_current_uid()),
+            original_gid: nix::unistd::Gid::from_raw(get_current_gid()),
             target: "".to_string(),
             command: "".to_string(),
             original_command: vec![],
             hostname: "localhost".to_string(),
             date: Utc::now().naive_utc(),
             groups: HashMap::new(),
-            directory: ".".to_string(),
+            directory: None,
             acl_type: ACLTYPE::RUN,
             reason: None,
             syslog: true,
@@ -136,6 +147,7 @@
             purge_token: false,
             warm_token: false,
             new_args: vec![],
+            old_umask: None,
         }
     }
 }
@@ -146,6 +158,36 @@
     }
 }
 
+struct PamConvo {
+    login: String,
+    passwd: Option<String>,
+    service: String,
+}
+
+impl pam::Converse for PamConvo {
+    fn prompt_echo(&mut self, _msg: &CStr) -> Result<CString, ()> {
+        CString::new(self.login.clone()).map_err(|_| ())
+    }
+    fn prompt_blind(&mut self, _msg: &CStr) -> Result<CString, ()> {
+        self.passwd = Some(
+            rpassword::read_password_from_tty(Some(&format!(
+                "[{}] password for {}: ",
+                self.service, self.login
+            )))
+            .unwrap(),
+        );
+
+        CString::new(self.passwd.clone().unwrap()).map_err(|_| ())
+    }
+    fn info(&mut self, _msg: &CStr) {}
+    fn error(&mut self, msg: &CStr) {
+        println!("[{} pam error] {}", self.service, msg.to_string_lossy());
+    }
+    fn username(&self) -> &str {
+        &self.login
+    }
+}
+
 #[derive(PartialEq, Eq, Clone, Debug)]
 pub enum ACLTYPE {
     RUN,
@@ -163,6 +205,20 @@
     }
 }
 
+pub fn print_may_not(ro: &RunOptions) {
+    println!(
+        "You may not {} \"{}\" on {} as {}",
+        if ro.acl_type == ACLTYPE::RUN {
+            "execute".to_string()
+        } else {
+            ro.acl_type.to_string()
+        },
+        &ro.command,
+        &ro.hostname,
+        &ro.target
+    );
+}
+
 /// build a regex and replace %{USER} with the user str, prefix with ^ and suffix with $
 pub fn regex_build(
     v: &str,
@@ -218,6 +274,7 @@
         "pin, for work with pkgsrc",
         "Stanley Dziegiel, for ini suggestions",
         "My wife and child, for putting up with me",
+        "The SUSE Security Team, especially Matthias Gerstner",
     ];
 
     print_version(&service);
@@ -291,13 +348,19 @@
     }
 
     if ro.purge_token {
+        if !esc_privs() {
+            std::process::exit(1);
+        }
         remove_token(&ro.name);
+        if !drop_privs(&ro) {
+            std::process::exit(1);
+        }
         std::process::exit(0);
     }
 
     if ro.warm_token {
         if ro.prompt {
-            challenge_password(&ro.name, EnvOptions::new(), &service, ro.prompt);
+            challenge_password(&ro, EnvOptions::new(), &service);
         }
         std::process::exit(0);
     }
@@ -310,6 +373,7 @@
     user: &str,
     fail_error: bool,
     config_path: &str,
+    bytes: &mut u64,
 ) -> bool {
     let parse_datetime_from_str = NaiveDateTime::parse_from_str;
     let parse_date_from_str = NaiveDate::parse_from_str;
@@ -359,7 +423,7 @@
                     println!("Includes should start with /");
                     return true;
                 }
-                if read_ini_config_file(&value, vec_eo, &user, fail_error) {
+                if read_ini_config_file(&value, vec_eo, &user, fail_error, bytes) {
                     println!("Could not include file");
                     return true;
                 }
@@ -385,7 +449,7 @@
                             if !can_dir_include(&incf) {
                                 continue;
                             }
-                            if read_ini_config_file(&incf, vec_eo, &user, fail_error) {
+                            if read_ini_config_file(&incf, vec_eo, &user, fail_error, bytes) {
                                 println!("Could not include file");
                                 return true;
                             }
@@ -511,28 +575,49 @@
 }
 
 /// read through an ini config file, appending EnvOptions to vec_eo
+/// hardcoded limit of 10M for confs
 pub fn read_ini_config_file(
     config_path: &str,
     vec_eo: &mut Vec<EnvOptions>,
     user: &str,
     fail_error: bool,
+    bytes: &mut u64,
 ) -> bool {
     let path = Path::new(config_path);
     let display = path.display();
 
-    let mut file = match File::open(&path) {
-        Err(_why) => return true,
+    let file = match File::open(&path) {
+        Err(why) => {
+            println!("Could not open {}: {}", display, why);
+            return true;
+        }
         Ok(file) => file,
     };
 
+    let byte_limit = 1024 * 1024 * 10;
+
+    if *bytes >= byte_limit {
+        println!("Exiting as too much config has already been read.");
+        std::process::exit(1);
+    }
+
     let mut s = String::new();
-    match file.read_to_string(&mut s) {
+    let reader = BufReader::new(file).take(byte_limit).read_to_string(&mut s);
+    *bytes += s.len() as u64;
+
+    match reader {
+        Ok(n) => {
+            if n >= byte_limit as usize {
+                println!("Exiting as too much config has already been read.");
+                std::process::exit(1);
+            }
+        }
         Err(why) => {
-            println!("couldn't read {}: {}", display, why);
-            true
+            println!("Could not read {}: {}", display, why);
+            return true;
         }
-        Ok(_) => read_ini(&s, vec_eo, &user, fail_error, config_path),
     }
+    read_ini(&s, vec_eo, &user, fail_error, config_path, bytes)
 }
 
 pub fn read_ini_config_str(
@@ -540,8 +625,9 @@
     vec_eo: &mut Vec<EnvOptions>,
     user: &str,
     fail_error: bool,
+    bytes: &mut u64,
 ) -> bool {
-    read_ini(&config, vec_eo, &user, fail_error, "static")
+    read_ini(&config, vec_eo, &user, fail_error, "static", bytes)
 }
 
 /// may we execute with this hostname
@@ -589,10 +675,18 @@
             }
         };
 
-        if !dir_re.is_match(&ro.directory) {
+        if ro.directory.as_ref().is_none() {
+            return false;
+        }
+
+        if (&ro.directory.as_ref()).is_some() && !dir_re.is_match(&ro.directory.as_ref().unwrap()) {
             // && ro.directory != "." {
             return false;
         }
+        return true;
+    }
+    if ro.directory.is_some() {
+        return false;
     }
     true
 }
@@ -732,16 +826,6 @@
     Ok(opt)
 }
 
-/// are user/password authentic
-pub fn auth_ok(u: &str, p: &str, service: &str) -> bool {
-    let mut auth = pam::Authenticator::with_password(&service).expect("Failed to init PAM client.");
-    auth.get_handler().set_credentials(u, p);
-    if auth.authenticate().is_ok() && auth.open_session().is_ok() {
-        return true;
-    }
-    false
-}
-
 /// find editor for user. return /usr/bin/vi if EDITOR and VISUAL are unset
 pub fn get_editor() -> String {
     let editor = "/usr/bin/vi";
@@ -755,8 +839,34 @@
     editor.to_string()
 }
 
+/// handler.authenticate without the root privs part for linux
+#[cfg(target_os = "linux")]
+pub fn handler_shim<T: pam::Converse>(
+    _ro: &RunOptions,
+    handler: &mut Authenticator<T>,
+) -> Result<(), pam::PamError> {
+    handler.authenticate()
+}
+
+/// handler.authenticate needs esc_privs on netbsd
+#[cfg(not(target_os = "linux"))]
+pub fn handler_shim<T: pam::Converse>(
+    ro: &RunOptions,
+    handler: &mut Authenticator<T>,
+) -> Result<(), pam::PamError> {
+    if !esc_privs() {
+        std::process::exit(1);
+    }
+    let auth = handler.authenticate();
+    if !drop_privs(&ro) {
+        std::process::exit(1);
+    }
+    auth
+}
+
 /// read password of user via rpassword
-pub fn challenge_password(user: &str, entry: EnvOptions, service: &str, prompt: bool) -> bool {
+/// should pam require a password, and it is successful, then we set a token
+pub fn challenge_password(ro: &RunOptions, entry: EnvOptions, service: &str) -> bool {
     if entry.require_pass {
         if tty_name().is_none() {
             println!("Cannot read password without tty");
@@ -764,24 +874,47 @@
         }
 
         let mut retry_counter = 0;
-        if valid_token(&user) {
-            update_token(&user);
+
+        if !esc_privs() {
+            std::process::exit(1);
+        }
+
+        if valid_token(&ro.name) {
+            update_token(&ro.name);
             return true;
         }
 
-        if !prompt {
+        if !drop_privs(&ro) {
+            std::process::exit(1);
+        }
+
+        if !ro.prompt {
             return false;
         }
 
+        let convo = PamConvo {
+            login: ro.name.to_string(),
+            passwd: None,
+            service: service.to_string(),
+        };
+
+        let mut handler = Authenticator::with_handler(service, convo).expect("Cannot init PAM");
+
         loop {
-            let pass = rpassword::read_password_from_tty(Some(&format!(
-                "[{}] password for {}: ",
-                &service, &user
-            )))
-            .unwrap();
+            let auth = handler_shim(&ro, &mut handler);
+
+            if auth.is_ok() {
+                if handler.get_handler().passwd.is_some() {
+                    if !esc_privs() {
+                        std::process::exit(1);
+                    }
+
+                    update_token(&ro.name);
 
-            if auth_ok(&user, &pass, &service) {
-                update_token(&user);
+                    if !drop_privs(&ro) {
+                        std::process::exit(1);
+                    }
+                }
                 return true;
             }
             retry_counter += 1;
@@ -918,30 +1051,113 @@
     None
 }
 
+/// clean environment aside from ~half a dozen vars
+pub fn clean_environment(ro: &mut RunOptions) {
+    ro.old_umask = Some(nix::sys::stat::umask(
+        nix::sys::stat::Mode::from_bits(0o077).unwrap(),
+    ));
+    for (key, _) in std::env::vars() {
+        if key == "LANGUAGE"
+            || key == "XAUTHORITY"
+            || key == "LANG"
+            || key == "LS_COLORS"
+            || key == "TERM"
+            || key == "DISPLAY"
+            || key == "LOGNAME"
+        {
+            continue;
+        }
+
+        if ro.acl_type == ACLTYPE::EDIT && (key == "EDITOR" || key == "VISUAL") {
+            continue;
+        }
+        std::env::remove_var(key);
+    }
+}
+
+/// set environment for helper scripts
+pub fn set_environment(
+    ro: &RunOptions,
+    original_user: &User,
+    original_uid: u32,
+    lookup_name: &User,
+) {
+    std::env::set_var("PLEASE_USER", original_user.name());
+    std::env::set_var("PLEASE_UID", original_uid.to_string());
+    std::env::set_var("PLEASE_GID", original_user.primary_group_id().to_string());
+    std::env::set_var("PLEASE_COMMAND", &ro.command);
+
+    std::env::set_var("SUDO_USER", original_user.name());
+    std::env::set_var("SUDO_UID", original_uid.to_string());
+    std::env::set_var("SUDO_GID", original_user.primary_group_id().to_string());
+    std::env::set_var("SUDO_COMMAND", &ro.command);
+
+    std::env::set_var(
+        "PATH",
+        "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin".to_string(),
+    );
+    std::env::set_var("HOME", lookup_name.home_dir().as_os_str());
+    std::env::set_var("MAIL", format!("/var/mail/{}", ro.target));
+    std::env::set_var("SHELL", lookup_name.shell());
+    std::env::set_var("USER", &ro.target);
+    std::env::set_var("LOGNAME", &ro.target);
+}
+
+pub fn bad_priv_msg() {
+    println!("I cannot set privs. Exiting as not installed correctly.");
+}
+
 /// set privs of usr to target_uid and target_gid. return false if fails
 pub fn set_privs(user: &str, target_uid: nix::unistd::Uid, target_gid: nix::unistd::Gid) -> bool {
     let user = CString::new(user).unwrap();
     if initgroups(&user, target_gid).is_err() {
+        bad_priv_msg();
         return false;
     }
 
     if setgid(target_gid).is_err() {
+        bad_priv_msg();
         return false;
     }
 
     if setuid(target_uid).is_err() {
+        bad_priv_msg();
+        return false;
+    }
+    true
+}
+
+/// set privs of usr to target_uid and target_gid. return false if fails
+pub fn set_eprivs(target_uid: nix::unistd::Uid, target_gid: nix::unistd::Gid) -> bool {
+    if setegid(target_gid).is_err() {
+        bad_priv_msg();
         return false;
     }
+    if seteuid(target_uid).is_err() {
+        bad_priv_msg();
+        return false;
+    }
+
     true
 }
 
+/// set privs (just call eprivs based on ro)
+pub fn drop_privs(ro: &RunOptions) -> bool {
+    esc_privs() && set_eprivs(ro.original_uid, ro.original_gid)
+}
+
+/// reset privs (just call eprivs based on root)
+pub fn esc_privs() -> bool {
+    set_eprivs(nix::unistd::Uid::from_raw(0), nix::unistd::Gid::from_raw(0))
+}
+
 /// return our best guess of what the user's tty is
 pub fn tty_name() -> Option<String> {
     let mut ttyname = None;
 
     /* sometimes a tty isn't attached for all pipes FIXME: make this testable */
     unsafe {
-        for n in 0..255 {
+        for n in 0..3 {
             let ptr = libc::ttyname(n);
             if ptr.is_null() {
                 continue;
@@ -1031,9 +1247,36 @@
     ))
 }
 
+pub fn create_token_dir() -> bool {
+    if !Path::new(&token_dir()).is_dir() && fs::create_dir_all(&token_dir()).is_err() {
+        println!("Could not create token directory");
+        return false;
+    }
+
+    true
+}
+
+pub fn boot_secs() -> libc::timespec {
+    let mut tp = libc::timespec {
+        tv_sec: 0,
+        tv_nsec: 0,
+    };
+    #[cfg(target_os = "linux")]
+    unsafe {
+        libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut tp)
+    };
+    #[cfg(not(target_os = "linux"))]
+    unsafe {
+        libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut tp)
+    };
+    tp
+}
+
 /// does the user have a valid token
+/// return false if time stamp is in the future
+/// return true if token was set within 600 seconds of wall and boot time
 pub fn valid_token(user: &str) -> bool {
-    if !Path::new(&token_dir()).is_dir() && fs::create_dir_all(&token_dir()).is_err() {
+    if !create_token_dir() {
         return false;
     }
 
@@ -1042,27 +1285,47 @@
         return false;
     }
 
+    let secs = 600;
+
     let token_path = token_path.unwrap();
     match fs::metadata(token_path) {
-        Ok(meta) => match meta.modified() {
-            Ok(t) => match SystemTime::now().duration_since(t) {
-                Ok(d) => {
-                    if d.as_secs() < 600 {
-                        return true;
+        Ok(meta) => {
+            match meta.modified() {
+                Ok(t) => {
+                    let tp = boot_secs();
+
+                    match t.duration_since(SystemTime::UNIX_EPOCH) {
+                        Ok(s) => {
+                            if (tp.tv_sec as u64) < s.as_secs() {
+                                // println!("tv_sec lower {} vs {}", tp.tv_sec, s.as_secs());
+                                return false;
+                            }
+                            if ((tp.tv_sec as u64) - s.as_secs()) < secs {
+                                // check the atime isn't older than 600 too
+
+                                match SystemTime::now().duration_since(meta.accessed().unwrap()) {
+                                    Ok(a) => return a.as_secs() <= secs,
+                                    Err(_) => return false,
+                                }
+                            }
+                        }
+                        Err(_) => {
+                            return false;
+                        }
                     }
+
                     false
                 }
                 Err(_e) => false,
-            },
-            Err(_e) => false,
-        },
+            }
+        }
         Err(_) => false,
     }
 }
 
 /// touch the users token on disk
 pub fn update_token(user: &str) {
-    if !Path::new(&token_dir()).is_dir() && fs::create_dir_all(&token_dir()).is_err() {
+    if !create_token_dir() {
         return;
     }
 
@@ -1071,16 +1334,42 @@
         return;
     }
 
+    let old_mode = nix::sys::stat::umask(nix::sys::stat::Mode::from_bits(0o077).unwrap());
     let token_path = token_path.unwrap();
-    match fs::File::create(token_path) {
+    let token_path_tmp = format!("{}.tmp", &token_path);
+    match fs::File::create(&token_path_tmp) {
         Ok(_x) => {}
         Err(x) => println!("Error creating token: {}", x),
     }
+    nix::sys::stat::umask(old_mode);
+
+    let tp = boot_secs();
+
+    let tv_mtime = nix::sys::time::TimeVal::from(libc::timeval {
+        tv_sec: tp.tv_sec,
+        tv_usec: 0,
+    });
+
+    let tv_atime = nix::sys::time::TimeVal::from(libc::timeval {
+        tv_sec: SystemTime::now()
+            .duration_since(SystemTime::UNIX_EPOCH)
+            .unwrap()
+            .as_secs() as libc::time_t,
+        tv_usec: 0,
+    });
+
+    if nix::sys::stat::utimes(Path::new(&token_path_tmp), &tv_atime, &tv_mtime).is_err() {
+        return;
+    }
+
+    if std::fs::rename(&token_path_tmp.as_str(), token_path).is_err() {
+        return;
+    }
 }
 
 /// remove from disk the users token
 pub fn remove_token(user: &str) {
-    if !Path::new(&token_dir()).is_dir() && fs::create_dir_all(&token_dir()).is_err() {
+    if !create_token_dir() {
         return;
     }
 
@@ -1125,6 +1414,12 @@
     println!("{} version {}", &program, env!("CARGO_PKG_VERSION"));
 }
 
+/// return a lump of random alpha numeric characters
+pub fn prng_alpha_num_string(n: usize) -> String {
+    let rng = thread_rng();
+    rng.sample_iter(&Alphanumeric).take(n).collect()
+}
+
 #[cfg(test)]
 mod test {
     use super::*;
@@ -1151,8 +1446,9 @@
 "
         .to_string();
 
+        let mut bytes = 0;
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
         ro.name = "ed".to_string();
@@ -1179,7 +1475,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -1221,19 +1518,20 @@
         ro.command = "/bin/bash".to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, &ro.name, false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, &ro.name, false, &mut bytes);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, false);
 
         ro.name = "other".to_string();
         ro.target = "thingy".to_string();
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, &ro.name, false);
+        read_ini_config_str(&config, &mut vec_eo, &ro.name, false, &mut bytes);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, true);
 
         ro.name = "other".to_string();
         ro.target = "oracle".to_string();
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, &ro.name, false);
+        read_ini_config_str(&config, &mut vec_eo, &ro.name, false, &mut bytes);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, false);
     }
 
@@ -1267,7 +1565,8 @@
         ro.command = "/bin/bash".to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
 
         ro.date = NaiveDate::from_ymd(2019, 12, 31).and_hms(0, 0, 0);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, false);
@@ -1301,7 +1600,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.acl_type = ACLTYPE::LIST;
@@ -1325,13 +1625,15 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
 
         ro.date = NaiveDate::from_ymd(2020, 8, 8).and_hms(0, 0, 0);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, true);
@@ -1369,7 +1671,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "oracle".to_string();
@@ -1408,7 +1711,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "oracle".to_string();
@@ -1447,7 +1751,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "oracle".to_string();
@@ -1483,7 +1788,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "".to_string();
         ro.target = "oracle".to_string();
@@ -1503,7 +1809,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "oracle".to_string();
@@ -1544,7 +1851,8 @@
             .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
         ro.name = "ed".to_string();
@@ -1565,7 +1873,8 @@
             .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
         ro.name = "ed".to_string();
@@ -1589,7 +1898,11 @@
 regex = ^/bin/cat /etc/("
             .to_string();
 
-        assert_eq!(read_ini_config_str(&config, &mut vec_eo, "ed", true), true);
+        let mut bytes = 0;
+        assert_eq!(
+            read_ini_config_str(&config, &mut vec_eo, "ed", true, &mut bytes),
+            true
+        );
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
         let config = "
@@ -1600,7 +1913,11 @@
 "
         .to_string();
 
-        assert_eq!(read_ini_config_str(&config, &mut vec_eo, "ed", true), false);
+        let mut bytes = 0;
+        assert_eq!(
+            read_ini_config_str(&config, &mut vec_eo, "ed", true, &mut bytes),
+            false
+        );
     }
 
     #[test]
@@ -1617,7 +1934,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
         ro.name = "ed".to_string();
@@ -1675,7 +1993,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
         ro.name = "ed".to_string();
@@ -1726,7 +2045,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
 
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
@@ -1765,7 +2085,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
 
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
@@ -1789,7 +2110,8 @@
 regex = ^/var/www/html/%USER.html$"
             .to_string();
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
 
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
@@ -1813,7 +2135,8 @@
 regex = ^/var/www/html/%{USER}.html$"
             .to_string();
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
 
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
@@ -1838,7 +2161,8 @@
             .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         assert_eq!(vec_eo.iter().next().unwrap().rule.as_str(), "/bin/bash");
 
         let mut ro = RunOptions::new();
@@ -1863,7 +2187,8 @@
     fn test_edit_regression_empty() {
         let mut vec_eo: Vec<EnvOptions> = vec![];
         let config = "".to_string();
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -1885,16 +2210,17 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "oracle".to_string();
         ro.acl_type = ACLTYPE::RUN;
         ro.command = "/bin/bash".to_string();
 
-        assert_eq!(can(&vec_eo, &ro).unwrap().permit, true);
+        assert_eq!(can(&vec_eo, &ro).unwrap().permit, false);
 
-        ro.directory = "/".to_string();
+        ro.directory = Some("/".to_string());
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, true);
     }
 
@@ -1911,7 +2237,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
         ro.name = "ed".to_string();
@@ -1925,19 +2252,68 @@
             "no directory given",
         );
 
-        ro.directory = "/".to_string();
+        ro.directory = Some("/".to_string());
         assert_eq!(
             can(&vec_eo, &ro).unwrap().permit,
             false,
             "change outside permitted",
         );
 
-        ro.directory = "/var/www".to_string();
-        assert_eq!(
-            can(&vec_eo, &ro).unwrap().permit,
-            true,
-            "change to permitted"
-        );
+        ro.directory = Some("/var/www".to_string());
+        assert_eq!(can(&vec_eo, &ro).unwrap().permit, true, "permitted");
+    }
+
+    #[test]
+    fn test_dir_tmp() {
+        let config = "
+[regex_anchor]
+name=ed
+target=root
+regex=/bin/bash
+dir=/tmp
+        "
+        .to_string();
+
+        let mut vec_eo: Vec<EnvOptions> = vec![];
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
+        let mut ro = RunOptions::new();
+        ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
+        ro.name = "ed".to_string();
+        ro.target = "root".to_string();
+        ro.acl_type = ACLTYPE::RUN;
+        ro.command = "/bin/bash".to_string();
+        ro.directory = Some("/tmp".to_string());
+
+        assert_eq!(can(&vec_eo, &ro).unwrap().permit, true, "dir_tmp",);
+    }
+
+    #[test]
+    fn test_dir_given_but_none_in_match() {
+        let config = "
+[regex_anchor]
+name=ed
+target=oracle
+hostname=localhost
+regex=.*
+        "
+        .to_string();
+
+        let mut vec_eo: Vec<EnvOptions> = vec![];
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
+        let mut ro = RunOptions::new();
+        ro.date = NaiveDate::from_ymd(2020, 1, 1).and_hms(0, 0, 0);
+        ro.name = "ed".to_string();
+        ro.target = "oracle".to_string();
+        ro.acl_type = ACLTYPE::RUN;
+        ro.command = "/bin/bash".to_string();
+        ro.directory = Some("/".to_string());
+
+        assert_eq!(can(&vec_eo, &ro).unwrap().permit, false, "directory given",);
+
+        ro.directory = Some("".to_string());
+        assert_eq!(can(&vec_eo, &ro).unwrap().permit, false, "directory given",);
     }
 
     #[test]
@@ -1948,13 +2324,13 @@
 target=root
 hostname=localhost
 regex=.*
-dir=.*
 datematch=Fri.*UTC.*
 "
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.date = NaiveDate::from_ymd(2020, 10, 02).and_hms(22, 0, 0);
@@ -1973,13 +2349,13 @@
 target=root
 hostname=localhost
 regex=.*
-dir=.*
 datematch=Fri.*\\s22:00:00\\s+UTC\\s2020
 "
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         ro.date = NaiveDate::from_ymd(2020, 10, 02).and_hms(21, 0, 0);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, false);
         ro.date = NaiveDate::from_ymd(2020, 10, 02).and_hms(23, 0, 0);
@@ -1993,13 +2369,13 @@
 target=root
 hostname=localhost
 regex=.*
-dir=.*
 datematch=Thu\\s+1\\s+Oct\\s+22:00:00\\s+UTC\\s+2020
 "
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         ro.date = NaiveDate::from_ymd(2020, 10, 01).and_hms(21, 0, 0);
         assert_eq!(can(&vec_eo, &ro).unwrap().permit, false);
         ro.date = NaiveDate::from_ymd(2020, 10, 01).and_hms(23, 0, 0);
@@ -2021,7 +2397,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -2036,10 +2413,17 @@
     #[test]
     fn test_read_ini_config_file() {
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        assert_eq!(read_ini_config_file(".", &mut vec_eo, "ed", true), true);
-        assert_eq!(read_ini_config_file("", &mut vec_eo, "ed", true), true);
+        let mut bytes = 0;
+        assert_eq!(
+            read_ini_config_file(".", &mut vec_eo, "ed", true, &mut bytes),
+            true
+        );
         assert_eq!(
-            read_ini_config_file("./faulty", &mut vec_eo, "ed", true),
+            read_ini_config_file("", &mut vec_eo, "ed", true, &mut bytes),
+            true
+        );
+        assert_eq!(
+            read_ini_config_file("./faulty", &mut vec_eo, "ed", true, &mut bytes),
             true
         );
     }
@@ -2063,7 +2447,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -2087,7 +2472,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -2119,7 +2505,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -2141,7 +2528,8 @@
         .to_string();
 
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -2184,7 +2572,8 @@
 "
         .to_string();
         let mut vec_eo: Vec<EnvOptions> = vec![];
-        read_ini_config_str(&config, &mut vec_eo, "ed", false);
+        let mut bytes = 0;
+        read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes);
         let mut ro = RunOptions::new();
         ro.name = "ed".to_string();
         ro.target = "root".to_string();
@@ -2202,14 +2591,21 @@
 include = ./some.ini
 "
         .to_string();
-        assert_eq!(read_ini_config_str(&config, &mut vec_eo, "ed", false), true);
+        let mut bytes: u64 = 0;
+        assert_eq!(
+            read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes),
+            true
+        );
 
         let config = "
 [inc]
 includedir = ./dir.d/some.ini
 "
         .to_string();
-        assert_eq!(read_ini_config_str(&config, &mut vec_eo, "ed", false), true);
+        assert_eq!(
+            read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes),
+            true
+        );
 
         let config = "
 [inc]
@@ -2217,7 +2613,7 @@
 "
         .to_string();
         assert_eq!(
-            read_ini_config_str(&config, &mut vec_eo, "ed", false),
+            read_ini_config_str(&config, &mut vec_eo, "ed", false, &mut bytes),
             false
         );
     }
@@ -2241,4 +2637,9 @@
             "multiple \\\"strings\\\"".to_string()
         );
     }
+
+    #[test]
+    fn test_prng_alpha_num_string() {
+        assert_eq!(prng_alpha_num_string(2).len(), 2);
+    }
 }

Reply to: