use clap::Parser;
use std::env;
use std::path::Path;
use std::io::Write;
use awol2005ex_kinit::{kinit_with_password, kinit_with_keytab, get_kdc_ip_from_config, parse_principal, get_default_keytab_path};
#[derive(Parser, Debug)]
#[command(name = "kinit")]
#[command(about = "Kerberos authentication using password or keytab", long_about = None)]
#[command(help_template = "{name} {version}\n{author}\n{about}\n\nUSAGE:\n {usage}\n\nOPTIONS:\n{options}\n\nEXAMPLES:\n # Password authentication (prompts for password)
kinit principal@REALM.COM
# Keytab authentication with default keytab
kinit -k principal@REALM.COM
# Keytab authentication with specific keytab file
kinit -k -t keytab_file.keytab principal@REALM.COM
# Specify KDC server
kinit principal@REALM.COM -s kdc.server.com
# Set ticket lifetime
kinit -l 10h principal@REALM.COM
# Specify credential cache file
kinit -c /tmp/my_cache principal@REALM.COM
# Forwardable ticket
kinit -f principal@REALM.COM
# Proxiable ticket
kinit -p principal@REALM.COM
")]
struct Args {
#[arg(short = 'k', long = "keytab-auth", help = "Use keytab authentication")]
keytab_auth: bool,
#[arg(short = 't', long = "keytab", help = "Path to keytab file for keytab authentication")]
keytab: Option<String>,
#[arg(required = true, help = "Kerberos principal in format username@REALM")]
principal: String,
#[arg(short = 's', long = "server", help = "KDC server hostname or IP address")]
kdc_server: Option<String>,
#[arg(short = 'e', long = "enctype", help = "Encryption type: 18 (AES256), 17 (AES128), 23 (RC4)")]
enctype: Option<i32>,
#[arg(short = 'l', long = "lifetime", help = "Ticket lifetime (e.g., '10h', '1d', '30m')")]
lifetime: Option<String>,
#[arg(short = 'c', long = "cache", help = "Path to credential cache file")]
cache: Option<String>,
#[arg(short = 'f', long = "forwardable", help = "Request forwardable ticket")]
forwardable: bool,
#[arg(short = 'p', long = "proxiable", help = "Request proxiable ticket")]
proxiable: bool,
#[arg(short = 'A', long = "no-addresses", help = "Do not include addresses in ticket")]
no_addresses: bool,
#[arg(short = 'r', long = "renewable-life", help = "Renewable ticket lifetime (e.g., '7d', '30d')")]
renewable_life: Option<String>,
#[arg(short, long, help = "Enable verbose logging for debugging")]
verbose: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
let args = Args::parse();
if args.verbose {
unsafe { env::set_var("RUST_LOG", "debug") };
} else {
unsafe { env::set_var("RUST_LOG", "info") };
}
let (_, realm) = parse_principal(&args.principal)?;
let kdc_server = if let Some(kdc_server) = args.kdc_server {
kdc_server
} else {
match get_kdc_ip_from_config(realm) {
Ok(ip) => {
log::info!("Found KDC {} for realm {} in krb5.conf", ip, realm);
ip
},
Err(e) => {
log::warn!("Could not get KDC IP from config: {}", e);
log::warn!("Falling back to default KDC IP: 192.168.1.100");
"192.168.1.100".to_string()
}
}
};
let result = if args.keytab_auth || args.keytab.is_some() {
let keytab_path = if let Some(keytab_path) = &args.keytab {
keytab_path.clone()
} else {
get_default_keytab_path()
};
if !Path::new(&keytab_path).exists() {
return Err(format!("Keytab file not found: {}", keytab_path).into());
}
println!("Using keytab: {}", keytab_path);
kinit_with_keytab(&keytab_path, &args.principal, &kdc_server)
} else {
print!("Password for {}: ", args.principal);
std::io::stdout().flush()?;
let password = rpassword::read_password()?;
kinit_with_password(&args.principal, &password, &kdc_server)
};
match result {
Ok(credential) => {
println!("\n=== Kinit Successful ===");
let ccache_file = if let Some(cache_path) = &args.cache {
cache_path.clone()
} else if let Ok(ccache_path) = env::var("KRB5CCNAME") {
ccache_path
} else {
format!("{}.ccache", args.principal.replace('@', "_").replace('/', "_"))
};
credential.clone().save_into_ccache_file(&ccache_file)?;
println!("Saved credential cache to: {}", ccache_file);
if args.lifetime.is_some() || args.renewable_life.is_some() {
println!("Ticket options:");
if let Some(lifetime) = &args.lifetime {
println!(" Lifetime: {}", lifetime);
}
if let Some(renewable_life) = &args.renewable_life {
println!(" Renewable life: {}", renewable_life);
}
}
if args.forwardable {
println!(" Forwardable: true");
}
if args.proxiable {
println!(" Proxiable: true");
}
if args.no_addresses {
println!(" No addresses: true");
}
let keytab_display = if args.keytab_auth {
if args.keytab.is_some() {
format!("-kt {}", args.keytab.as_ref().unwrap())
} else {
"-k".to_string()
}
} else {
"".to_string()
};
println!("\nEquivalent to: kinit {} {}", keytab_display, args.principal);
Ok(())
}
Err(e) => {
eprintln!("Kinit failed: {}", e);
Err(e)
}
}
}