pam_rs/
client.rs

1//! Authentication related structure and functions
2use std::{env, ffi::CStr, os::raw::c_char};
3
4use crate::{conv, enums::*, functions::*, types::*};
5
6/// Main struct to authenticate a user
7///
8/// You need to create an instance of it to start an authentication process. If you
9/// want a simple password-based authentication, you can use `Client::with_password`,
10/// and to the following flow:
11///
12/// ```no_run
13/// use pam::Client;
14///
15/// let mut client = Client::with_password("system-auth")
16///         .expect("Failed to init PAM client.");
17/// // Preset the login & password we will use for authentication
18/// client.conversation_mut().set_credentials("login", "password");
19/// // Actually try to authenticate:
20/// client.authenticate().expect("Authentication failed!");
21/// // Now that we are authenticated, it's possible to open a sesssion:
22/// client.open_session().expect("Failed to open a session!");
23/// ```
24///
25/// If you wish to customise the PAM conversation function, you should rather create your
26/// client with `Client::with_handler`, providing a struct implementing the
27/// `conv::Conversation` trait. You can then mutably access your conversation handler using the
28/// `Client::handler_mut` method.
29///
30/// By default, the `Client` will close any opened session when dropped. If you don't
31/// want this, you can change its `close_on_drop` field to `False`.
32pub struct Client<'a, C: conv::Conversation> {
33    /// Flag indicating whether the Client should close the session on drop
34    pub close_on_drop: bool,
35    handle: &'a mut PamHandle,
36    conversation: Box<C>,
37    is_authenticated: bool,
38    has_open_session: bool,
39    last_code: PamReturnCode,
40}
41
42impl<'a> Client<'a, conv::PasswordConv> {
43    /// Create a new `Client` with the given service name and a password-based conversation
44    pub fn with_password(service: &str) -> PamResult<Client<'a, conv::PasswordConv>> {
45        Client::with_conversation(service, conv::PasswordConv::new())
46    }
47}
48
49impl<'a, C: conv::Conversation> Client<'a, C> {
50    /// Create a new `Client` with the given service name and conversation handler
51    pub fn with_conversation(service: &str, conversation: C) -> PamResult<Client<'a, C>> {
52        let mut conversation = Box::new(conversation);
53        let conv = conv::into_pam_conv(&mut *conversation);
54
55        let handle = start(service, None, &conv)?;
56        Ok(Client {
57            close_on_drop: true,
58            handle,
59            conversation,
60            is_authenticated: false,
61            has_open_session: false,
62            last_code: PamReturnCode::Success,
63        })
64    }
65
66    /// Immutable access to the conversation handler of this Client
67    pub fn conversation(&self) -> &C {
68        &*self.conversation
69    }
70
71    /// Mutable access to the conversation handler of this Client
72    pub fn conversation_mut(&mut self) -> &mut C {
73        &mut *self.conversation
74    }
75
76    /// Perform authentication with the provided credentials
77    pub fn authenticate(&mut self, flags: PamFlag) -> PamResult<()> {
78        self.last_code = authenticate(self.handle, flags);
79        if self.last_code != PamReturnCode::Success {
80            // No need to reset here
81            return Err(From::from(self.last_code));
82        }
83
84        self.is_authenticated = true;
85
86        self.last_code = acct_mgmt(self.handle, flags);
87        if self.last_code != PamReturnCode::Success {
88            // Probably not strictly neccessary but better be sure
89            return self.reset();
90        }
91        Ok(())
92    }
93
94    /// Perform the chauthtok to support password update
95    pub fn change_authentication_token(&mut self, flags: PamFlag) -> PamResult<()> {
96        self.last_code = chauthtok(self.handle, flags);
97        if self.last_code != PamReturnCode::Success {
98            // No need to reset here
99            return Err(From::from(self.last_code));
100        }
101        Ok(())
102    }
103
104    /// Perform the get_item / PAM_USER to retrive the username
105    pub fn get_user(&mut self) -> PamResult<String> {
106        get_item(self.handle, PamItemType::User).and_then(|result| {
107            // Pam user is a char *
108            let ptr: *const c_char = unsafe { std::mem::transmute(result) };
109            let username = unsafe { CStr::from_ptr(ptr) };
110            match username.to_str() {
111                Err(_) => Err(PamError(PamReturnCode::System_Err)),
112                Ok(username) => Ok(username.to_string()),
113            }
114        })
115    }
116
117    /// Open a session for a previously authenticated user and
118    /// initialize the environment appropriately (in PAM and regular enviroment variables).
119    pub fn open_session(&mut self) -> PamResult<()> {
120        if !self.is_authenticated {
121            //TODO: is this the right return code?
122            return Err(PamReturnCode::Perm_Denied.into());
123        }
124
125        self.last_code = setcred(self.handle, PamFlag::Establish_Cred);
126        if self.last_code != PamReturnCode::Success {
127            return self.reset();
128        }
129
130        self.last_code = open_session(self.handle, false);
131        if self.last_code != PamReturnCode::Success {
132            return self.reset();
133        }
134
135        // Follow openSSH and call pam_setcred before and after open_session
136        self.last_code = setcred(self.handle, PamFlag::Reinitialize_Cred);
137        if self.last_code != PamReturnCode::Success {
138            return self.reset();
139        }
140
141        self.has_open_session = true;
142        self.initialize_environment()
143    }
144
145    // Initialize the client environment with common variables.
146    // Currently always called from Client.open_session()
147    fn initialize_environment(&mut self) -> PamResult<()> {
148        use uzers::os::unix::UserExt;
149
150        let user = uzers::get_user_by_name(&self.get_user()?).unwrap_or_else(|| {
151            panic!(
152                "Could not get user by name: {:?}",
153                self.get_user()
154            )
155        });
156
157        // Set some common environment variables
158        self.set_env(
159            "USER",
160            user.name()
161                .to_str()
162                .expect("Unix usernames should be valid UTF-8"),
163        )?;
164        self.set_env(
165            "LOGNAME",
166            user.name()
167                .to_str()
168                .expect("Unix usernames should be valid UTF-8"),
169        )?;
170        self.set_env("HOME", user.home_dir().to_str().unwrap())?;
171        self.set_env("PWD", user.home_dir().to_str().unwrap())?;
172        self.set_env("SHELL", user.shell().to_str().unwrap())?;
173        // Note: We don't set PATH here, as this should be the job of `pam_env.so`
174
175        Ok(())
176    }
177
178    // Utility function to set an environment variable in PAM and the process
179    fn set_env(&mut self, key: &str, value: &str) -> PamResult<()> {
180        // Set regular environment variable
181        env::set_var(key, value);
182
183        // Set pam environment variable
184        if getenv(self.handle, key).is_ok() {
185            let name_value = format!("{}={}", key, value);
186            putenv(self.handle, &name_value)
187        } else {
188            Ok(())
189        }
190    }
191
192    // Utility function to reset the pam handle in case of intermediate errors
193    fn reset(&mut self) -> PamResult<()> {
194        setcred(self.handle, PamFlag::Delete_Cred);
195        self.is_authenticated = false;
196        Err(From::from(self.last_code))
197    }
198}
199
200impl<'a, C: conv::Conversation> Drop for Client<'a, C> {
201    fn drop(&mut self) {
202        if self.has_open_session && self.close_on_drop {
203            close_session(self.handle, false);
204        }
205        let code = setcred(self.handle, PamFlag::Delete_Cred);
206        end(self.handle, code);
207    }
208}