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
use errors::*;
use libc;
use nix::unistd;
use std::path::{Path, PathBuf};

/// PrivDrop structure
///
/// # Example
/// ```
/// PrivDrop::default().chroot("/var/empty").user("_appuser").apply().unwrap();
/// ```
#[derive(Default)]
pub struct PrivDrop {
    chroot: Option<PathBuf>,
    user: Option<String>,
    group: Option<String>,
}

impl PrivDrop {
    /// chroot() to a specific directory before switching to a non-root user
    pub fn chroot<T: AsRef<Path>>(mut self, path: T) -> Self {
        self.chroot = Some(path.as_ref().to_owned());
        self
    }

    /// Set the name of a user to switch to
    pub fn user<T: AsRef<str>>(mut self, user: T) -> Self {
        self.user = Some(user.as_ref().to_owned());
        self
    }

    /// Set a group name to switch to, if different from the primary group of the user
    pub fn group<T: AsRef<str>>(mut self, group: T) -> Self {
        self.group = Some(group.as_ref().to_owned());
        self
    }

    /// Apply the changes
    pub fn apply(self) -> Result<(), PrivDropError> {
        try!(self.do_preload());
        try!(self.do_chroot());
        try!(self.do_userchange());
        Ok(())
    }

    fn do_preload(&self) -> Result<(), PrivDropError> {
        unsafe {
            libc::strerror(1);
            libc::setlocale(libc::LC_CTYPE, "C".as_ptr() as *const i8);
            libc::setlocale(libc::LC_COLLATE, "C".as_ptr() as *const i8);
            let mut now: libc::time_t = 0;
            libc::time(&mut now);
            libc::localtime(&now);
        }
        Ok(())
    }

    fn do_chroot(&self) -> Result<(), PrivDropError> {
        if let Some(ref chroot) = self.chroot {
            try!(unistd::chdir(chroot));
            try!(unistd::chroot(chroot));
            try!(unistd::chdir("/"))
        }
        Ok(())
    }

    fn do_userchange(&self) -> Result<(), PrivDropError> {
        if let Some(ref user) = self.user {
            let pwent = unsafe { libc::getpwnam(user.as_ptr() as *const i8) };
            if pwent.is_null() {
                return Err(PrivDropError::from((ErrorKind::SysError, "User not found")));
            }
            let (uid, gid) = (unsafe { *pwent }.pw_uid, unsafe { *pwent }.pw_gid);
            let gid = if let Some(ref group) = self.group {
                let grent = unsafe { libc::getgrnam(group.as_ptr() as *const i8) };
                if grent.is_null() {
                    return Err(PrivDropError::from((ErrorKind::SysError, "Group not found")));
                }
                unsafe { *grent }.gr_gid
            } else {
                gid
            };
            if unsafe { libc::setgroups(1, &gid) } != 0 {
                return Err(PrivDropError::from((ErrorKind::SysError,
                                                "Unable to revoke supplementary groups")));
            }
            try!(unistd::setgid(gid));
            try!(unistd::setuid(uid));
        }
        Ok(())
    }
}