/* Copyright 2021 Dominik George <nik@naturalnet.de> * * 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 crate::config::{ argv_to_config, get_config }; use config::Config; use crate::logging::setup_log; use oauth2::{ AuthUrl, ClientId, ClientSecret, RequestTokenError, ResourceOwnerPassword, ResourceOwnerUsername, Scope, TokenUrl }; use oauth2::basic::{ BasicClient, BasicTokenResponse }; use oauth2::reqwest; use oauth2::reqwest::http_client; 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) => { debug!("Configuration key found: {} = {}", key, v); return Ok(v); }, _ => { error!("Configuration key not found: {}", key); return Err(PamError::SERVICE_ERR); }, } } fn do_legacy_auth(username: String, password: String, config: Config) -> Result<BasicTokenResponse, PamError> { let client_id = ClientId::new(get_or_pam_error(&config, "pam.client_id")?); 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, _ => { 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, _ => { error!("Could not parse token URL"); return Err(PamError::SERVICE_ERR); }, }; let scope = get_or_pam_error(&config, "pam.scope")?; let client = BasicClient::new(client_id, Some(client_secret), auth_url, Some(token_url)); let result = client .exchange_password( &ResourceOwnerUsername::new(username), &ResourceOwnerPassword::new(password) ) .add_scope(Scope::new(scope.to_string())) .request(http_client); match result { Ok(t) => Ok(t), Err(e) => match e { RequestTokenError::Request(re) => match re { reqwest::Error::Reqwest(ree) => { if ree.is_status() { error!("Authentication failed for invalid grant: {}", ree); return Err(PamError::AUTH_ERR); } else { error!("Request error requesting token: {}", ree); return Err(PamError::AUTHINFO_UNAVAIL); } }, reqwest::Error::Http(he) => { error!("HTTP error requesting token: {}", he); return Err(PamError::AUTHINFO_UNAVAIL); }, reqwest::Error::Io(ioe) => { error!("IO error requesting token: {}", ioe); return Err(PamError::SYSTEM_ERR); }, _ => { error!("Unknown error: {}", re); return Err(PamError::SERVICE_ERR); }, }, RequestTokenError::ServerResponse(t) => { error!("Authorization server returned error: {}", t); return Err(PamError::AUTH_ERR); }, RequestTokenError::Parse(pe, _) => { error!("Error parsing response: {}", pe); return Err(PamError::SERVICE_ERR); }, _ => { error!("Unknown error: {}", e); return Err(PamError::SERVICE_ERR); }, }, } } 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 = 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, _ => { 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; }, }; debug!("Successfully got user name"); let password = match pamh.get_authtok(None) { Ok(Some(p)) => match p.to_str() { Ok(p) => p, _ => { 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; }, }; debug!("Successfully got password"); match do_legacy_auth(username.to_string(), password.to_string(), conf) { 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; } } pam_module!(PamOidc);