/* ** Copyright (C) 2021-2023 KunoiSayami ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU Affero General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU Affero General Public License for more details. ** ** You should have received a copy of the GNU Affero General Public License ** along with this program. If not, see . */ mod authentication; mod database; mod datastructures; #[cfg(test)] mod test; use crate::datastructures::{Config, TestSuite}; use anyhow::Result; use clap::{Arg, ArgMatches, Command}; use log4rs::append::file::FileAppender; use log4rs::config::{Appender, Root}; use log4rs::encode::pattern::PatternEncoder; use std::env; async fn async_main(arg_matches: ArgMatches) -> Result { let cfg = if env::args().any(|x| x.eq("--test")) { Config::generate_test_config() } else { Config::new() }; match arg_matches.subcommand() { Some(("authenticate-cookie", matches)) => { if let Ok(should_pass) = authentication::cmd_authenticate_cookie(matches, cfg).await { if should_pass { return Ok(1); } } } Some(("authenticate-post", matches)) => { let stdin = std::io::stdin(); let input = stdin.lock(); let output = std::io::stdout(); let mut module = authentication::IOModule::new(input, output); module.cmd_authenticate_post(matches, cfg).await?; } Some(("body", matches)) => { authentication::cmd_body(matches, cfg).await; } Some(("user", matches)) => match matches.subcommand() { Some(("add", matches)) => { authentication::cmd_add_user(matches, cfg).await?; } Some(("del", matches)) => { authentication::cmd_delete_user(matches, cfg).await?; } Some(("list", _matches)) => { authentication::cmd_list_user(cfg).await?; } _ => {} }, Some(("database", matches)) => match matches.subcommand() { Some(("init", _matches)) => { authentication::cmd_init(cfg).await?; } Some(("upgrade", _matches)) => { authentication::cmd_upgrade_database(cfg).await?; } Some(("reset", matches)) => { authentication::cmd_reset_database(matches, cfg).await?; } _ => {} }, Some(("repo", matches)) => match matches.subcommand() { Some(("add", matches)) => { authentication::cmd_repo_user_control(matches, cfg, false).await? } Some(("del", matches)) => { authentication::cmd_repo_user_control(matches, cfg, true).await?; } Some(("list", matches)) => { authentication::cmd_list_repos_acl(matches, cfg).await?; } _ => {} }, _ => {} } Ok(0) } fn get_arg_matches(arguments: Option>) -> ArgMatches { // Sub-arguments for each command, see cgi defines. let sub_args = &[ Arg::new("http-cookie"), // 2 Arg::new("request-method"), Arg::new("query-string"), Arg::new("http-referer"), // 5 Arg::new("path-info"), Arg::new("http-host"), Arg::new("https"), Arg::new("repo"), Arg::new("page"), // 10 Arg::new("current-url"), Arg::new("login-url"), ]; let app = Command::new("Simple Authentication Filter for cgit") .version(env!("CARGO_PKG_VERSION")) .subcommand( Command::new("authenticate-cookie") .about("Processing authenticated cookie") .args(sub_args) .hide(true), ) .subcommand( Command::new("authenticate-post") .about("Processing posted username and password") .args(sub_args) .hide(true), ) .subcommand( Command::new("body") .about("Return the login form") .args(sub_args) .hide(true), ) .subcommand( Command::new("database") .about("Database rated commands") .subcommand( Command::new("init") .about("Init sqlite database") .display_order(0), ) .subcommand( Command::new("reset") .about("Reset database") .arg(Arg::new("confirm").long("confirm")) .display_order(0), ) .subcommand( Command::new("upgrade") .about("Upgrade database from v2(v0.3.x) to v3(^v0.4.x)") .display_order(0), ) .display_order(0), ) .subcommand( Command::new("user") .about("Users rated commands") .subcommand( Command::new("add") .about("Add user to database") .arg(Arg::new("user").required(true)) .arg(Arg::new("password").required(true)) .display_order(0), ) .subcommand( Command::new("del") .about("Delete user from database") .arg(Arg::new("user").required(true)) .display_order(0), ) .subcommand( Command::new("list") .about("List all users") .display_order(0), ) .display_order(0), ) .subcommand( Command::new("repo") .about("Repository ACL rated commands") .subcommand( Command::new("add") .about("Add user to repository") .arg(Arg::new("repo").required(true)) .arg(Arg::new("user").required(true)) .display_order(0), ) .subcommand( Command::new("del") .about("Del user from repository") .arg(Arg::new("repo")) .arg(Arg::new("user")) .arg(Arg::new("clear-all").conflicts_with("user")) .display_order(0), ) .subcommand( Command::new("list") .about("Show all repositories or only show specify repository detail") .arg(Arg::new("repo")) .display_order(0), ) .display_order(0), ); if let Some(args) = arguments { app.get_matches_from(args) } else { app.get_matches() } } fn process_arguments() -> Result<()> { let ret = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .unwrap() .block_on(async_main(get_arg_matches(None)))?; if ret == 1 { std::process::exit(1); } Ok(()) } fn main() -> Result<()> { let logfile_path = env::var("LOG_FILE").unwrap_or_else(|_| "/var/cache/cgit/auth.log".to_string()); let logfile = FileAppender::builder() .encoder(Box::new(PatternEncoder::new( "{d(%Y-%m-%d %H:%M:%S)}- {h({l})} - {m}{n}", ))) .build(&logfile_path); let logfile = match logfile { Ok(f) => f, Err(e) => { return Err(anyhow::anyhow!( "Got error while append to {logfile_path}: {e:?}", )); } }; let config = log4rs::Config::builder() .appender(Appender::builder().build("logfile", Box::new(logfile))) .loggers([ log4rs::config::Logger::builder().build("handlebars::render", log::LevelFilter::Warn), log4rs::config::Logger::builder().build("handlebars::context", log::LevelFilter::Warn), ]) .build( Root::builder() .appender("logfile") .build(log::LevelFilter::Debug), )?; log4rs::init_config(config)?; log::debug!( "{}", env::args() .enumerate() .map(|(nth, arg)| format!("[{nth}]={arg}")) .collect::>() .join(" ") ); if let Err(e) = process_arguments() { log::error!("{e:?}"); }; Ok(()) }