1use std::any::Any;
2use std::convert::TryFrom;
3use std::ffi::{CString, OsStr, OsString};
4use std::fs::File;
5use std::io::prelude::*;
6use std::path::{Path, PathBuf};
7use std::process::exit;
8
9use nix::sys::stat::{umask, Mode};
10#[cfg(target_os = "macos")]
11use nix::unistd::{
12 chdir, chown, close, dup2, fork, getpid, setgid, setsid, setuid, ForkResult, Gid, Pid, Uid,
13};
14#[cfg(not(target_os = "macos"))]
15use nix::unistd::{
16 chdir, chown, fork, getpid, initgroups, setgid, setsid, setuid, ForkResult, Gid, Uid,
17};
18
19use crate::ffi::{set_proc_name, PasswdRecord};
20use crate::group::Group;
21use crate::stdio::{redirect_stdio, Stdio};
22use crate::user::User;
23use crate::DaemonError::{InvalidGroup, InvalidUser, StartNotCalled};
24use crate::{DaemonError, Result};
25
26#[derive(Debug, Clone)]
32pub struct Daemon<'a> {
33 pub(crate) chdir: PathBuf,
34 pub(crate) pid_file: Option<PathBuf>,
35 pub(crate) chown_pid_file: bool,
36 pub(crate) user: Option<User>,
37 pub(crate) group: Option<Group>,
38 pub(crate) umask: u16,
39 pub(crate) stdin: Box<Stdio<'a>>,
41 pub(crate) stdout: Box<Stdio<'a>>,
42 pub(crate) stderr: Box<Stdio<'a>>,
43 pub(crate) name: Option<OsString>,
44 pub(crate) before_fork_hook: Option<fn(pid: i32)>,
45 pub(crate) after_fork_parent_hook: Option<fn(parent_pid: i32, child_pid: i32) -> !>,
46 pub(crate) after_fork_child_hook: Option<fn(parent_pid: i32, child_pid: i32) -> ()>,
47 pub(crate) after_init_hook_data: Option<&'a dyn Any>,
48 pub(crate) after_init_hook: Option<fn(Option<&'a dyn Any>)>,
49 pub(crate) child_pid: Option<i32>,
50 pub(crate) parent_pid: i32,
51 pub(crate) is_child: bool,
52 pub(crate) has_forked: bool,
53 pub(crate) dropped_privileges: bool,
54}
55
56pub struct PidPair {
57 pub child_pid: i32,
58 pub parent_pid: i32,
59}
60
61impl<'a> Daemon<'a> {
62 pub fn new() -> Self {
63 Daemon {
64 chdir: Path::new("/").to_owned(),
65 pid_file: None,
66 chown_pid_file: false,
67 user: None,
68 group: None,
69 umask: 0o027,
70 stdin: Box::new(Stdio::devnull()),
71 stdout: Box::new(Stdio::devnull()),
72 stderr: Box::new(Stdio::devnull()),
73 name: None,
74 before_fork_hook: None,
75 after_fork_parent_hook: None,
76 after_fork_child_hook: None,
77 after_init_hook_data: None,
78 after_init_hook: None,
79 child_pid: None,
80 parent_pid: getpid().as_raw(),
81 has_forked: false,
82 is_child: false,
83 dropped_privileges: false,
84 }
85 }
86
87 pub fn pid_file<T: AsRef<Path>>(mut self, path: T, chmod: Option<bool>) -> Self {
95 self.pid_file = Some(path.as_ref().to_owned());
96 self.chown_pid_file = chmod.unwrap_or(false);
97 self
98 }
99
100 pub fn work_dir<T: AsRef<Path>>(mut self, path: T) -> Self {
102 self.chdir = path.as_ref().to_owned();
103 self
104 }
105
106 pub fn user<T: Into<User>>(mut self, user: T) -> Self {
110 self.user = Some(user.into());
111 self
112 }
113
114 pub fn group<T: Into<Group>>(mut self, group: T) -> Self {
118 self.group = Some(group.into());
119 self
120 }
121
122 pub fn group_copy_user(mut self) -> Result<Self> {
123 if let Some(user) = &self.user {
124 self.group = Some(Group::try_from(&user.name)?);
125 Ok(self)
126 } else {
127 Err(InvalidUser)
128 }
129 }
130
131 pub fn umask(mut self, mask: u16) -> Self {
133 self.umask = mask;
134 self
135 }
136
137 pub fn stdin<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
139 self.stdin = Box::new(stdio.into());
140 self
141 }
142
143 pub fn stdout<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
147 self.stdout = Box::new(stdio.into());
148 self
149 }
150
151 pub fn stderr<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
155 self.stderr = Box::new(stdio.into());
156 self
157 }
158
159 pub fn name(mut self, name: &OsStr) -> Self {
163 self.name = Some(OsString::from(name));
164 self
165 }
166
167 pub fn setup_pre_fork_hook(mut self, pre_fork_hook: fn(pid: i32)) -> Self {
169 self.before_fork_hook = Some(pre_fork_hook);
170 self
171 }
172
173 pub fn setup_post_fork_parent_hook(
194 mut self,
195 post_fork_parent_hook: fn(parent_pid: i32, child_pid: i32) -> !,
196 ) -> Self {
197 self.after_fork_parent_hook = Some(post_fork_parent_hook);
198 self
199 }
200
201 pub fn setup_post_fork_child_hook(
220 mut self,
221 post_fork_child_hook: fn(parent_pid: i32, child_pid: i32) -> (),
222 ) -> Self {
223 self.after_fork_child_hook = Some(post_fork_child_hook);
224 self
225 }
226
227 pub fn setup_post_init_hook(
245 mut self,
246 post_fork_child_hook: fn(ctx: Option<&'a dyn Any>),
247 data: Option<&'a dyn Any>,
248 ) -> Self {
249 self.after_init_hook = Some(post_fork_child_hook);
250 self.after_init_hook_data = data;
251 self
252 }
253
254 pub fn is_child(self) -> bool {
255 self.is_child
256 }
257
258 pub fn get_parent_pid(self) -> i32 {
259 self.parent_pid
260 }
261
262 pub fn get_child_pid(self) -> Option<i32> {
263 self.child_pid
264 }
265
266 pub fn get_pids(&self) -> Result<PidPair> {
267 if let Some(cpid) = self.child_pid {
268 Ok(PidPair {
269 child_pid: cpid,
270 parent_pid: self.parent_pid,
271 })
272 } else {
273 Err(StartNotCalled)
274 }
275 }
276
277 fn do_fork(&mut self) -> Result<()> {
278 unsafe {
279 match fork() {
280 Ok(ForkResult::Parent { child: cpid }) => {
281 self.is_child = false;
282 self.child_pid = Some(cpid.as_raw());
283
284 if let Some(hook) = self.after_fork_parent_hook {
285 hook(self.parent_pid, cpid.as_raw());
286 } else {
287 exit(0)
288 }
289 }
290 Ok(ForkResult::Child) => {
291 redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
293 let pid = getpid();
294 self.is_child = true;
295 self.child_pid = Some(pid.as_raw());
296
297 if let Some(hook) = self.after_fork_child_hook {
298 hook(self.parent_pid, pid.as_raw());
299 }
300 ()
301 }
302 Err(_) => return Err(DaemonError::Fork),
303 }
304 self.has_forked = true;
305 Ok(())
306 }
307 }
308
309 fn do_chdir(&self) -> Result<()> {
310 let chdir_path = self.chdir.to_owned();
311 match chdir::<Path>(chdir_path.as_ref()) {
312 Ok(_) => Ok(()),
313 Err(_) => Err(DaemonError::ChDir),
314 }
315 }
316
317 fn setup_pid_file(&self, pid_file_path: &PathBuf) -> Result<()> {
318 let pid_file = &pid_file_path;
319
320 let user = match self.user.clone() {
321 Some(user) => Uid::from_raw(user.id),
322 None => return Err(InvalidUser),
323 };
324
325 let gr = match self.group.clone() {
326 Some(grp) => Gid::from_raw(grp.id),
327 None => return Err(InvalidGroup),
328 };
329
330 match File::create(pid_file) {
331 Ok(mut fp) => {
332 if let Err(_) = fp.write_all(self.child_pid.unwrap().to_string().as_ref()) {
333 return Err(DaemonError::WritePid);
334 }
335 }
336 Err(_) => return Err(DaemonError::WritePid),
337 }
338
339 if self.chown_pid_file && self.pid_file.is_some() {
340 match chown::<PathBuf>(pid_file_path, Some(user), Some(gr)) {
341 Ok(_) => return Ok(()),
342 Err(_) => return Err(DaemonError::ChownPid),
343 };
344 };
345 Ok(())
346 }
347
348 fn check_chown_precodnitions(&self) -> Result<()> {
349 if self.chown_pid_file && (self.user.is_none() || self.group.is_none()) {
350 return Err(DaemonError::InvalidUserGroupPair);
351 } else if (self.user.is_some() || self.group.is_some())
352 && (self.user.is_none() || self.group.is_none())
353 {
354 return Err(DaemonError::InvalidUserGroupPair);
355 } else {
356 Ok(())
357 }
358 }
359
360 fn setup_privileges(&mut self) -> Result<()> {
361 self.check_chown_precodnitions()?;
362
363 let pid_file_path = match self.pid_file.clone() {
364 Some(path) => path.clone(),
365 None => Path::new("").to_path_buf(),
366 };
367
368 if self.pid_file.is_some() {
369 self.setup_pid_file(&pid_file_path)?;
370 }
371
372 Ok({
374 let user = match self.user.clone() {
375 Some(user) => Uid::from_raw(user.id),
376 None => return Err(InvalidUser),
377 };
378
379 let uname = match PasswdRecord::lookup_record_by_id(user.as_raw()) {
380 Ok(record) => record.pw_name,
381 Err(_) => return Err(DaemonError::InvalidUser),
382 };
383
384 let gr = match self.group.clone() {
385 Some(grp) => Gid::from_raw(grp.id),
386 None => return Err(InvalidGroup),
387 };
388
389 match setgid(gr) {
391 Ok(_) => (),
392 Err(_) => return Err(DaemonError::SetGid),
393 };
394 #[cfg(not(target_os = "macos"))]
395 {
396 let u_cstr = match CString::new(uname) {
397 Ok(cstr) => cstr,
398 Err(_) => return Err(DaemonError::SetGid),
399 };
400 match initgroups(&u_cstr, gr) {
401 Ok(_) => (),
402 Err(_) => return Err(DaemonError::InitGroups),
403 };
404 }
405
406 match setuid(user) {
408 Ok(_) => (),
409 Err(_) => return Err(DaemonError::SetUid),
410 }
411 self.dropped_privileges = true;
412 })
413 }
414
415 pub fn start(&mut self) -> Result<PidPair> {
417 if let Some(hook) = self.before_fork_hook {
421 hook(self.parent_pid);
422 }
423
424 self.do_fork()?;
429
430 if let Some(proc_name) = &self.name {
431 match set_proc_name(proc_name.as_ref()) {
432 Ok(()) => (),
433 Err(e) => return Err(e),
434 }
435 }
436 let umask_mode = match Mode::from_bits(self.umask as _) {
438 Some(mode) => mode,
439 None => return Err(DaemonError::InvalidUmaskBits),
440 };
441 umask(umask_mode);
442
443 if let Err(_) = setsid() {
445 return Err(DaemonError::SetSid);
446 };
447 if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
449 return Err(DaemonError::ChDir);
450 };
451
452 self.setup_privileges()?;
455
456 self.do_chdir()?;
458
459 let pid_pair = if let Ok(pair) = self.get_pids() {
460 pair
461 } else {
462 unreachable!("This call should be impossible to fail, check if do_fork is updating the pid correctly")
463 };
464
465 if let Some(hook) = self.after_init_hook {
467 hook(self.after_init_hook_data);
468 Ok(pid_pair)
469 } else {
470 Ok(pid_pair)
471 }
472 }
473}