Mercurial > crates > nonstick
comparison src/handle.rs @ 80:5aa1a010f1e8
Start using PAM headers; improve owned/borrowed distinction.
- Uses bindgen to generate bindings (only if needed).
- Gets the story together on owned vs. borrowed handles.
- Reduces number of mutable borrows in handle operation
(since `PamHandle` is neither `Send` nor `Sync`,
we never have to worry about thread safety.
- Improves a bunch of macros so we don't have our own
special syntax for docs.
- Implement question indirection for standard XSSO PAM implementations.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Tue, 10 Jun 2025 01:09:30 -0400 |
parents | 002adfb98c5c |
children |
comparison
equal
deleted
inserted
replaced
79:2128123b9406 | 80:5aa1a010f1e8 |
---|---|
1 //! The wrapper types and traits for handles into the PAM library. | 1 //! The wrapper types and traits for handles into the PAM library. |
2 | |
2 use crate::constants::Result; | 3 use crate::constants::Result; |
3 use crate::conv::Conversation; | 4 use crate::conv::Conversation; |
4 | 5 |
5 macro_rules! trait_item { | 6 macro_rules! trait_item { |
6 (get = $getter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => { | 7 ($(#[$md:meta])* get = $getter:ident, item = $item:literal $(, see = $see:path)?) => { |
7 $( | 8 $(#[$md])* |
8 $(#[doc = $doc])* | 9 #[doc = ""] |
9 #[doc = ""] | |
10 )? | |
11 #[doc = concat!("Gets the `", $item, "` of the PAM handle.")] | 10 #[doc = concat!("Gets the `", $item, "` of the PAM handle.")] |
12 $( | 11 $( |
13 #[doc = concat!("See [`", stringify!($see), "`].")] | 12 #[doc = concat!("See [`", stringify!($see), "`].")] |
14 )? | 13 )? |
15 #[doc = ""] | 14 /// |
16 #[doc = "Returns a reference to the item's value, owned by PAM."] | 15 /// Returns a reference to the item's value, owned by PAM. |
17 #[doc = "The item is assumed to be valid UTF-8 text."] | 16 /// The item is assumed to be valid UTF-8 text. |
18 #[doc = "If it is not, `ConversationError` is returned."] | 17 /// If it is not, `ConversationError` is returned. |
19 #[doc = ""] | 18 /// |
20 #[doc = "See the [`pam_get_item`][man] manual page,"] | 19 /// See the [`pam_get_item`][man] manual page, |
21 #[doc = "[`pam_get_item` in the Module Writers' Guide][mwg], or"] | 20 /// [`pam_get_item` in the Module Writers' Guide][mwg], or |
22 #[doc = "[`pam_get_item` in the Application Developers' Guide][adg]."] | 21 /// [`pam_get_item` in the Application Developers' Guide][adg]. |
23 #[doc = ""] | 22 /// |
24 #[doc = "[man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html"] | 23 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_item.3.html |
25 #[doc = "[adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_get_item"] | 24 /// [adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_get_item |
26 #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item"] | 25 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item |
27 fn $getter(&mut self) -> Result<Option<&str>>; | 26 fn $getter(&self) -> Result<Option<&str>>; |
28 }; | 27 }; |
29 (set = $setter:ident, item = $item:literal $(, see = $see:path)? $(, $($doc:literal)*)?) => { | 28 ($(#[$md:meta])* set = $setter:ident, item = $item:literal $(, see = $see:path)?) => { |
30 $( | 29 $(#[$md])* |
31 $(#[doc = $doc])* | |
32 #[doc = ""] | |
33 )? | |
34 #[doc = concat!("Sets the `", $item, "` from the PAM handle.")] | 30 #[doc = concat!("Sets the `", $item, "` from the PAM handle.")] |
35 $( | 31 $( |
36 #[doc = concat!("See [`", stringify!($see), "`].")] | 32 #[doc = concat!("See [`", stringify!($see), "`].")] |
37 )? | 33 )? |
38 #[doc = ""] | 34 /// |
39 #[doc = "Sets the item's value. PAM copies the string's contents."] | 35 /// Sets the item's value. PAM copies the string's contents. |
40 #[doc = "If the string contains a null byte, this will return "] | 36 /// If the string contains a null byte, this will return |
41 #[doc = "a `ConversationError`."] | 37 /// a `ConversationError`. |
42 #[doc = ""] | 38 /// |
43 #[doc = "See the [`pam_set_item`][man] manual page,"] | 39 /// See the [`pam_set_item`][man] manual page, |
44 #[doc = "[`pam_set_item` in the Module Writers' Guide][mwg], or"] | 40 /// [`pam_set_item` in the Module Writers' Guide][mwg], or |
45 #[doc = "[`pam_set_item` in the Application Developers' Guide][adg]."] | 41 /// [`pam_set_item` in the Application Developers' Guide][adg]. |
46 #[doc = ""] | 42 /// |
47 #[doc = "[man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html"] | 43 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_set_item.3.html |
48 #[doc = "[adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_set_item"] | 44 /// [adg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/adg-interface-by-app-expected.html#adg-pam_set_item |
49 #[doc = "[mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item"] | 45 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_set_item |
50 fn $setter(&mut self, value: Option<&str>) -> Result<()>; | 46 fn $setter(&mut self, value: Option<&str>) -> Result<()>; |
51 }; | 47 }; |
52 } | 48 } |
53 | |
54 /// All-in-one trait for what you should expect from PAM as an application. | |
55 pub trait PamHandleApplication: PamApplicationOnly + PamShared {} | |
56 impl<T> PamHandleApplication for T where T: PamApplicationOnly + PamShared {} | |
57 | |
58 /// All-in-one trait for what you should expect from PAM as a module. | |
59 pub trait PamHandleModule: PamModuleOnly + PamShared {} | |
60 impl<T> PamHandleModule for T where T: PamModuleOnly + PamShared {} | |
61 | 49 |
62 /// Functionality for both PAM applications and PAM modules. | 50 /// Functionality for both PAM applications and PAM modules. |
63 /// | 51 /// |
64 /// This base trait includes features of a PAM handle that are available | 52 /// This base trait includes features of a PAM handle that are available |
65 /// to both applications and modules. | 53 /// to both applications and modules. |
98 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html | 86 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_user.3.html |
99 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user | 87 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_user |
100 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>; | 88 fn get_user(&mut self, prompt: Option<&str>) -> Result<&str>; |
101 | 89 |
102 trait_item!( | 90 trait_item!( |
91 /// The identity of the user for whom service is being requested. | |
92 /// | |
93 /// Unlike [`get_user`](Self::get_user), this will simply get | |
94 /// the current state of the user item, and not request the username. | |
95 /// While PAM usually sets this automatically in the `get_user` call, | |
96 /// it may be changed by a module during the PAM transaction. | |
97 /// Applications should check it after each step of the PAM process. | |
103 get = user_item, | 98 get = user_item, |
104 item = "PAM_USER", | 99 item = "PAM_USER", |
105 see = Self::get_user, | 100 see = Self::get_user |
106 "The identity of the user for whom service is being requested." | 101 ); |
107 "" | 102 trait_item!( |
108 "Unlike [`get_user`](Self::get_user), this will simply get" | 103 /// Sets the identity of the logging-in user. |
109 "the current state of the user item, and not request the username. " | 104 /// |
110 "While PAM usually sets this automatically in the `get_user` call, " | 105 /// Usually this will be set during the course of |
111 "it may be changed by a module during the PAM transaction. " | 106 /// a [`get_user`](Self::get_user) call, but you may set it manually |
112 "Applications should check it after each step of the PAM process." | 107 /// or change it during the PAM process. |
113 ); | |
114 trait_item!( | |
115 set = set_user_item, | 108 set = set_user_item, |
116 item = "PAM_USER", | 109 item = "PAM_USER", |
117 see = Self::user_item, | 110 see = Self::user_item |
118 "Sets the identity of the logging-in user." | 111 ); |
119 "" | 112 |
120 "Usually this will be set during the course of " | 113 trait_item!( |
121 "a [`get_user`](Self::get_user) call, but you may set it manually " | 114 /// The service name, which identifies the PAM stack which is used |
122 "or change it during the PAM process." | 115 /// to perform authentication. |
123 ); | |
124 | |
125 trait_item!( | |
126 get = service, | 116 get = service, |
127 item = "PAM_SERVICE", | 117 item = "PAM_SERVICE" |
128 "The service name, which identifies the PAM stack which is used " | 118 ); |
129 "to perform authentication." | 119 trait_item!( |
130 ); | 120 /// The service name, which identifies the PAM stack which is used |
131 trait_item!( | 121 /// to perform authentication. It's probably a bad idea to change this. |
132 set = set_service, | 122 set = set_service, |
133 item = "PAM_SERVICE", | 123 item = "PAM_SERVICE", |
134 see = Self::service, | 124 see = Self::service |
135 "The service name, which identifies the PAM stack which is used " | 125 ); |
136 "to perform authentication. It's probably a bad idea to change this." | 126 |
137 ); | 127 trait_item!( |
138 | 128 /// The string used to prompt for a user's name. |
139 trait_item!( | 129 /// By default, this is a localized version of `login: `. |
140 get = user_prompt, | 130 get = user_prompt, |
141 item = "PAM_USER_PROMPT", | 131 item = "PAM_USER_PROMPT" |
142 "The string used to prompt for a user's name." | 132 ); |
143 "By default, this is a localized version of `login: `." | 133 trait_item!( |
144 ); | 134 /// Sets the string used to prompt for a user's name. |
145 trait_item!( | |
146 set = set_user_prompt, | 135 set = set_user_prompt, |
147 item = "PAM_USER_PROMPT", | 136 item = "PAM_USER_PROMPT", |
148 see = Self::user_prompt, | 137 see = Self::user_prompt |
149 "Sets the string used to prompt for a user's name." | 138 ); |
150 ); | 139 |
151 | 140 trait_item!( |
152 trait_item!( | 141 /// "The terminal name prefixed by /dev/ for device files." |
142 /// | |
143 /// This is the terminal the user is logging in on. | |
144 /// Very old applications may use this instead of `PAM_XDISPLAY`. | |
153 get = tty_name, | 145 get = tty_name, |
154 item = "PAM_TTY", | 146 item = "PAM_TTY" |
155 "\"The terminal name prefixed by /dev/ for device files.\"" | 147 ); |
156 "" | 148 trait_item!( |
157 "This is the terminal the user is logging in on." | 149 /// Sets the terminal name. |
158 "Very old applications may use this instead of `PAM_XDISPLAY`." | 150 /// |
159 ); | 151 /// (TODO: See if libpam sets this itself or if the application does.) |
160 trait_item!( | |
161 set = set_tty_name, | 152 set = set_tty_name, |
162 item = "PAM_TTY", | 153 item = "PAM_TTY", |
163 see = Self::tty_name, | 154 see = Self::tty_name |
164 "Sets the terminal name." | 155 ); |
165 "" | 156 |
166 "(TODO: See if libpam sets this itself or if the application does.)" | 157 trait_item!( |
167 ); | 158 /// If set, the identity of the remote user logging in. |
168 | 159 /// |
169 trait_item!( | 160 /// This is only as trustworthy as the application calling PAM. |
161 /// Also see [`remote_host`](Self::remote_host). | |
170 get = remote_user, | 162 get = remote_user, |
171 item = "PAM_RUSER", | 163 item = "PAM_RUSER" |
172 "If set, the identity of the remote user logging in." | 164 ); |
173 "" | 165 trait_item!( |
174 "This is only as trustworthy as the application calling PAM." | 166 /// Sets the identity of the remote user logging in. |
175 "Also see [`remote_host`](Self::remote_host)." | 167 /// |
176 ); | 168 /// This is usually set by the application before making calls |
177 trait_item!( | 169 /// into a PAM session. (TODO: check this!) |
178 set = set_remote_user, | 170 set = set_remote_user, |
179 item = "PAM_RUSER", | 171 item = "PAM_RUSER" |
180 "Sets the identity of the remote user logging in." | 172 ); |
181 "" | 173 |
182 "This is usually set by the application before making calls " | 174 trait_item!( |
183 "into a PAM session. (TODO: check this!)" | 175 /// If set, the remote location where the user is coming from. |
184 ); | 176 /// |
185 | 177 /// This is only as trustworthy as the application calling PAM. |
186 trait_item!( | 178 /// This can be combined with [`Self::remote_user`] to identify |
179 /// the account the user is attempting to log in from, | |
180 /// with `remote_user@remote_host`. | |
181 /// | |
182 /// If unset, "it is unclear where the authentication request | |
183 /// is originating from." | |
187 get = remote_host, | 184 get = remote_host, |
188 item = "PAM_RHOST", | 185 item = "PAM_RHOST" |
189 "If set, the remote location where the user is coming from." | 186 ); |
190 "" | 187 trait_item!( |
191 "This is only as trustworthy as the application calling PAM. " | 188 /// Sets the location where the user is coming from. |
192 "This can be combined with [`Self::remote_user`] to identify " | 189 /// |
193 "the account the user is attempting to log in from, " | 190 /// This is usually set by the application before making calls |
194 "with `remote_user@remote_host`." | 191 /// into a PAM session. (TODO: check this!) |
195 "" | |
196 "If unset, \"it is unclear where the authentication request " | |
197 "is originating from.\"" | |
198 ); | |
199 trait_item!( | |
200 set = set_remote_host, | 192 set = set_remote_host, |
201 item = "PAM_RHOST", | 193 item = "PAM_RHOST", |
202 see = Self::remote_host, | 194 see = Self::remote_host |
203 "Sets the location where the user is coming from." | 195 ); |
204 "" | 196 |
205 "This is usually set by the application before making calls " | 197 trait_item!( |
206 "into a PAM session. (TODO: check this!)" | 198 /// Gets the user's authentication token (e.g., password). |
207 ); | 199 /// |
208 | 200 /// This is usually set automatically when |
209 trait_item!( | 201 /// [`get_authtok`](PamHandleModule::get_authtok) is called, |
202 /// but can be manually set. | |
210 set = set_authtok_item, | 203 set = set_authtok_item, |
211 item = "PAM_AUTHTOK", | 204 item = "PAM_AUTHTOK", |
212 see = PamModuleOnly::authtok_item, | 205 see = PamHandleModule::authtok_item |
213 "Gets the user's authentication token (e.g., password)." | 206 ); |
214 "" | 207 |
215 "This is usually set automatically when " | 208 trait_item!( |
216 "[`get_authtok`](PamModuleOnly::get_authtok) is called, " | 209 /// Sets the user's "old authentication token" when changing passwords. |
217 "but can be manually set." | 210 // |
218 ); | 211 /// This is usually set automatically by PAM. |
219 | |
220 trait_item!( | |
221 set = set_old_authtok_item, | 212 set = set_old_authtok_item, |
222 item = "PAM_OLDAUTHTOK", | 213 item = "PAM_OLDAUTHTOK", |
223 see = PamModuleOnly::old_authtok_item, | 214 see = PamHandleModule::old_authtok_item |
224 "Sets the user's \"old authentication token\" when changing passwords." | |
225 "" | |
226 "This is usually set automatically by PAM." | |
227 ); | 215 ); |
228 } | 216 } |
229 | 217 |
230 /// Functionality of a PAM handle that can be expected by a PAM application. | 218 /// Functionality of a PAM handle that can be expected by a PAM application. |
231 /// | 219 /// |
232 /// If you are not writing a PAM client application (e.g., you are writing | 220 /// If you are not writing a PAM client application (e.g., you are writing |
233 /// a module), you should not use the functionality exposed by this trait. | 221 /// a module), you should not use the functionality exposed by this trait. |
234 /// | 222 /// |
235 /// Like [`PamShared`], this is intended to allow creating mock implementations | 223 /// Like [`PamShared`], this is intended to allow creating mock implementations |
236 /// of PAM for testing PAM applications. | 224 /// of PAM for testing PAM applications. |
237 pub trait PamApplicationOnly { | 225 pub trait PamHandleApplication: PamShared { |
238 /// Closes the PAM session on an owned PAM handle. | 226 // reserved! |
239 /// | |
240 /// This should be called with the result of the application's last call | |
241 /// into PAM services. Since this is only applicable to *owned* PAM handles, | |
242 /// a PAM module should never call this (and it will never be handed | |
243 /// an owned `PamHandle` that it can `close`). | |
244 /// | |
245 /// See the [`pam_end` manual page][man] for more information. | |
246 /// | |
247 /// ```no_run | |
248 /// # use nonstick::handle::PamApplicationOnly; | |
249 /// # use std::error::Error; | |
250 /// # fn _doc(handle: impl PamApplicationOnly, auth_result: nonstick::Result<()>) -> Result<(), Box<dyn Error>> { | |
251 /// // Earlier: authentication was performed and the result was stored | |
252 /// // into auth_result. | |
253 /// handle.close(auth_result)?; | |
254 /// # Ok(()) | |
255 /// # } | |
256 /// ``` | |
257 /// | |
258 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_end.3.html | |
259 fn close(self, status: Result<()>) -> Result<()>; | |
260 } | 227 } |
261 | 228 |
262 /// Functionality of a PAM handle that can be expected by a PAM module. | 229 /// Functionality of a PAM handle that can be expected by a PAM module. |
263 /// | 230 /// |
264 /// If you are not writing a PAM module (e.g., you are writing an application), | 231 /// If you are not writing a PAM module (e.g., you are writing an application), |
265 /// you should not use any of the functionality exposed by this trait. | 232 /// you should not use any of the functionality exposed by this trait. |
266 /// | 233 /// |
267 /// Like [`PamShared`], this is intended to allow creating mock implementations | 234 /// Like [`PamShared`], this is intended to allow creating mock implementations |
268 /// of PAM for testing PAM modules. | 235 /// of PAM for testing PAM modules. |
269 pub trait PamModuleOnly: Conversation { | 236 pub trait PamHandleModule: Conversation + PamShared { |
270 /// Retrieves the authentication token from the user. | 237 /// Retrieves the authentication token from the user. |
271 /// | 238 /// |
272 /// This should only be used by *authentication* and *password-change* | 239 /// This should only be used by *authentication* and *password-change* |
273 /// PAM modules. | 240 /// PAM modules. |
274 /// | 241 /// |
276 /// or [`pam_get_item` in the Module Writer's Guide][mwg]. | 243 /// or [`pam_get_item` in the Module Writer's Guide][mwg]. |
277 /// | 244 /// |
278 /// # Example | 245 /// # Example |
279 /// | 246 /// |
280 /// ```no_run | 247 /// ```no_run |
281 /// # use nonstick::handle::PamModuleOnly; | 248 /// # use nonstick::handle::PamHandleModule; |
282 /// # fn _doc(handle: &mut impl PamModuleOnly) -> Result<(), Box<dyn std::error::Error>> { | 249 /// # fn _doc(handle: &mut impl PamHandleModule) -> Result<(), Box<dyn std::error::Error>> { |
283 /// // Get the user's password using the default prompt. | 250 /// // Get the user's password using the default prompt. |
284 /// let pass = handle.get_authtok(None)?; | 251 /// let pass = handle.get_authtok(None)?; |
285 /// // Get the user's password using a custom prompt. | 252 /// // Get the user's password using a custom prompt. |
286 /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; | 253 /// let pass = handle.get_authtok(Some("Reveal your secrets!"))?; |
287 /// Ok(()) | 254 /// Ok(()) |
291 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html | 258 /// [man]: https://www.man7.org/linux/man-pages/man3/pam_get_authtok.3.html |
292 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item | 259 /// [mwg]: https://www.chiark.greenend.org.uk/doc/libpam-doc/html/mwg-expected-by-module-item.html#mwg-pam_get_item |
293 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str>; | 260 fn get_authtok(&mut self, prompt: Option<&str>) -> Result<&str>; |
294 | 261 |
295 trait_item!( | 262 trait_item!( |
263 /// Gets the user's authentication token (e.g., password). | |
264 /// | |
265 /// This is normally set automatically by PAM when calling | |
266 /// [`get_authtok`](Self::get_authtok), but can be set explicitly. | |
267 /// | |
268 /// Like `get_authtok`, this should only ever be called | |
269 /// by *authentication* and *password-change* PAM modules. | |
296 get = authtok_item, | 270 get = authtok_item, |
297 item = "PAM_AUTHTOK", | 271 item = "PAM_AUTHTOK", |
298 see = Self::get_authtok, | 272 see = Self::get_authtok |
299 "Gets the user's authentication token (e.g., password)." | 273 ); |
300 "" | 274 |
301 "This is normally set automatically by PAM when calling " | 275 trait_item!( |
302 "[`get_authtok`](Self::get_authtok), but can be set explicitly." | 276 /// Gets the user's old authentication token when changing passwords. |
303 "" | 277 /// |
304 "Like `get_authtok`, this should only ever be called " | 278 /// This should only ever be called by *password-change* PAM modules. |
305 "by *authentication* and *password-change* PAM modules." | |
306 ); | |
307 | |
308 trait_item!( | |
309 get = old_authtok_item, | 279 get = old_authtok_item, |
310 item = "PAM_OLDAUTHTOK", | 280 item = "PAM_OLDAUTHTOK", |
311 see = PamShared::set_old_authtok_item, | 281 see = PamShared::set_old_authtok_item |
312 "Gets the user's old authentication token when changing passwords." | |
313 "" | |
314 "This should only ever be called by *password-change* PAM modules." | |
315 ); | 282 ); |
316 | 283 |
317 /* | 284 /* |
318 TODO: Re-enable this at some point. | 285 TODO: Re-enable this at some point. |
319 /// Gets some pointer, identified by `key`, that has been set previously | 286 /// Gets some pointer, identified by `key`, that has been set previously |