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, InvalidUserGroupPair, StartNotCalled};
24use crate::{DaemonError, Result};
25
26type HookFnFinal = fn(DaemonStatus) -> !;
27type HookFnReturning = fn(DaemonStatus) -> ();
28type HookfFnPostInit<'a> = fn(Option<&'a dyn Any>, st: DaemonStatus);
29
30#[derive(Debug, Clone)]
36pub struct Daemon<'a> {
37 pub(crate) chdir: PathBuf,
38 pub(crate) pid_file: Option<PathBuf>,
39 pub(crate) chown_pid_file: bool,
40 pub(crate) user: Option<User>,
41 pub(crate) group: Option<Group>,
42 pub(crate) umask: u16,
43 pub(crate) stdin: Box<Stdio<'a>>,
45 pub(crate) stdout: Box<Stdio<'a>>,
46 pub(crate) stderr: Box<Stdio<'a>>,
47 pub(crate) name: Option<OsString>,
48 pub(crate) before_fork_hook: Option<fn(pid: i32)>,
49 pub(crate) after_fork_parent_hook: Option<HookFnFinal>,
50 pub(crate) after_fork_child_hook: Option<HookFnReturning>,
51 pub(crate) after_init_hook_data: Option<&'a dyn Any>,
52 pub(crate) after_init_hook: Option<HookfFnPostInit<'a>>,
53 pub(crate) child_pid: Option<i32>,
54 pub(crate) parent_pid: i32,
55 pub(crate) is_child: bool,
56 pub(crate) has_forked: bool,
57 pub(crate) dropped_privileges: bool,
58}
59
60pub struct PidPair {
61 pub child_pid: i32,
62 pub parent_pid: i32,
63}
64
65pub struct DaemonStatus {
66 pub pids: Option<PidPair>,
67 pub is_child: bool,
68 pub dropped_privileges: bool,
69 pub has_forked: bool,
70 pub self_pid: i32,
71}
72
73impl<'a> Daemon<'a> {
74 pub fn new() -> Self {
75 Daemon {
76 chdir: Path::new("/").to_owned(),
77 pid_file: None,
78 chown_pid_file: false,
79 user: None,
80 group: None,
81 umask: 0o027,
82 stdin: Box::new(Stdio::devnull()),
83 stdout: Box::new(Stdio::devnull()),
84 stderr: Box::new(Stdio::devnull()),
85 name: None,
86 before_fork_hook: None,
87 after_fork_parent_hook: None,
88 after_fork_child_hook: None,
89 after_init_hook_data: None,
90 after_init_hook: None,
91 child_pid: None,
92 parent_pid: getpid().as_raw(),
93 has_forked: false,
94 is_child: false,
95 dropped_privileges: false,
96 }
97 }
98
99 pub fn pid_file<T: AsRef<Path>>(mut self, path: T, chmod: Option<bool>) -> Self {
107 self.pid_file = Some(path.as_ref().to_owned());
108 self.chown_pid_file = chmod.unwrap_or(false);
109 self
110 }
111
112 pub fn work_dir<T: AsRef<Path>>(mut self, path: T) -> Self {
114 self.chdir = path.as_ref().to_owned();
115 self
116 }
117
118 pub fn user<T: Into<User>>(mut self, user: T) -> Self {
122 self.user = Some(user.into());
123 self
124 }
125
126 pub fn group<T: Into<Group>>(mut self, group: T) -> Self {
130 self.group = Some(group.into());
131 self
132 }
133
134 pub fn group_copy_user(mut self) -> Result<Self> {
135 if let Some(user) = &self.user {
136 self.group = Some(Group::try_from(&user.name)?);
137 Ok(self)
138 } else {
139 Err(InvalidUser)
140 }
141 }
142
143 pub fn umask(mut self, mask: u16) -> Self {
145 self.umask = mask;
146 self
147 }
148
149 pub fn stdin<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
151 self.stdin = Box::new(stdio.into());
152 self
153 }
154
155 pub fn stdout<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
159 self.stdout = Box::new(stdio.into());
160 self
161 }
162
163 pub fn stderr<T: Into<Stdio<'a>>>(mut self, stdio: T) -> Self {
167 self.stderr = Box::new(stdio.into());
168 self
169 }
170
171 pub fn name(mut self, name: &OsStr) -> Self {
175 self.name = Some(OsString::from(name));
176 self
177 }
178
179 pub fn setup_pre_fork_hook(mut self, pre_fork_hook: fn(pid: i32)) -> Self {
181 self.before_fork_hook = Some(pre_fork_hook);
182 self
183 }
184
185 pub fn setup_post_fork_parent_hook(mut self, post_fork_parent_hook: HookFnFinal) -> Self {
207 self.after_fork_parent_hook = Some(post_fork_parent_hook);
208 self
209 }
210
211 pub fn setup_post_fork_child_hook(mut self, post_fork_child_hook: HookFnReturning) -> Self {
231 self.after_fork_child_hook = Some(post_fork_child_hook);
232 self
233 }
234
235 pub fn setup_post_init_hook(
253 mut self,
254 post_fork_child_hook: HookfFnPostInit<'a>,
255 data: Option<&'a dyn Any>,
256 ) -> Self {
257 self.after_init_hook = Some(post_fork_child_hook);
258 self.after_init_hook_data = data;
259 self
260 }
261
262 pub fn is_child(self) -> bool {
263 self.is_child
264 }
265
266 pub fn get_parent_pid(self) -> i32 {
267 self.parent_pid
268 }
269
270 pub fn get_child_pid(self) -> Option<i32> {
271 self.child_pid
272 }
273
274 pub fn get_pids(&self) -> Result<PidPair> {
275 if let Some(cpid) = self.child_pid {
276 Ok(PidPair {
277 child_pid: cpid,
278 parent_pid: self.parent_pid,
279 })
280 } else {
281 Err(StartNotCalled)
282 }
283 }
284
285 fn do_fork(&mut self) -> Result<()> {
286 unsafe {
287 match fork() {
288 Ok(ForkResult::Parent { child: cpid }) => {
289 self.is_child = false;
290 self.child_pid = Some(cpid.as_raw());
291
292 if let Some(hook) = self.after_fork_parent_hook {
293 hook(self.get_status());
294 } else {
295 exit(0)
296 }
297 }
298 Ok(ForkResult::Child) => {
299 redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
301 let pid = getpid();
302 self.is_child = true;
303 self.child_pid = Some(pid.as_raw());
304
305 if let Some(hook) = self.after_fork_child_hook {
306 hook(self.get_status());
307 }
308 ()
309 }
310 Err(_) => return Err(DaemonError::Fork),
311 }
312 }
313 self.has_forked = true;
314 Ok(())
315 }
316
317 fn do_chdir(&self) -> Result<()> {
318 let chdir_path = self.chdir.to_owned();
319 match chdir::<Path>(chdir_path.as_ref()) {
320 Ok(_) => Ok(()),
321 Err(_) => Err(DaemonError::ChDir),
322 }
323 }
324
325 fn setup_pid_file(&self, pid_file_path: &PathBuf) -> Result<()> {
326 let pid_file = &pid_file_path;
327
328 match File::create(pid_file) {
329 Ok(mut fp) => {
330 if let Err(_) = fp.write_all(self.child_pid.unwrap().to_string().as_ref()) {
331 return Err(DaemonError::WritePid);
332 }
333 }
334 Err(_) => return Err(DaemonError::WritePid),
335 }
336
337 if self.user.is_some() && self.group.is_some() {
338 let user = match self.user.clone() {
339 Some(user) => Uid::from_raw(user.id),
340 None => return Err(InvalidUser),
341 };
342
343 let gr = match self.group.clone() {
344 Some(grp) => Gid::from_raw(grp.id),
345 None => return Err(InvalidGroup),
346 };
347 if self.chown_pid_file && self.pid_file.is_some() {
348 match chown::<PathBuf>(pid_file_path, Some(user), Some(gr)) {
349 Ok(_) => return Ok(()),
350 Err(_) => return Err(DaemonError::ChownPid),
351 };
352 }
353 };
354 Ok(())
355 }
356
357 fn valid_usr_gr_pair(&self) -> bool {
358 (self.user.is_some() && self.group.is_none())
359 || (self.user.is_none() && self.group.is_some())
360 }
361
362 fn setup_privileges(&mut self) -> Result<()> {
363 if self.user.is_none() && self.group.is_none() {
364 return Ok(());
365 } else if self.valid_usr_gr_pair() {
366 return Err(InvalidUserGroupPair);
367 }
368
369 let pid_file_path = match self.pid_file.clone() {
370 Some(path) => path.clone(),
371 None => Path::new("").to_path_buf(),
372 };
373
374 if self.pid_file.is_some() {
375 self.setup_pid_file(&pid_file_path)?;
376 }
377
378 Ok({
380 let user = match self.user.clone() {
381 Some(user) => Uid::from_raw(user.id),
382 None => return Err(InvalidUser),
383 };
384
385 let uname = match PasswdRecord::lookup_record_by_id(user.as_raw()) {
386 Ok(record) => record.pw_name,
387 Err(_) => return Err(DaemonError::InvalidUser),
388 };
389
390 let gr = match self.group.clone() {
391 Some(grp) => Gid::from_raw(grp.id),
392 None => return Err(InvalidGroup),
393 };
394
395 match setgid(gr) {
397 Ok(_) => (),
398 Err(_) => return Err(DaemonError::SetGid),
399 };
400 #[cfg(not(target_os = "macos"))]
401 {
402 let u_cstr = match CString::new(uname) {
403 Ok(cstr) => cstr,
404 Err(_) => return Err(DaemonError::SetGid),
405 };
406 match initgroups(&u_cstr, gr) {
407 Ok(_) => (),
408 Err(_) => return Err(DaemonError::InitGroups),
409 };
410 }
411
412 match setuid(user) {
414 Ok(_) => (),
415 Err(_) => return Err(DaemonError::SetUid),
416 }
417 self.dropped_privileges = true;
418 })
419 }
420
421 pub fn get_status(&self) -> DaemonStatus {
422 let pids = if let Ok(pair) = self.get_pids() {
423 Some(pair)
424 } else {
425 None
426 };
427 let self_pid = if self.is_child {
428 self.child_pid.unwrap()
429 } else {
430 self.parent_pid
431 };
432
433 DaemonStatus {
434 pids: pids,
435 is_child: self.is_child,
436 dropped_privileges: self.dropped_privileges,
437 has_forked: self.has_forked,
438 self_pid: self_pid,
439 }
440 }
441
442 pub fn start(&mut self) -> Result<PidPair> {
444 if let Some(hook) = self.before_fork_hook {
448 hook(self.parent_pid);
449 }
450
451 self.do_fork()?;
453
454 if let Some(proc_name) = &self.name {
455 match set_proc_name(proc_name.as_ref()) {
456 Ok(()) => (),
457 Err(e) => return Err(e),
458 }
459 }
460 let umask_mode = match Mode::from_bits(self.umask as _) {
462 Some(mode) => mode,
463 None => return Err(DaemonError::InvalidUmaskBits),
464 };
465 umask(umask_mode);
466
467 if let Err(_) = setsid() {
469 return Err(DaemonError::SetSid);
470 };
471 if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
473 return Err(DaemonError::ChDir);
474 };
475
476 self.setup_privileges()?;
479
480 self.do_chdir()?;
482
483 let pid_pair = if let Ok(pair) = self.get_pids() {
484 pair
485 } else {
486 unreachable!("This call should be impossible to fail, check if do_fork is updating the child_pid correctly")
487 };
488
489 if let Some(hook) = self.after_init_hook {
491 hook(self.after_init_hook_data, self.get_status());
492 Ok(pid_pair)
493 } else {
494 Ok(pid_pair)
495 }
496 }
497}