awol2005ex_kinit 0.0.1

Kerberos authentication using password or keytab
Documentation
// Copyright 2024 awol2005ex
//
// 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.

//! Kinit command-line tool - Equivalent to: kinit [principal] or kinit -kt keytab principal
//!
//! This tool implements the kinit command functionality supporting both password and keytab authentication modes.

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};

/// Command line arguments for kinit
#[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 {
    /// Use keytab authentication (with default or specified keytab file)
    #[arg(short = 'k', long = "keytab-auth", help = "Use keytab authentication")]
    keytab_auth: bool,

    /// Keytab file path (optional, if not provided will use default keytab)
    #[arg(short = 't', long = "keytab", help = "Path to keytab file for keytab authentication")]
    keytab: Option<String>,

    /// Kerberos principal (username@REALM)
    #[arg(required = true, help = "Kerberos principal in format username@REALM")]
    principal: String,

    /// KDC IP address or hostname (optional, will be read from krb5.conf if not provided)
    #[arg(short = 's', long = "server", help = "KDC server hostname or IP address")]
    kdc_server: Option<String>,

    /// Encryption type for password authentication (optional)
    #[arg(short = 'e', long = "enctype", help = "Encryption type: 18 (AES256), 17 (AES128), 23 (RC4)")]
    enctype: Option<i32>,

    /// Ticket lifetime (optional)
    #[arg(short = 'l', long = "lifetime", help = "Ticket lifetime (e.g., '10h', '1d', '30m')")]
    lifetime: Option<String>,

    /// Credential cache file path (optional)
    #[arg(short = 'c', long = "cache", help = "Path to credential cache file")]
    cache: Option<String>,

    /// Forwardable ticket flag
    #[arg(short = 'f', long = "forwardable", help = "Request forwardable ticket")]
    forwardable: bool,

    /// Proxiable ticket flag
    #[arg(short = 'p', long = "proxiable", help = "Request proxiable ticket")]
    proxiable: bool,

    /// No addresses flag
    #[arg(short = 'A', long = "no-addresses", help = "Do not include addresses in ticket")]
    no_addresses: bool,

    /// Renewable ticket lifetime (optional)
    #[arg(short = 'r', long = "renewable-life", help = "Renewable ticket lifetime (e.g., '7d', '30d')")]
    renewable_life: Option<String>,

    /// Enable verbose logging
    #[arg(short, long, help = "Enable verbose logging for debugging")]
    verbose: bool,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    env_logger::init();
    
    let args = Args::parse();
    
    // Set log level based on verbose flag
    if args.verbose {
        unsafe { env::set_var("RUST_LOG", "debug") };
    } else {
        unsafe { env::set_var("RUST_LOG", "info") };
    }
    
    // Parse the principal to extract realm for KDC lookup
    let (_, realm) = parse_principal(&args.principal)?;
    
    // Determine KDC server to use
    let kdc_server = if let Some(kdc_server) = args.kdc_server {
        kdc_server
    } else {
        // Try to get KDC IP from Kerberos configuration
        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()
            }
        }
    };
    
    // Perform kinit based on the provided arguments
    let result = if args.keytab_auth || args.keytab.is_some() {
        // Keytab authentication mode
        let keytab_path = if let Some(keytab_path) = &args.keytab {
            keytab_path.clone()
        } else {
            // Use default keytab path when -k is specified but no -t
            get_default_keytab_path()
        };
        
        // Check if keytab file exists
        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 {
        // Password authentication mode
        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 ===");
            
            // Save the ticket to files
            // Use specified cache file or environment variable KRB5CCNAME, or generate default
            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 {
                // Generate default filename
                format!("{}.ccache", args.principal.replace('@', "_").replace('/', "_"))
            };

            // Save credential to the specified cache file
            credential.clone().save_into_ccache_file(&ccache_file)?;
            println!("Saved credential cache to: {}", ccache_file);
            
            // Display ticket information
            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)
        }
    }
}