1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
use std::{fmt, str};
use nix::{
libc::mode_t,
sys::stat::{self, Mode},
};
#[derive(Clone, Copy)]
pub enum Umask {
Masked,
Inherit,
Set(Mode),
}
impl Umask {
pub fn set(self) {
match self {
Self::Masked => Self::masked(),
Self::Inherit => Self::inherit(),
Self::Set(mode) => Self::set_to(mode),
}
}
fn masked() {
// In the year 2025, this is _still_ the only reasonable way to check the
// process' current umask. This is also why we don't calculate a default
// umask and stick it in Clap's `default_value_t` - I'd prefer not to touch
// the process' umask before we know what we're doing with it, but there's
// no other way to look at the current one.
//
// The choice of `all` here is a complicated compromise. In theory, nothing
// will ever observe this umask, as we immediately overwrite it immediately
// below. As of this writing, there is nothing in this service that could
// create a file in this interval, which could then be affected by the
// temporary umask. However, future code changes _could_ introduce something
// that races with this, such as a signal handler that does more than just
// exiting the process.
//
// Using `all` makes sure that _any_ file created in that interval is
// created with "deny all" file permission bits, which will make it useless
// even for this program. Hopefully, that failure will be immediate enough
// to attract attention. The other alternatives which might work are zero
// (don't mask off any permissions, things might be globally readable)
// or 0o0027 (the permissions we'd want to use on most Linux distros, which
// could then silently hide the above race condition).
let current = stat::umask(Mode::all());
// In addition to whatever we inherit, by default, we ensure that files
// created by pilcrow are not world-readable or world-writeable. However,
// we respect the inherited group and user permissions on the assumption
// that the user, or their administrator, has either made a decision about
// group permissions, or are relying on the distro's defaults.
//
// The main thing `pilcrow` creates are its database and its backup
// database, which can contain both confidential information (users'
// conversations) and sensitive information (the service's configuration
// and any API tokens, keys, &c used by the service).
//
// There is no way to tell `sqlite` to restrict the permissions of database
// files directly, which is otherwise what we'd prefer.
let desired = current | Mode::S_IRWXO;
stat::umask(desired);
}
fn inherit() {
// Here's a complete list of steps required to inherit the umask from the calling process:
}
fn set_to(mode: Mode) {
stat::umask(mode);
}
}
impl str::FromStr for Umask {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let umask = match s {
"masked" => Self::Masked,
"inherit" => Self::Inherit,
octal => {
let mode = mode_t::from_str_radix(octal, 8)?;
let mode = Mode::from_bits(mode).ok_or(Error::UnknownBits)?;
Self::Set(mode)
}
};
Ok(umask)
}
}
impl fmt::Display for Umask {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Masked => "masked".fmt(f),
Self::Inherit => "inherit".fmt(f),
Self::Set(mode) => write!(f, "{mode:o}"),
}
}
}
/// Errors occurring during umask option parsing.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Failed to parse a umask value from the input.
#[error(transparent)]
Parse(#[from] std::num::ParseIntError),
/// The provided umask contained invalid bits. (See the constants associated with [`Mode`] for
/// valid umask bits.)
// We dont need to hold onto the actual umask value here - Clap does that for us, and prints
// the value as the user input it, which beats anything we could do here.
#[error("unknown bits in umask")]
UnknownBits,
}
|