aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-07-19 01:41:53 +0200
committerLinnea Gräf <nea@nea.moe>2024-07-19 01:41:53 +0200
commite4b9a9b40838777d4d017001fef00e1c57efbb0c (patch)
treed9873f599cb4fd92cd7f71284d6be4df9155a3fe /src
downloadfagit-e4b9a9b40838777d4d017001fef00e1c57efbb0c.tar.gz
fagit-e4b9a9b40838777d4d017001fef00e1c57efbb0c.tar.bz2
fagit-e4b9a9b40838777d4d017001fef00e1c57efbb0c.zip
Init
Diffstat (limited to 'src')
-rw-r--r--src/config.rs11
-rw-r--r--src/login.rs115
-rw-r--r--src/main.rs74
3 files changed, 200 insertions, 0 deletions
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..5e8cc5d
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,11 @@
+use std::path::Path;
+
+pub fn ssh_user() -> &'static str {
+ return "git";
+}
+pub fn binary_path() -> &'static str {
+ return "/opt/fagit/fagit";
+}
+pub fn repo_store() -> &'static Path {
+ return Path::new("/opt/fagit/store/");
+}
diff --git a/src/login.rs b/src/login.rs
new file mode 100644
index 0000000..ca12c50
--- /dev/null
+++ b/src/login.rs
@@ -0,0 +1,115 @@
+use std::path::{Path, PathBuf};
+
+use crate::Login;
+
+use clap::Args;
+use clap::{Parser, Subcommand};
+
+#[derive(Parser)]
+#[command()]
+struct Cli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+ #[command(name = "git-upload-pack")]
+ GitUploadPack(GitUploadPack),
+ #[command(name = "git-receive-pack")]
+ GitReceivePack(GitUploadPack), // TODO: own args
+}
+#[derive(Args)]
+struct GitUploadPack {
+ repo_path: String,
+}
+pub(crate) fn do_login(login: &Login) {
+ let command = std::env::var("SSH_ORIGINAL_COMMAND").unwrap_or("<no command>".to_owned());
+ log::info!(
+ "Performed token login for {} {} with command: {}",
+ login.keytype,
+ login.keydata,
+ command
+ );
+ let user_token = get_user_token(login.keytype.to_owned(), login.keydata.to_owned());
+ let mut command = shlex::split(&command).unwrap();
+ command.insert(0, "fagit-dummy".to_owned());
+ let args = Cli::parse_from(command.iter());
+ match args.command {
+ Commands::GitUploadPack(upload_pack) => {
+ let repo_path = canonicalize_repo(&upload_pack.repo_path, &user_token);
+ if can_read(&repo_path, &user_token) {
+ let mut child = std::process::Command::new("git-upload-pack")
+ .args([repo_path.repo_path().as_os_str()])
+ .spawn()
+ .unwrap();
+ child.wait().unwrap();
+ }
+ }
+ Commands::GitReceivePack(upload_pack) => {
+ let repo_path = canonicalize_repo(&upload_pack.repo_path, &user_token);
+ if can_write(&repo_path, &user_token) {
+ let mut child = std::process::Command::new("git-receive-pack")
+ .args([repo_path.repo_path().as_os_str()])
+ .spawn()
+ .unwrap();
+ child.wait().unwrap();
+ }
+ }
+ }
+}
+struct UserToken {
+ keytype: String,
+ keydata: String,
+
+ is_operator: bool,
+}
+struct RepoId(String);
+impl RepoId {
+ fn is_meta_repo(&self) -> bool {
+ return self.0.starts_with("meta/") || self.0 == "meta.git";
+ }
+ fn repo_path(&self) -> PathBuf {
+ return Path::join(crate::config::repo_store(), self.0.clone());
+ }
+}
+
+fn canonicalize_repo(path: &str, user: &UserToken) -> RepoId {
+ let mut path = path.to_owned();
+ path.make_ascii_lowercase();
+ if path.starts_with("/") {
+ path.remove(0);
+ }
+ if path.ends_with("/") {
+ path.remove(path.len() - 1);
+ }
+ if !path.ends_with(".git") {
+ path += ".git";
+ }
+ // TODO: properly check directory traversal or smth here. just a regex should do the trick
+ return RepoId(path);
+}
+
+fn can_write(repo_path: &RepoId, user_token: &UserToken) -> bool {
+ if repo_path.is_meta_repo() {
+ return user_token.is_operator;
+ }
+ return true;
+}
+fn can_read(repo_path: &RepoId, user_token: &UserToken) -> bool {
+ if repo_path.is_meta_repo() {
+ return user_token.is_operator;
+ }
+ return true;
+}
+
+fn get_user_token(keytype: String, keydata: String) -> UserToken {
+ // TODO: settings
+ let is_operator = "ssh-ed25519" == keytype
+ && "AAAAC3NzaC1lZDI1NTE5AAAAINg2WYMRKINwbH5UCqqK2qq/qW0gG1NnaALHqEyU4NzM" == keydata;
+ return UserToken {
+ keytype,
+ keydata,
+ is_operator,
+ };
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..cb5a926
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,74 @@
+use std::io::{Read, Write};
+use std::path::Path;
+
+use clap::Args;
+use clap::{Parser, Subcommand};
+mod config;
+
+#[derive(Parser)]
+#[command(version, about, long_about=include_str!("../README.md"))]
+struct Cli {
+ #[command(subcommand)]
+ command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+ Auth(Auth),
+ Login(Login),
+}
+#[derive(Args)]
+struct Auth {
+ user: String,
+ home: String,
+ keytype: String,
+ keydata: String,
+}
+#[derive(Args)]
+struct Login {
+ keytype: String,
+ keydata: String,
+}
+
+fn main() {
+ systemd_journal_logger::JournalLog::new()
+ .unwrap()
+ .with_extra_fields(vec![("VERSION", env!("CARGO_PKG_VERSION"))])
+ .with_syslog_identifier("fagit".to_string())
+ .install()
+ .unwrap();
+ log::set_max_level(log::LevelFilter::Info);
+ let args = std::env::args().collect::<Vec<_>>();
+ if args.len() == 0 {
+ println!(include_str!("../README.md"));
+ }
+ let cli = Cli::parse();
+ match &cli.command {
+ Commands::Auth(auth) => find_auth_keys(auth),
+ Commands::Login(login) => crate::login::do_login(login),
+ }
+}
+mod login;
+
+fn find_auth_keys(auth: &Auth) {
+ if auth.user != config::ssh_user() {
+ let authorized_keys_path = Path::new(&auth.home).join(".ssh/authorized_keys_path");
+ let mut data = std::fs::File::open(authorized_keys_path).unwrap();
+ let mut vec = vec![];
+ data.read_to_end(&mut vec).unwrap();
+ std::io::stdout().write_all(&vec).unwrap();
+ return;
+ }
+ // TODO: escape shit in here properly
+ let login_command = format!(
+ "{} login {} {}",
+ config::binary_path(),
+ auth.keytype,
+ auth.keydata
+ );
+ println!("command=\"{}\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict {} {}",
+ login_command,
+ auth.keytype,
+ auth.keydata
+ );
+}