From d233d138a356fb65731bcf4b63814d08fbc19188 Mon Sep 17 00:00:00 2001 From: Dominik George <nik@naturalnet.de> Date: Wed, 5 May 2021 21:43:01 +0200 Subject: [PATCH] [PAM] Add syslog output and debug functionality --- Cargo.toml | 2 ++ src/config.rs | 4 +-- src/lib.rs | 2 ++ src/logging.rs | 30 +++++++++++++++++ src/pam.rs | 90 +++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 src/logging.rs diff --git a/Cargo.toml b/Cargo.toml index 0553594..0cbfcc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ crate-type = [ "cdylib" ] pamsm = { version = "^0.4.2", features = ["libpam"] } oauth2 = "^4.0.0" config = "^0.11.0" +log = "^0.4.11" +syslog = "^5.0.0" [profile.release] opt-level = 'z' diff --git a/src/config.rs b/src/config.rs index 66cf6b2..05264bc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -42,10 +42,10 @@ pub fn get_config(conf_args: config::Config) -> config::Config { return conf; } -pub fn argv_to_config(argv: Vec<String>) -> config::Config { +pub fn argv_to_config(argv: &Vec<String>) -> config::Config { let mut conf = config::Config::default(); - for arg in &argv { + for arg in argv { if arg.contains("=") { let split: Vec<&str> = arg.split("=").collect(); conf.set(split[0], split[1]); diff --git a/src/lib.rs b/src/lib.rs index 04823d1..f81a45d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ #[macro_use] extern crate pamsm; +#[macro_use] extern crate log; +mod logging; mod config; mod pam; diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..f34150b --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,30 @@ +/* Copyright 2021 Nicolas Goy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use log::{LevelFilter}; +use syslog::{BasicLogger, Facility, Formatter3164}; + +pub fn setup_log(log_level: LevelFilter) { + let formatter = Formatter3164 { + facility: Facility::LOG_AUTHPRIV, + hostname: None, + process: "nss_pam_oidc".into(), + pid: 0, + }; + let logger = syslog::unix(formatter).expect("could not connect to syslog"); + log::set_boxed_logger(Box::new(BasicLogger::new(logger))) + .map(|()| log::set_max_level(LevelFilter::Debug)); + log::set_max_level(log_level); +} diff --git a/src/pam.rs b/src/pam.rs index 24ca4e2..cb51a94 100644 --- a/src/pam.rs +++ b/src/pam.rs @@ -19,6 +19,8 @@ use crate::config::{ }; use config::Config; +use crate::logging::setup_log; + use oauth2::{ AuthUrl, ClientId, @@ -38,8 +40,14 @@ use pamsm::{PamServiceModule, Pam, PamFlag, PamError, PamLibExt}; fn get_or_pam_error(config: &Config, key: &str) -> Result<String, PamError> { match config.get_str(key) { - Ok(v) => Ok(v), - _ => Err(PamError::SERVICE_ERR), + Ok(v) => { + debug!("Configuration key found: {} = {}", key, v); + return Ok(v); + }, + _ => { + error!("Configuration key not found: {}", key); + return Err(PamError::SERVICE_ERR); + }, } } @@ -48,11 +56,17 @@ fn do_legacy_auth(username: String, password: String, config: Config) -> Result< let client_secret = ClientSecret::new(get_or_pam_error(&config, "pam.client_secret")?); let auth_url = match AuthUrl::new(get_or_pam_error(&config, "pam.auth_url")?) { Ok(u) => u, - _ => return Err(PamError::SERVICE_ERR), + _ => { + error!("Could not parse authorization URL"); + return Err(PamError::SERVICE_ERR); + }, }; let token_url = match TokenUrl::new(get_or_pam_error(&config, "pam.token_url")?){ Ok(u) => u, - _ => return Err(PamError::SERVICE_ERR), + _ => { + error!("Could not parse token URL"); + return Err(PamError::SERVICE_ERR); + }, }; let scope = get_or_pam_error(&config, "pam.scope")?; @@ -65,41 +79,85 @@ fn do_legacy_auth(username: String, password: String, config: Config) -> Result< .add_scope(Scope::new(scope.to_string())) .request(http_client) { Ok(t) => Ok(t), - _ => Err(PamError::AUTHINFO_UNAVAIL), + Err(e) => { + error!("Error requesting grant: {}", e); + return Err(PamError::AUTHINFO_UNAVAIL); + }, } } +fn pam_sm_prepare(argv: &Vec<String>) -> Config { + let conf_args = argv_to_config(argv); + let conf = get_config(conf_args); + + let mut log_level = log::LevelFilter::Error; + if conf.get_bool("debug").unwrap_or_default() || conf.get_bool("pam.debug").unwrap_or_default() { + log_level = log::LevelFilter::Debug; + } + setup_log(log_level); + + return conf; +} + struct PamOidc; impl PamServiceModule for PamOidc { fn authenticate(pamh: Pam, _: PamFlag, argv: Vec<String>) -> PamError { - let conf_args = argv_to_config(argv); - let conf = get_config(conf_args); - + let conf = pam_sm_prepare(&argv); if conf.get_str("pam.flow").unwrap() == "password" { + debug!("Starting Resource Owner Password Credentials OAuth flow"); + let username = match pamh.get_user(None) { Ok(Some(u)) => match u.to_str() { Ok(u) => u, - _ => return PamError::CRED_INSUFFICIENT, + _ => { + error!("Could not convert user name to string, aborting"); + return PamError::CRED_INSUFFICIENT; + }, + }, + Ok(None) => { + error!("No username supplied"); + return PamError::CRED_INSUFFICIENT; + }, + Err(e) => { + error!("Error getting user name: {}", e); + return e; }, - Ok(None) => return PamError::CRED_INSUFFICIENT, - Err(e) => return e, }; + debug!("Successfully got user name"); + let password = match pamh.get_authtok(None) { Ok(Some(p)) => match p.to_str() { Ok(p) => p, - _ => return PamError::CRED_INSUFFICIENT, + _ => { + error!("Could not convert password to string"); + return PamError::CRED_INSUFFICIENT; + }, + }, + Ok(None) => { + error!("No password supplied"); + return PamError::CRED_INSUFFICIENT; + }, + Err(e) => { + error!("Error getting password: {}", e); + return e; }, - Ok(None) => return PamError::CRED_INSUFFICIENT, - Err(e) => return e, }; + debug!("Successfully got password"); match do_legacy_auth(username.to_string(), password.to_string(), conf) { - Ok(_) => return PamError::SUCCESS, - Err(e) => return e, + Ok(_) => { + info!("Authenticated {} using Resource Owner Password Grant", username); + return PamError::SUCCESS; + }, + Err(e) => { + error!("Error in legacy authentication: {}", e); + return e; + }, }; } + error!("Unknown flow for authentication"); return PamError::SERVICE_ERR; } } -- GitLab