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 {
206 self.after_fork_parent_hook = Some(post_fork_parent_hook);
207 self
208 }
209
210 pub fn setup_post_fork_child_hook(mut self, post_fork_child_hook: HookFnReturning) -> Self {
229 self.after_fork_child_hook = Some(post_fork_child_hook);
230 self
231 }
232
233 pub fn setup_post_init_hook(
251 mut self,
252 post_fork_child_hook: HookfFnPostInit<'a>,
253 data: Option<&'a dyn Any>,
254 ) -> Self {
255 self.after_init_hook = Some(post_fork_child_hook);
256 self.after_init_hook_data = data;
257 self
258 }
259
260 pub fn is_child(self) -> bool {
261 self.is_child
262 }
263
264 pub fn get_parent_pid(self) -> i32 {
265 self.parent_pid
266 }
267
268 pub fn get_child_pid(self) -> Option<i32> {
269 self.child_pid
270 }
271
272 pub fn get_pids(&self) -> Result<PidPair> {
273 if let Some(cpid) = self.child_pid {
274 Ok(PidPair {
275 child_pid: cpid,
276 parent_pid: self.parent_pid,
277 })
278 } else {
279 Err(StartNotCalled)
280 }
281 }
282
283 fn do_fork(&mut self) -> Result<()> {
284 unsafe {
285 match fork() {
286 Ok(ForkResult::Parent { child: cpid }) => {
287 self.is_child = false;
288 self.child_pid = Some(cpid.as_raw());
289
290 if let Some(hook) = self.after_fork_parent_hook {
291 hook(self.get_status());
292 } else {
293 exit(0)
294 }
295 }
296 Ok(ForkResult::Child) => {
297 redirect_stdio(&self.stdin, &self.stdout, &self.stderr)?;
299 let pid = getpid();
300 self.is_child = true;
301 self.child_pid = Some(pid.as_raw());
302
303 if let Some(hook) = self.after_fork_child_hook {
304 hook(self.get_status());
305 }
306 ()
307 }
308 Err(_) => return Err(DaemonError::Fork),
309 }
310 }
311 self.has_forked = true;
312 Ok(())
313 }
314
315 fn do_chdir(&self) -> Result<()> {
316 let chdir_path = self.chdir.to_owned();
317 match chdir::<Path>(chdir_path.as_ref()) {
318 Ok(_) => Ok(()),
319 Err(_) => Err(DaemonError::ChDir),
320 }
321 }
322
323 fn setup_pid_file(&self, pid_file_path: &PathBuf) -> Result<()> {
324 let pid_file = &pid_file_path;
325
326 match File::create(pid_file) {
327 Ok(mut fp) => {
328 if let Err(_) = fp.write_all(self.child_pid.unwrap().to_string().as_ref()) {
329 return Err(DaemonError::WritePid);
330 }
331 }
332 Err(_) => return Err(DaemonError::WritePid),
333 }
334
335 if self.user.is_some() && self.group.is_some() {
336 let user = match self.user.clone() {
337 Some(user) => Uid::from_raw(user.id),
338 None => return Err(InvalidUser),
339 };
340
341 let gr = match self.group.clone() {
342 Some(grp) => Gid::from_raw(grp.id),
343 None => return Err(InvalidGroup),
344 };
345 if self.chown_pid_file && self.pid_file.is_some() {
346 match chown::<PathBuf>(pid_file_path, Some(user), Some(gr)) {
347 Ok(_) => return Ok(()),
348 Err(_) => return Err(DaemonError::ChownPid),
349 };
350 }
351 };
352 Ok(())
353 }
354
355 fn valid_usr_gr_pair(&self) -> bool {
356 (self.user.is_some() && self.group.is_none())
357 || (self.user.is_none() && self.group.is_some())
358 }
359
360 fn setup_privileges(&mut self) -> Result<()> {
361 if self.user.is_none() && self.group.is_none() {
362 return Ok(());
363 } else if self.valid_usr_gr_pair() {
364 return Err(InvalidUserGroupPair);
365 }
366
367 let pid_file_path = match self.pid_file.clone() {
368 Some(path) => path.clone(),
369 None => Path::new("").to_path_buf(),
370 };
371
372 if self.pid_file.is_some() {
373 self.setup_pid_file(&pid_file_path)?;
374 }
375
376 Ok({
378 let user = match self.user.clone() {
379 Some(user) => Uid::from_raw(user.id),
380 None => return Err(InvalidUser),
381 };
382
383 let uname = match PasswdRecord::lookup_record_by_id(user.as_raw()) {
384 Ok(record) => record.pw_name,
385 Err(_) => return Err(DaemonError::InvalidUser),
386 };
387
388 let gr = match self.group.clone() {
389 Some(grp) => Gid::from_raw(grp.id),
390 None => return Err(InvalidGroup),
391 };
392
393 match setgid(gr) {
395 Ok(_) => (),
396 Err(_) => return Err(DaemonError::SetGid),
397 };
398 #[cfg(not(target_os = "macos"))]
399 {
400 let u_cstr = match CString::new(uname) {
401 Ok(cstr) => cstr,
402 Err(_) => return Err(DaemonError::SetGid),
403 };
404 match initgroups(&u_cstr, gr) {
405 Ok(_) => (),
406 Err(_) => return Err(DaemonError::InitGroups),
407 };
408 }
409
410 match setuid(user) {
412 Ok(_) => (),
413 Err(_) => return Err(DaemonError::SetUid),
414 }
415 self.dropped_privileges = true;
416 })
417 }
418
419 pub fn get_status(&self) -> DaemonStatus {
420 let pids = if let Ok(pair) = self.get_pids() {
421 Some(pair)
422 } else {
423 None
424 };
425 let self_pid = if self.is_child {
426 self.child_pid.unwrap()
427 } else {
428 self.parent_pid
429 };
430
431 DaemonStatus {
432 pids: pids,
433 is_child: self.is_child,
434 dropped_privileges: self.dropped_privileges,
435 has_forked: self.has_forked,
436 self_pid: self_pid,
437 }
438 }
439
440 pub fn start(&mut self) -> Result<PidPair> {
442 if let Some(hook) = self.before_fork_hook {
446 hook(self.parent_pid);
447 }
448
449 self.do_fork()?;
451
452 if let Some(proc_name) = &self.name {
453 match set_proc_name(proc_name.as_ref()) {
454 Ok(()) => (),
455 Err(e) => return Err(e),
456 }
457 }
458 let umask_mode = match Mode::from_bits(self.umask as _) {
460 Some(mode) => mode,
461 None => return Err(DaemonError::InvalidUmaskBits),
462 };
463 umask(umask_mode);
464
465 if let Err(_) = setsid() {
467 return Err(DaemonError::SetSid);
468 };
469 if let Err(_) = chdir::<Path>(self.chdir.as_path()) {
471 return Err(DaemonError::ChDir);
472 };
473
474 self.setup_privileges()?;
477
478 self.do_chdir()?;
480
481 let pid_pair = if let Ok(pair) = self.get_pids() {
482 pair
483 } else {
484 unreachable!("This call should be impossible to fail, check if do_fork is updating the child_pid correctly")
485 };
486
487 if let Some(hook) = self.after_init_hook {
489 hook(self.after_init_hook_data, self.get_status());
490 Ok(pid_pair)
491 } else {
492 Ok(pid_pair)
493 }
494 }
495}