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