This commit is contained in:
2022-12-30 20:55:14 +08:00
parent 5a9c09d673
commit 118d6a5a1d
53 changed files with 4720 additions and 1 deletions

32
pam/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "pam-sandboxed"
# When releasing to crates.io:
# - Update html_root_url in src/lib.rs
# - Update CHANGELOG.md.
# - Run: cargo readme > README.md
# - Create git tag pam-sandboxed-0.x.y
version = "0.2.0"
readme = "README.md"
documentation = "https://docs.rs/pam-sandboxed"
repository = "https://github.com/miquels/webdav-server-rs"
homepage = "https://github.com/miquels/webdav-server-rs/tree/master/pam"
authors = ["Miquel van Smoorenburg <mike@langeraar.net>"]
edition = "2018"
license = "Apache-2.0"
categories = ["authentication"]
[build-dependencies]
cc = "1.0.66"
[dependencies]
bincode = "1.3.1"
env_logger = "0.8.2"
futures = "0.3.12"
libc = "0.2.82"
log = "0.4.13"
serde = "1.0.120"
serde_derive = "1.0.120"
threadpool = "1.8.1"
tokio = { version = "1.0.2", features = ["io-util", "net", "rt"] }

49
pam/README.md Normal file
View File

@@ -0,0 +1,49 @@
# pam-sandboxed
### PAM authentication with the pam library running in a separate process.
The PAM client in this crate creates a future that resolves with the
PAM authentication result.
### HOW.
When initialized, the code fork()s and sets up a pipe-based communications
channel between the parent (pam-client) and the child (pam-server). All
the Pam work is then done on a threadpool in the child process.
### WHY.
Reasons for doing this instead of just calling libpam directly:
- Debian still comes with pam 1.8, which when calling setuid helpers
will first close all filedescriptors up to the rlimit. if
If that limit is high (millions) then it takes a looong while.
`RLIMIT_NOFILE` is reset to a reasonably low number in the child process.
- You might want to run the pam modules as a different user than
the main process
- There is code in libpam that might call setreuid(), and that is an
absolute non-starter in threaded code.
- Also, if you're mucking around with per-thread uid credentials on Linux by
calling the setresuid syscall directly, the pthread library code that
handles setuid() gets confused.
### EXAMPLE.
```rust
// call this once.
let mut pam = PamAuth::new(None).expect("failed to initialized PAM");
// now use `pam` as a handle to authenticate.
let fut = pam.auth("other", "user", "pass", None)
.then(|res| {
println!("pam auth result: {:?}", res);
res
});
tokio::spawn(fut.map_err(|_| ()));
```
### Copyright and License.
* © 2018, 2019 XS4ALL Internet bv
* © 2018, 2019 Miquel van Smoorenburg
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)

11
pam/README.tpl Normal file
View File

@@ -0,0 +1,11 @@
# {{crate}}
{{readme}}
### Copyright and License.
* © 2018, 2019 XS4ALL Internet bv
* © 2018, 2019 Miquel van Smoorenburg
* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0)

11
pam/TODO.md Normal file
View File

@@ -0,0 +1,11 @@
## PAM crate TODO items
- check all panics. If the server-side panics - that's OK, the client-side
will just return errors.
- client side, when server has gone away:
- panic ?
- start returning errors ?
- try to restart the server ?

6
pam/build.rs Normal file
View File

@@ -0,0 +1,6 @@
extern crate cc;
fn main() {
println!("cargo:rustc-link-lib=pam");
cc::Build::new().file("src/pam.c").compile("rpam"); // outputs `librpam.a`
}

92
pam/src/bin/main.rs Normal file
View File

@@ -0,0 +1,92 @@
use std::io::{self, Write};
use env_logger;
use pam_sandboxed::PamAuth;
fn prompt(s: &str) -> io::Result<String> {
print!("{}", s);
io::stdout().flush()?;
let mut input = String::new();
io::stdin().read_line(&mut input)?;
Ok(input.trim().to_string())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let name = prompt("What's your login? ")?;
let pass = prompt("What's your password? ")?;
let mut pamauth = PamAuth::new(None)?;
let rt = tokio::runtime::Builder::new_current_thread()
.enable_io()
.build()?;
rt.block_on(async move {
match pamauth.auth("other", &name, &pass, None).await {
Ok(res) => println!("pam.auth returned Ok({:?})", res),
Err(e) => println!("pam.auth returned error: {}", e),
}
Ok(())
})
}
// I've put the tests here in bin/main.rs instead of in lib.rs, because "cargo test"
// for the library links the tests without -lpam, so it fails. The price we pay
// for that is a dynamic test-mode setting in the library, instead of compile-time.
#[cfg(test)]
mod tests {
use pam_sandboxed::{test_mode, PamAuth, PamError};
use tokio;
const TEST_STR: &str = "xyzzy-test-test";
#[test]
fn test_auth() {
test_mode(true);
let mut pam = PamAuth::new(None).unwrap();
let mut rt = tokio::runtime::Runtime::new().unwrap();
let res = rt.block_on(async {
let mut pam2 = pam.clone();
if let Err(e) = pam.auth(TEST_STR, "test", "foo", Some(TEST_STR)).await {
eprintln!("auth(test) failed: {:?}", e);
return Err(e);
}
if let Ok(_) = pam2.auth(TEST_STR, "unknown", "bar", Some(TEST_STR)).await {
eprintln!("auth(unknown) succeeded, should have failed");
return Err(PamError::unknown());
}
Ok(())
});
assert!(res.is_ok());
}
#[test]
fn test_many() {
test_mode(true);
let pam = PamAuth::new(None).unwrap();
let mut rt = tokio::runtime::Runtime::new().unwrap();
let mut handles = Vec::new();
rt.block_on(async move {
for i in 1u32..=1000 {
let mut pam = pam.clone();
let handle = tokio::spawn(async move {
if let Err(e) = pam.auth(TEST_STR, "test", "bar", Some(TEST_STR)).await {
panic!("auth(test) failed at iteration {}: {:?}", i, e);
}
});
handles.push(handle);
}
for handle in handles.drain(..) {
let _ = handle.await;
}
});
}
}

64
pam/src/lib.rs Normal file
View File

@@ -0,0 +1,64 @@
#![doc(html_root_url = "https://docs.rs/pam-sandboxed/0.2.0")]
//! ## PAM authentication with the pam library running in a separate process.
//!
//! The PAM client in this crate creates a future that resolves with the
//! PAM authentication result.
//!
//! ## HOW.
//!
//! When initialized, the code fork()s and sets up a pipe-based communications
//! channel between the parent (pam-client) and the child (pam-server). All
//! the Pam work is then done on a threadpool in the child process.
//!
//! ## WHY.
//!
//! Reasons for doing this instead of just calling libpam directly:
//!
//! - Debian still comes with pam 1.8, which when calling setuid helpers
//! will first close all filedescriptors up to the rlimit. if
//! If that limit is high (millions) then it takes a looong while.
//! `RLIMIT_NOFILE` is reset to a reasonably low number in the child process.
//! - You might want to run the pam modules as a different user than
//! the main process
//! - There is code in libpam that might call setreuid(), and that is an
//! absolute non-starter in threaded code.
//! - Also, if you're mucking around with per-thread uid credentials on Linux by
//! calling the setresuid syscall directly, the pthread library code that
//! handles setuid() gets confused.
//!
//! ## EXAMPLE.
//! ```
//! use pam_sandboxed::PamAuth;
//!
//! fn main() {
//! // call this once, early.
//! let mut pam = PamAuth::new(None).expect("failed to initialized PAM");
//!
//! let mut rt = tokio::runtime::Runtime::new().expect("failed to initialize tokio runtime");
//! rt.block_on(async move {
//! let res = pam.auth("other", "user", "pass", None).await;
//! println!("pam auth result: {:?}", res);
//! });
//! }
//! ```
#[macro_use]
extern crate log;
#[macro_use]
extern crate serde_derive;
mod pam;
mod pamclient;
mod pamserver;
use std::sync::atomic::Ordering;
pub use crate::pam::PamError;
pub use crate::pamclient::PamAuth;
// See bin/main.rs, mod tests.
#[doc(hidden)]
pub fn test_mode(enabled: bool) {
use crate::pam::TEST_MODE;
let getal = if enabled { 1 } else { 0 };
TEST_MODE.store(getal, Ordering::SeqCst);
}

82
pam/src/pam.c Normal file
View File

@@ -0,0 +1,82 @@
#include <security/pam_appl.h>
#include <sys/resource.h>
#include <string.h>
#include <stdlib.h>
struct creds {
char *user;
char *password;
};
static void add_reply(struct pam_response **reply, int count, char *txt)
{
*reply = realloc(*reply, (count + 1) * sizeof(struct pam_response));
(*reply)[count].resp_retcode = 0;
(*reply)[count].resp = strdup(txt ? txt: "");
}
static int c_pam_conv(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata)
{
struct pam_response *reply = NULL;
struct creds *creds = (struct creds *)appdata;
int replies = 0;
int count;
for (count = 0; count < num_msg; count++) {
switch (msg[count]->msg_style) {
case PAM_PROMPT_ECHO_ON:
add_reply(&reply, replies++, creds->user);
break;
case PAM_PROMPT_ECHO_OFF:
add_reply(&reply, replies++, creds->password);
break;
case PAM_TEXT_INFO:
break;
case PAM_ERROR_MSG:
default:
if (reply != NULL)
free(reply);
return PAM_CONV_ERR;
}
}
*resp = reply;
return PAM_SUCCESS;
}
int c_pam_auth(char *service, char *user, char *pass, char *remip)
{
struct creds creds = {
user,
pass,
};
struct pam_conv conv = {
c_pam_conv,
&creds,
};
pam_handle_t *pamh = NULL;
int ret = pam_start(service, user, &conv, &pamh);
if (ret != PAM_SUCCESS)
return ret;
if (ret == PAM_SUCCESS && remip && remip[0])
ret = pam_set_item(pamh, PAM_RHOST, remip);
if (ret == PAM_SUCCESS)
ret = pam_authenticate(pamh, 0);
pam_end(pamh, 0);
return ret;
}
void c_pam_lower_rlimits()
{
struct rlimit rlim;
if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
rlim_t l = rlim.rlim_cur;
if (l > 256)
l = 256;
rlim.rlim_cur = l;
rlim.rlim_max = l;
setrlimit(RLIMIT_NOFILE, &rlim);
}
}

97
pam/src/pam.rs Normal file
View File

@@ -0,0 +1,97 @@
use std::error::Error;
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int, c_void};
use std::sync::atomic::{AtomicUsize, Ordering};
extern "C" {
fn c_pam_auth(
service: *const c_char,
user: *const c_char,
pass: *const c_char,
remip: *const c_char,
) -> c_int;
fn _c_pam_return_value(index: c_int) -> c_int;
fn pam_strerror(pamh: *const c_void, errnum: c_int) -> *const c_char;
fn c_pam_lower_rlimits();
}
pub(crate) const ERR_NUL_BYTE: i32 = 414243;
pub(crate) const ERR_SEND_TO_SERVER: i32 = 414244;
pub(crate) const ERR_RECV_FROM_SERVER: i32 = 414245;
pub(crate) static TEST_MODE: AtomicUsize = AtomicUsize::new(0);
/// Error returned if authentication fails.
///
/// It's best not to try to interpret this, and handle all errors
/// as "authentication failed".
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PamError(pub(crate) i32);
impl PamError {
#[doc(hidden)]
pub fn unknown() -> PamError {
PamError(13)
}
}
impl std::fmt::Display for PamError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 {
ERR_NUL_BYTE => write!(f, "embedded 0 byte in string"),
ERR_SEND_TO_SERVER => write!(f, "error sending request to server"),
ERR_RECV_FROM_SERVER => write!(f, "error receiving response from server"),
_ => {
let errnum = self.0 as c_int;
let nullptr: *const c_void = std::ptr::null();
let errstr = unsafe { CStr::from_ptr(pam_strerror(nullptr, errnum)).to_string_lossy() };
f.write_str(&format!("PAM error: {}", errstr))
},
}
}
}
impl Error for PamError {
fn description(&self) -> &str {
"PAM authentication error"
}
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl From<std::ffi::NulError> for PamError {
fn from(_e: std::ffi::NulError) -> Self {
PamError(ERR_NUL_BYTE)
}
}
pub(crate) fn pam_auth(service: &str, user: &str, pass: &str, remip: &str) -> Result<(), PamError> {
if TEST_MODE.load(Ordering::SeqCst) > 0 {
return if user == "test" { Ok(()) } else { Err(PamError(1)) };
}
let c_service = CString::new(service)?;
let c_user = CString::new(user)?;
let c_pass = CString::new(pass)?;
let c_remip = CString::new(remip)?;
let ret = unsafe {
c_pam_auth(
c_service.as_ptr(),
c_user.as_ptr(),
c_pass.as_ptr(),
c_remip.as_ptr(),
)
};
match ret {
0 => Ok(()),
errnum => Err(PamError(errnum)),
}
}
pub(crate) fn pam_lower_rlimits() {
unsafe {
c_pam_lower_rlimits();
}
}

265
pam/src/pamclient.rs Normal file
View File

@@ -0,0 +1,265 @@
// Client part, that is, the part that runs in the local process.
//
// All the futures based code lives here.
//
use std::cell::RefCell;
use std::collections::HashMap;
use std::io;
use std::os::unix::net::UnixStream as StdUnixStream;
use std::sync::{Arc, Mutex, Once};
use futures::channel::{mpsc, oneshot};
use futures::join;
use futures::{sink::SinkExt, stream::StreamExt};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::unix::ReadHalf as UnixReadHalf;
use tokio::net::unix::WriteHalf as UnixWriteHalf;
use tokio::net::UnixStream;
use crate::pam::{PamError, ERR_RECV_FROM_SERVER, ERR_SEND_TO_SERVER};
use crate::pamserver::{PamResponse, PamServer};
// Request to be sent to the server process.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct PamRequest {
pub id: u64,
pub user: String,
pub pass: String,
pub service: String,
pub remip: Option<String>,
}
// sent over request channel to PamAuthTask.
struct PamRequest1 {
req: PamRequest,
resp_chan: oneshot::Sender<Result<(), PamError>>,
}
/// Pam authenticator.
#[derive(Clone)]
pub struct PamAuth {
inner: Arc<PamAuthInner>,
}
struct PamAuthInner {
once: Once,
serversock: RefCell<Option<StdUnixStream>>,
req_chan: RefCell<Option<mpsc::Sender<PamRequest1>>>,
}
// Mutation of PamAuthInner only happens once,
// protected by atomic Once, so this is safe.
unsafe impl Sync for PamAuthInner {}
unsafe impl Send for PamAuthInner {}
impl PamAuth {
/// Create a new PAM authenticator. This will start a new PAM server process
/// in the background, and it will contain a new PAM coordination task that
/// will be lazily spawned the first time auth() is called.
///
/// Note that it is important to call this very early in main(), before any
/// threads or runtimes have started.
///
/// ```no_run
/// use pam_sandboxed::PamAuth;
///
/// fn main() -> Result<(), Box<std::error::Error>> {
/// // get pam authentication handle.
/// let mut pam = PamAuth::new(None)?;
///
/// // now start tokio runtime and use handle.
/// let mut rt = tokio::runtime::Runtime::new()?;
/// rt.block_on(async move {
/// let res = pam.auth("other", "user", "pass", None).await;
/// println!("pam auth result: {:?}", res);
/// });
/// Ok(())
/// }
/// ```
///
pub fn new(num_threads: Option<usize>) -> Result<PamAuth, io::Error> {
// spawn the server process.
let serversock = PamServer::start(num_threads)?;
let inner = PamAuthInner {
once: Once::new(),
req_chan: RefCell::new(None),
serversock: RefCell::new(Some(serversock)),
};
Ok(PamAuth {
inner: Arc::new(inner),
})
}
/// Authenticate via pam and return the result.
///
/// - `service`: PAM service to use - usually "other".
/// - `username`: account username
/// - `password`: account password
/// - `remoteip`: if this is a networking service, the remote IP address of the client.
pub async fn auth(
&mut self,
service: &str,
username: &str,
password: &str,
remoteip: Option<&str>,
) -> Result<(), PamError>
{
// If we haven't started the background task yet, do it now.
// That also initializes req_chan.
let inner = &self.inner;
inner.once.call_once(|| {
// These should not ever panic on unwrap().
let serversock = inner.serversock.borrow_mut().take().unwrap();
inner
.req_chan
.replace(Some(PamAuthTask::start(serversock).unwrap()));
});
// create request to be sent to the server.
let req = PamRequest {
id: 0,
user: username.to_string(),
pass: password.to_string(),
service: service.to_string(),
remip: remoteip.map(|s| s.to_string()),
};
// add a one-shot channel for the response.
let (tx, rx) = oneshot::channel::<Result<(), PamError>>();
// put it all together and send it.
let req1 = PamRequest1 {
req: req,
resp_chan: tx,
};
let mut authtask_chan = inner.req_chan.borrow().as_ref().unwrap().clone();
authtask_chan
.send(req1)
.await
.map_err(|_| PamError(ERR_SEND_TO_SERVER))?;
// wait for the response.
match rx.await {
Ok(res) => res,
Err(_) => Err(PamError(ERR_RECV_FROM_SERVER)),
}
}
}
// Shared data for the PamAuthTask tasks.
struct PamAuthTask {
// clients waiting for a response.
waiters: Mutex<HashMap<u64, oneshot::Sender<Result<(), PamError>>>>,
}
impl PamAuthTask {
// Start the server process. Then return a handle to send requests on.
fn start(serversock: StdUnixStream) -> io::Result<mpsc::Sender<PamRequest1>> {
let mut serversock = UnixStream::from_std(serversock)?;
// create a request channel.
let (req_tx, req_rx) = mpsc::channel::<PamRequest1>(0);
// shared state between request and response task.
let this = PamAuthTask {
waiters: Mutex::new(HashMap::new()),
};
debug!("PamAuthTask: spawning task on runtime");
tokio::spawn(async move {
// split serversock into send/receive halves.
let (srx, stx) = serversock.split();
join!(this.handle_request(req_rx, stx), this.handle_response(srx));
});
Ok(req_tx)
}
async fn handle_request(&self, mut req_rx: mpsc::Receiver<PamRequest1>, mut stx: UnixWriteHalf<'_>) {
let mut id: u64 = 0;
loop {
// receive next request.
let PamRequest1 { mut req, resp_chan } = match req_rx.next().await {
Some(r1) => r1,
None => {
// PamAuth handle was dropped. Ask server to exit.
let data = [0u8; 2];
let _ = stx.write_all(&data).await;
return;
},
};
// store the response channel.
req.id = id;
id += 1;
{
let mut waiters = self.waiters.lock().unwrap();
waiters.insert(req.id, resp_chan);
}
// serialize data and send.
let mut data: Vec<u8> = match bincode::serialize(&req) {
Ok(data) => data,
Err(e) => {
// this panic can never happen at runtime.
panic!("PamClient: serializing data: {:?}", e);
},
};
if data.len() > 65533 {
// this panic can never happen at runtime.
panic!("PamClient: serialized data > 65533 bytes");
}
let l1 = ((data.len() >> 8) & 0xff) as u8;
let l2 = (data.len() & 0xff) as u8;
data.insert(0, l1);
data.insert(1, l2);
if let Err(e) = stx.write_all(&data).await {
// this can happen if the server has gone away.
// in which case, handle_response() will exit as well.
error!("PamClient: FATAL: writing data to server: {:?}", e);
return;
}
}
}
async fn handle_response(&self, mut srx: UnixReadHalf<'_>) {
loop {
// read size header.
let mut buf = [0u8; 2];
if let Err(_) = srx.read_exact(&mut buf).await {
error!("PamClient: FATAL: short read, server gone away?!");
return;
}
let sz = ((buf[0] as usize) << 8) + (buf[1] as usize);
// read response data.
let mut data = Vec::with_capacity(sz);
data.resize(sz, 0u8);
if let Err(_) = srx.read_exact(&mut data[..]).await {
error!("PamClient: FATAL: short read, server gone away?!");
return;
}
// deserialize.
let resp: PamResponse = match bincode::deserialize(&data[..]) {
Ok(req) => req,
Err(_) => {
// this panic can never happen at runtime.
panic!("PamCLient: error deserializing response");
},
};
// and send response to waiting requester.
let resp_chan = {
let mut waiters = self.waiters.lock().unwrap();
waiters.remove(&resp.id)
};
if let Some(resp_chan) = resp_chan {
let _ = resp_chan.send(resp.result);
}
}
}
}

169
pam/src/pamserver.rs Normal file
View File

@@ -0,0 +1,169 @@
// Server part - the code here is fork()ed off and lives in its own
// process. We communicate with it through a unix stream socket.
//
// This is all old-fashioned blocking and thread-based code.
//
use std::io::{self, Read, Write};
use std::os::unix::io::AsRawFd;
use std::os::unix::net::UnixStream as StdUnixStream;
use std::sync::{Arc, Mutex};
use bincode::{deserialize, serialize};
use libc;
use crate::pam::{pam_auth, pam_lower_rlimits, PamError};
use crate::pamclient::PamRequest;
// Response back from the server process.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct PamResponse {
pub id: u64,
pub result: Result<(), PamError>,
}
// server side.
pub(crate) struct PamServer {
rx_socket: StdUnixStream,
tx_socket: Arc<Mutex<StdUnixStream>>,
}
impl PamServer {
// fork and start the server, return the stream socket for communication.
pub(crate) fn start(num_threads: Option<usize>) -> Result<StdUnixStream, io::Error> {
// Create a unix socketpair for communication.
let (sock1, sock2) = StdUnixStream::pair()?;
let sock3 = sock2.try_clone()?;
let handle = std::thread::spawn(move || {
// fork server.
let pid = unsafe { libc::fork() };
if pid < 0 {
return Err(io::Error::last_os_error());
}
if pid == 0 {
// first, close all filedescriptors (well, all..)
for fdno in 3..8192 {
if fdno != sock2.as_raw_fd() && fdno != sock3.as_raw_fd() {
unsafe {
libc::close(fdno);
}
}
}
let mut server = PamServer {
rx_socket: sock2,
tx_socket: Arc::new(Mutex::new(sock3)),
};
pam_lower_rlimits();
trace!("PamServer: child: starting server");
server.serve(num_threads.unwrap_or(8));
drop(server);
std::process::exit(0);
}
Ok(())
});
handle.join().unwrap()?;
trace!("PamServer: parent: started server");
Ok(sock1)
}
// serve requests.
fn serve(&mut self, num_threads: usize) {
// create a threadpool, then serve connections via the threadpool.
let pool = threadpool::ThreadPool::new(num_threads);
// process incoming connections.
loop {
// read length.
let mut buf = [0u8; 2];
let res = self.rx_socket.read_exact(&mut buf);
if let Err(e) = res {
if e.kind() == std::io::ErrorKind::UnexpectedEof {
// parent probably exited - not an error.
trace!("PamServer::serve: EOF reached on input");
break;
}
panic!("PamServer::serve: read socket: {}", e);
}
let sz = ((buf[0] as usize) << 8) + (buf[1] as usize);
if sz == 0 {
// size 0 packet indicates client wants to shut us down.
trace!("PamServer::serve: EOF packet on input");
break;
}
// read request data.
let mut data = Vec::with_capacity(sz);
data.resize(sz, 0u8);
let res = self.rx_socket.read_exact(&mut data);
if let Err(e) = res {
panic!("PamServer::serve: read socket: {}", e);
}
let req: PamRequest = match deserialize(&data[..]) {
Ok(req) => req,
Err(_) => panic!("PamServer::serve: error deserializing request"),
};
trace!(
"PamServer::serve: read request {:?} active threads: {} queued {}",
req,
pool.active_count(),
pool.queued_count()
);
// run request on pool.
let sock = self.tx_socket.clone();
pool.execute(move || {
if let Err(e) = pam_process(req, sock) {
panic!("PamServer::pam_process: error: {}", e);
}
});
let mut i = 0;
while pool.queued_count() > 2 * pool.max_count() {
if i == 399 {
debug!(
"PamServer::serve: pool busy! active {}, max {}, queued: {}",
pool.active_count(),
pool.max_count(),
pool.queued_count()
);
}
i += 1;
i = i % 400;
std::thread::sleep(std::time::Duration::from_millis(5));
}
}
pool.join();
trace!("PamServer::serve: exit.");
std::process::exit(0);
}
}
// Process one request. This is run on the threadpool.
fn pam_process(req: PamRequest, sock: Arc<Mutex<StdUnixStream>>) -> Result<(), io::Error> {
trace!("PamServer::pam_process: starting with request {:?}", req);
// authenticate.
let remip = req.remip.as_ref().map(|s| s.as_str()).unwrap_or("");
let res = PamResponse {
id: req.id,
result: pam_auth(&req.service, &req.user, &req.pass, remip),
};
// and send back result.
trace!("PamServer::pam_process: returning response {:?}", res);
let mut response: Vec<u8> = serialize(&res)
.map_err(|e| io::Error::new(io::ErrorKind::Other, format!("error serializing response: {}", e)))?;
let l1 = ((response.len() >> 8) & 0xff) as u8;
let l2 = (response.len() & 0xff) as u8;
response.insert(0, l1);
response.insert(1, l2);
match sock.lock().unwrap().write_all(&response) {
Err(e) => {
debug!("PamServer::pam_process: writing to response socket: {}", e);
Err(e)
},
Ok(..) => Ok(()),
}
}