Skip to content

Commit

Permalink
Merge pull request #23 from z4yx/dev
Browse files Browse the repository at this point in the history
Add the support of variable substitution in options
  • Loading branch information
z4yx authored Sep 2, 2024
2 parents e3c8986 + b8573c8 commit 645d5ba
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 36 deletions.
31 changes: 30 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pam_rssh"
version = "1.1.0"
version = "1.2.0"
authors = ["Yuxiang Zhang"]
edition = "2018"

Expand All @@ -17,5 +17,6 @@ base64 = "^0.22.1"
openssl-sys = "^0.9"
openssl = "^0.10"
log = { version = "^0.4", features = ["std", "serde"] }
subst = "^0.3.0"
syslog = "^7.0"
pwd = "1"
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ cp target/release/libpam_rssh.so <pam module path>
```

## `pam module path`
- the module path is specific to certain distributions

The module path is specific to certain distributions

| OS | Destination |
| ------------ | ----------------------------------- |
Expand Down Expand Up @@ -74,7 +75,7 @@ The following arguments are supported:
- `loglevel=<off|error|warn|info|debug|trace>` Select the level of messages logged to syslog. Defaults to `warn`.
- `debug` Equivalent to `loglevel=debug`.
- `ssh_agent_addr=<IP:port or UNIX domain address>` The address of ssh-agent. Defaults to the value of `SSH_AUTH_SOCK` environment variable, which is set by ssh automatically.
- `auth_key_file=<Path to authorized_keys>` Public keys allowed for user authentication. Defaults to `$HOME/.ssh/authorized_keys`. Usually `$HOME` expands to `/home/<username>`.
- `auth_key_file=<Path to authorized_keys>` Public keys allowed for user authentication. Defaults to `<home>/.ssh/authorized_keys`. `<home>` is read from system configuration, usually it expands to `/home/<username>`.
- `authorized_keys_command=<Path to command>` A command to generate the authorized_keys. It takes a single argument, the username of the user being authenticated. The standard output of this command will be parsed as authorized_keys. The `auth_key_file` will be ignored if you specify this argument.
- `authorized_keys_command_user=<Username>` The `authorized_keys_command` will be run as the user specified here. If this argument is omitted, the `authorized_keys_command` will be run as the user being authenticated.

Expand All @@ -83,3 +84,21 @@ Arguments should be appended to the PAM rule. For example:
```
auth sufficient libpam_rssh.so debug authorized_keys_command=/usr/bin/sss_ssh_authorizedkeys authorized_keys_command_user=nobody
```

## Use Variables in Arguments

Certain variables can be used in arguments. Supported formats are `$var`, `${var}` and `${var:default value}`. For example:

```
auth sufficient libpam_rssh.so auth_key_file=/data/${user}.keys
```

Variables are mapped to PAM items. Currently the following variables are available:

- service: PAM_SERVICE. The service name (which identifies the PAM stack that will be used).
- user: PAM_USER. The username of the entity under whose identity service will be given.
- tty: PAM_TTY. The terminal name.
- rhost: PAM_RHOST. The requesting hostname.
- ruser: PAM_RUSER. The requesting entity.

For detailed description on PAM items, read man page pam_get_item(3).
8 changes: 8 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ pub enum RsshErr {
GetUidErr,
CmdExitErr(Option<i32>),
CmdOutputDecodeErr,
InvalidLogLvlErr,
OptNameErr(String),
OptValEmptyErr(String),
OptVarErr(String),
}

impl RsshErr {
Expand Down Expand Up @@ -47,6 +51,10 @@ impl Display for RsshErr {
RsshErr::GetUidErr => S!("Cannot get uid of specified user"),
RsshErr::CmdExitErr(code) => format!("Command exit code is {}", code.unwrap_or(-1)),
RsshErr::CmdOutputDecodeErr => S!("Failed to decode the output of command"),
RsshErr::InvalidLogLvlErr => S!("Invalid log level"),
RsshErr::OptNameErr(name) => format!("Unknown option name `{}`", name),
RsshErr::OptValEmptyErr(name) => format!("Value of option `{}` is empty", name),
RsshErr::OptVarErr(name) => format!("Failed to evaluate variables in option `{}`", name),
};
f.write_str(&msg)
}
Expand Down
81 changes: 49 additions & 32 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod error;
mod logger;
mod sign_verify;
mod ssh_agent_auth;
mod pam_items;

use log::*;
use pam::constants::{PamFlag, PamResultCode};
Expand Down Expand Up @@ -89,55 +90,71 @@ fn setup_logger() {
.map(|()| log::set_max_level(log::LevelFilter::Warn));
}

fn substitute_variables(kv: &Vec<&str>, variables: &pam_items::PamItemsMap) -> Result<String, ErrType> {
subst::substitute(kv[1], variables)
.or(Err(RsshErr::OptVarErr(kv[0].to_string()).into_ptr()))
}

fn non_empty_option_check(kv: &Vec<&str>) -> Result<(), ErrType> {
if kv.len() == 1 || kv[1].is_empty() {
return Err(RsshErr::OptValEmptyErr(kv[0].to_string()).into_ptr())
}
Ok(())
}

impl PamHooks for PamRssh {
fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
/* if (flags & pam::constants::PAM_SILENT) == 0 */
{
setup_logger();
}

let mut ssh_agent_addr = "";
let mut auth_key_file = "";
let mut authorized_keys_command = "";
let mut authorized_keys_command_user = "";
let pam_vars = pam_items::PamItemsMap::new(&pamh);
let mut ssh_agent_addr = String::new();
let mut auth_key_file = String::new();
let mut authorized_keys_command = String::new();
let mut authorized_keys_command_user = String::new();
for carg in args {
let kv: Vec<&str> = carg.to_str().unwrap_or("").splitn(2, '=').collect();
if kv.len() == 0 {
continue;
}
trace!("Parsing option {:?}", kv);
match kv[0] {
"loglevel" => {
if kv.len() > 1 {
let _ = log::Level::from_str(kv[1])
.and_then(|level| Ok(log::set_max_level(level.to_level_filter())));
let mut parse_options = || -> Result<(), ErrType> {
match kv[0] {
"loglevel" => {
non_empty_option_check(&kv)?;
match log::Level::from_str(kv[1]) {
Ok(level) => log::set_max_level(level.to_level_filter()),
Err(_) => { return Err(RsshErr::InvalidLogLvlErr.into_ptr()) }
}
}
}
"debug" => log::set_max_level(log::LevelFilter::Debug),
"ssh_agent_addr" => {
if kv.len() > 1 {
ssh_agent_addr = kv[1];
"debug" => log::set_max_level(log::LevelFilter::Debug),
"ssh_agent_addr" => {
non_empty_option_check(&kv)?;
ssh_agent_addr = substitute_variables(&kv, &pam_vars)?;
}
}
"auth_key_file" => {
if kv.len() > 1 {
auth_key_file = kv[1];
"auth_key_file" => {
non_empty_option_check(&kv)?;
auth_key_file = substitute_variables(&kv, &pam_vars)?;
}
}
"authorized_keys_command" => {
if kv.len() > 1 {
authorized_keys_command = kv[1];
"authorized_keys_command" => {
non_empty_option_check(&kv)?;
authorized_keys_command = substitute_variables(&kv, &pam_vars)?;
}
}
"authorized_keys_command_user" => {
if kv.len() > 1 {
authorized_keys_command_user = kv[1];
"authorized_keys_command_user" => {
non_empty_option_check(&kv)?;
authorized_keys_command_user = substitute_variables(&kv, &pam_vars)?;
}
_ => {
return Err(RsshErr::OptNameErr(kv[0].to_string()).into_ptr());
}
}
_ => {
error!("Unknown option `{}`", kv[0]);
return PamResultCode::PAM_SYSTEM_ERR;
}
Ok(())
};
if let Err(opt_err) = parse_options() {
error!("{}", opt_err);
return PamResultCode::PAM_SYSTEM_ERR;
}
}

Expand All @@ -146,7 +163,7 @@ impl PamHooks for PamRssh {
let agent_addr_os = std::env::var_os("SSH_AUTH_SOCK");
if let Some(a) = agent_addr_os {
addr_from_env = a;
ssh_agent_addr = addr_from_env.to_str().unwrap_or("");
ssh_agent_addr = addr_from_env.to_str().unwrap_or("").to_string();
}
debug!("SSH-Agent address: {}", ssh_agent_addr);
if ssh_agent_addr.is_empty() {
Expand All @@ -171,7 +188,7 @@ impl PamHooks for PamRssh {
}
};

let mut agent = ssh_agent_auth::AgentClient::new(ssh_agent_addr);
let mut agent = ssh_agent_auth::AgentClient::new(ssh_agent_addr.as_str());
let result = agent.list_identities().and_then(|client_keys| {
debug!("SSH-Agent reports {} keys", client_keys.len());
for (i, key) in client_keys.iter().enumerate() {
Expand Down
50 changes: 50 additions & 0 deletions src/pam_items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

use pam::constants::PamResultCode;
use pam::module::{PamHandle, PamResult};
use pam::items;
use subst::VariableMap;

pub struct PamItemsMap<'a> {
pam: &'a PamHandle,
}

impl<'a> VariableMap<'a> for PamItemsMap<'a> {
type Value = String;
fn get(&'a self, key: &str) -> Option<Self::Value> {
self.get_str_val(key).ok()
}
}

impl<'a> PamItemsMap<'a> {
pub fn new(pam_handle: &PamHandle) ->PamItemsMap {
PamItemsMap { pam: pam_handle }
}
fn get_str_val(&'a self, key: &str) -> PamResult<String> {
let str_val = match key {
"service" => {
let v = self.pam.get_item::<items::Service>()?.ok_or(PamResultCode::PAM_BUF_ERR)?;
v.to_str().or(Err(PamResultCode::PAM_BUF_ERR))?
}
"user" => {
let v = self.pam.get_item::<items::User>()?.ok_or(PamResultCode::PAM_BUF_ERR)?;
v.to_str().or(Err(PamResultCode::PAM_BUF_ERR))?
}
"tty" => {
let v = self.pam.get_item::<items::Tty>()?.ok_or(PamResultCode::PAM_BUF_ERR)?;
v.to_str().or(Err(PamResultCode::PAM_BUF_ERR))?
}
"rhost" => {
let v = self.pam.get_item::<items::RHost>()?.ok_or(PamResultCode::PAM_BUF_ERR)?;
v.to_str().or(Err(PamResultCode::PAM_BUF_ERR))?
}
"ruser" => {
let v = self.pam.get_item::<items::RUser>()?.ok_or(PamResultCode::PAM_BUF_ERR)?;
v.to_str().or(Err(PamResultCode::PAM_BUF_ERR))?
}
_ => {
return Err(PamResultCode::PAM_BAD_ITEM)
}
};
Ok(str_val.to_string())
}
}

0 comments on commit 645d5ba

Please sign in to comment.