use std::collections::HashMap; use clap::{arg, Subcommand}; use std::fmt; use crate::lib::nerror::NError; use crate::lib::xml::*; use crate::lib::utils::{send_api_request, check_string, Response}; // url constants const BASE_URL: &'static str = "https://www.namesilo.com/api/"; const API_VERSION: &'static str = "1"; const API_TYPE: &'static str = "xml"; // operation constants #[derive(Subcommand)] #[clap(rename_all = "lower")] pub(crate) enum Operation { // Register a new domain name #[command(skip)] RegisterDomain, // Register a new domain name using drop-catching #[command(skip)] RegisterDomainDrop, // Renew a domain name #[command(skip)] RenewDomain, // Transfer a domain name into your NameSilo account #[command(skip)] TransferDomain, // Check the status of a domain transfer #[command(skip)] CheckTransferStatus, // Add/Change the EPP code for a domain transfer #[command(skip)] TransferUpdateChangeEPPCode, // Update a Transfer to Re-Send the Admin Verification Email #[command(skip)] TransferUpdateResendAdminEmail, // Update a Transfer to Re-Submit the transfer to the registry #[command(skip)] TransferUpdateResubmitToRegistry, // Determine if up to 200 domains can be registered at this time #[command(skip)] CheckRegisterAvailability, // Determine if up to 200 domains can be transferred into your account at this time #[command(skip)] CheckTransferAvailability, /// A list of all active domains within your account ListDomains, /// Get essential information on a domain within your account GetDomainInfo { #[arg(short, long)] /// TLD String [example.com] domain: String, }, // View all contact profiles in your account #[command(skip)] ContactList, // Add a contact profile to your account #[command(skip)] ContactAdd, // Update a contact profile in account #[command(skip)] ContactUpdate, // Delete a contact profile in account #[command(skip)] ContactDelete, // Associate contact profiles with a domain #[command(skip)] ContactDomainAssociate, // Change the NameServers for up to 200 domains #[command(skip)] ChangeNameServers, /// View all DNS records associated with your domain DnsListRecords { #[arg(short, long)] /// TLD String [example.com] domain: String, }, /// Add a new DNS resource record or update existing DnsAddRecord { #[arg(short, long)] /// TLD String [example.com] domain: String, #[arg(short = 't', long, value_enum)] rrtype: ResourceType, #[arg(short = 'o', long)] /// Subdomain String [www] (can be empty) rrhost: String, #[arg(short = 'v', long)] /// Value of Record [127.0.0.1] rrvalue: String, #[arg(short = 'i', long, default_value_t = 10)] /// [Optional] distance, only required for mx rrdistance: u16, #[arg(short = 'l', long, default_value_t = 3600)] /// [Optional] time to life rrttl: u16, }, /// Update an existing DNS resource record DnsUpdateRecord { #[arg(short, long)] /// TLD String [example.com] domain: String, #[arg(short = 'r', long)] rrid: String, #[arg(short = 'o', long)] /// Subdomain String [www] (can be empty) rrhost: String, #[arg(short = 'v', long)] /// Value of Record [127.0.0.1] rrvalue: String, #[arg(short = 'i', long, default_value_t = 10)] /// [Optional] distance, only required for mx rrdistance: u16, #[arg(short = 'l', long, default_value_t = 3600)] /// [Optional] time to life rrttl: u16, }, /// Delete an existing DNS resource record DnsDeleteRecord { #[arg(short, long)] /// TLD String [example.com] domain: String, #[arg(short = 't', long, value_enum)] rrtype: ResourceType, #[arg(short = 'o', long)] /// Subdomain String [www] (can be empty) rrhost: String, }, // View all DS (DNSSEC) records associated with your domain #[command(skip)] DnsSecListRecords, // Add a DS record (DNSSEC) to your domain #[command(skip)] DnsSecAddRecord, // Delete a DS record (DNSSEC) from your domain #[command(skip)] DnsSecDeleteRecord, // List the active portfolios within your account #[command(skip)] PortfolioList, // Add a portfolio to your account #[command(skip)] PortfolioAdd, // Delete a portfolio from your account #[command(skip)] PortfolioDelete, // Add up to 200 domains to a portfolio #[command(skip)] PortfolioDomainAssociate, // List the Registered NameServers associated with one of your domains #[command(skip)] ListRegisteredNameServers, // Add a Registered NameServer for one of your domains #[command(skip)] AddRegisteredNameServer, // Modify a Registered NameServer #[command(skip)] ModifyRegisteredNameServer, // Delete a Registered NameServer #[command(skip)] DeleteRegisteredNameServer, // Add WHOIS Privacy to a domain #[command(skip)] AddPrivacy, // Remove WHOIS Privacy from a domain #[command(skip)] RemovePrivacy, // Set your domain to be auto-renewed #[command(skip)] AddAutoRenewal, // Remove the auto-renewal setting from your domain #[command(skip)] RemoveAutoRenewal, // Have the EPP authorization code for the domain emailed to the administrative contact #[command(skip)] RetrieveAuthCode, // Forward your domain #[command(skip)] DomainForward, // Forward a sub-domain #[command(skip)] DomainForwardSubDomain, // Delete a sub-domain forward #[command(skip)] DomainForwardSubDomainDelete, // Lock your domain #[command(skip)] DomainLock, // Unlock your domain #[command(skip)] DomainUnlock, // List all email forwards for your domain #[command(skip)] ListEmailForwards, // Add or modify an email forward for your domain #[command(skip)] ConfigureEmailForward, // Delete an email forward for your domain #[command(skip)] DeleteEmailForward, // See the verification status of any Registrant email addresses #[command(skip)] RegistrantVerificationStatus, // Verify a Registrant email address #[command(skip)] EmailVerification, // View your NameSilo account funds balance #[command(skip)] GetAccountBalance, // Increase your NameSilo account funds #[command(skip)] AddAccountFunds, // Returns a list and specifics for all of your active Marketplace sales #[command(skip)] MarketplaceActiveSalesOverview, // Allows you to add a new Marketplace sale or modify and existing sale #[command(skip)] MarketplaceAddOrModifySale, // Allows you to update the appearance of your Marketplace Landing Page #[command(skip)] MarketplaceLandingPageUpdate, // Returns our price list customized optionally based upon your account's specific pricing #[command(skip)] GetPrices, // Returns a list of all orders placed in your account #[command(skip)] ListOrders, // Provides details for the provided order number #[command(skip)] OrderDetails, /// [Custom] Get DNS record id if it exists DnsRecordId { #[arg(short, long)] /// TLD String [example.com] domain: String, #[arg(short = 't', long, value_enum)] rrtype: ResourceType, #[arg(short = 'o', long)] /// Subdomain String [www] (can be empty) rrhost: String, }, } #[derive(clap::ValueEnum, Clone)] /// Resource Type pub(crate) enum ResourceType { /// The IPV4 Address A, /// The IPV6 Address AAAA, /// The Target Hostname CNAME, /// The Target Hostname MX, /// The Text TXT, } impl fmt::Display for ResourceType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ResourceType::A => write!(f, "A"), ResourceType::AAAA => write!(f, "AAAA"), ResourceType::CNAME => write!(f, "CNAME"), ResourceType::MX => write!(f, "MX"), ResourceType::TXT => write!(f, "TXT"), } } } #[allow(dead_code)] impl fmt::Display for Operation { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Operation::RegisterDomain => write!(f, "registerDomain"), // Register a new domain name Operation::RegisterDomainDrop => write!(f, "registerDomainDrop"), // Register a new domain name using drop-catching Operation::RenewDomain => write!(f, "renewDomain"), // Renew a domain name Operation::TransferDomain => write!(f, "transferDomain"), // Transfer a domain name into your NameSilo account Operation::CheckTransferStatus => write!(f, "checkTransferStatus"), // Check the status of a domain transfer Operation::TransferUpdateChangeEPPCode => write!(f, "transferUpdateChangeEPPCode"), // Add/Change the EPP code for a domain transfer Operation::TransferUpdateResendAdminEmail => write!(f, "transferUpdateResendAdminEmail"), // Update a Transfer to Re-Send the Admin Verification Email Operation::TransferUpdateResubmitToRegistry => write!(f, "transferUpdateResubmitToRegistry"), // Update a Transfer to Re-Submit the transfer to the registry Operation::CheckRegisterAvailability => write!(f, "checkRegisterAvailability"), // Determine if up to 200 domains can be registered at this time Operation::CheckTransferAvailability => write!(f, "checkTransferAvailability"), // Determine if up to 200 domains can be transferred into your account at this time Operation::ListDomains => write!(f, "listDomains"), // A list of all active domains within your account Operation::GetDomainInfo { .. } => write!(f, "getDomainInfo"), // Get essential information on a domain within your account Operation::ContactList => write!(f, "contactList"), // View all contact profiles in your account Operation::ContactAdd => write!(f, "contactAdd"), // Add a contact profile to your account Operation::ContactUpdate => write!(f, "contactUpdate"), // Update a contact profile in account Operation::ContactDelete => write!(f, "contactDelete"), // Delete a contact profile in account Operation::ContactDomainAssociate => write!(f, "contactDomainAssociate"), // Associate contact profiles with a domain Operation::ChangeNameServers => write!(f, "changeNameServers"), // Change the NameServers for up to 200 domains Operation::DnsListRecords { .. } => write!(f, "dnsListRecords"), // View all DNS records associated with your domain Operation::DnsAddRecord { .. } => write!(f, "dnsAddRecord"), // Add a new DNS resource record Operation::DnsUpdateRecord { .. } => write!(f, "dnsUpdateRecord"), // Update an existing DNS resource record Operation::DnsDeleteRecord { .. } => write!(f, "dnsDeleteRecord"), // Delete an existing DNS resource record Operation::DnsSecListRecords => write!(f, "dnsSecListRecords"), // View all DS (DNSSEC) records associated with your domain Operation::DnsSecAddRecord => write!(f, "dnsSecAddRecord"), // Add a DS record (DNSSEC) to your domain Operation::DnsSecDeleteRecord => write!(f, "dnsSecDeleteRecord"), // Delete a DS record (DNSSEC) from your domain Operation::PortfolioList => write!(f, "portfolioList"), // List the active portfolios within your account Operation::PortfolioAdd => write!(f, "portfolioAdd"), // Add a portfolio to your account Operation::PortfolioDelete => write!(f, "portfolioDelete"), // Delete a portfolio from your account Operation::PortfolioDomainAssociate => write!(f, "portfolioDomainAssociate"), // Add up to 200 domains to a portfolio Operation::ListRegisteredNameServers => write!(f, "listRegisteredNameServers"), // List the Registered NameServers associated with one of your domains Operation::AddRegisteredNameServer => write!(f, "addRegisteredNameServer"), // Add a Registered NameServer for one of your domains Operation::ModifyRegisteredNameServer => write!(f, "modifyRegisteredNameServer"), // Modify a Registered NameServer Operation::DeleteRegisteredNameServer => write!(f, "deleteRegisteredNameServer"), // Delete a Registered NameServer Operation::AddPrivacy => write!(f, "addPrivacy"), // Add WHOIS Privacy to a domain Operation::RemovePrivacy => write!(f, "removePrivacy"), // Remove WHOIS Privacy from a domain Operation::AddAutoRenewal => write!(f, "addAutoRenewal"), // Set your domain to be auto-renewed Operation::RemoveAutoRenewal => write!(f, "removeAutoRenewal"), // Remove the auto-renewal setting from your domain Operation::RetrieveAuthCode => write!(f, "retrieveAuthCode"), // Have the EPP authorization code for the domain emailed to the administrative contact Operation::DomainForward => write!(f, "domainForward"), // Forward your domain Operation::DomainForwardSubDomain => write!(f, "domainForwardSubDomain"), // Forward a sub-domain Operation::DomainForwardSubDomainDelete => write!(f, "domainForwardSubDomainDelete"), // Delete a sub-domain forward Operation::DomainLock => write!(f, "domainLock"), // Lock your domain Operation::DomainUnlock => write!(f, "domainUnlock"), // Unlock your domain Operation::ListEmailForwards => write!(f, "listEmailForwards"), // List all email forwards for your domain Operation::ConfigureEmailForward => write!(f, "configureEmailForward"), // Add or modify an email forward for your domain Operation::DeleteEmailForward => write!(f, "deleteEmailForward"), // Delete an email forward for your domain Operation::RegistrantVerificationStatus => write!(f, "registrantVerificationStatus"), // See the verification status of any Registrant email addresses Operation::EmailVerification => write!(f, "emailVerification"), // Verify a Registrant email address Operation::GetAccountBalance => write!(f, "getAccountBalance"), // View your NameSilo account funds balance Operation::AddAccountFunds => write!(f, "addAccountFunds"), // Increase your NameSilo account funds Operation::MarketplaceActiveSalesOverview => write!(f, "marketplaceActiveSalesOverview"), // Returns a list and specifics for all of your active Marketplace sales Operation::MarketplaceAddOrModifySale => write!(f, "marketplaceAddOrModifySale"), // Allows you to add a new Marketplace sale or modify and existing sale Operation::MarketplaceLandingPageUpdate => write!(f, "marketplaceLandingPageUpdate"), // Allows you to update the appearance of your Marketplace Landing Page Operation::GetPrices => write!(f, "getPrices"), // Returns our price list customized optionally based upon your account's specific pricing Operation::ListOrders => write!(f, "listOrders"), // Returns a list of all orders placed in your account Operation::OrderDetails => write!(f, "orderDetails"), // Provides details for the provided order number _ => panic!("unofficial API call") // unofficial api call } } } impl Operation { pub(crate) fn call(&self, api_key: &String) -> Result { match self { Operation::ListDomains => send_api_request(&self.url(api_key, &HashMap::new())?), Operation::GetDomainInfo { domain } => { send_api_request(&self.url(api_key, &hashmap![type(&str, (String, Option<(&[Range], &str)>)) "domain" => (domain.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))) ])?) } Operation::DnsListRecords { domain } => { send_api_request(&self.url(api_key, &hashmap![type(&str, (String, Option<(&[Range], &str)>)) "domain" => (domain.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))) ])?) } Operation::DnsAddRecord { domain, rrtype, rrhost, rrvalue, rrdistance, rrttl, } => { let id_check = Operation::DnsRecordId { domain: domain.to_string(), rrtype: rrtype.clone(), rrhost: rrhost.to_string() }.call(api_key); match id_check { Ok(id) => Operation::DnsUpdateRecord { domain: domain.to_string(), rrid: id.get_string().to_string(), rrhost: rrhost.to_string(), rrvalue: rrvalue.to_string(), rrdistance: *rrdistance, rrttl: *rrttl, }.call(api_key), Err(e) => { match e { NError::RecordNotFound(_) => send_api_request(&self.url(api_key, &hashmap![type(&str, (String, Option<(&[Range], &str)>)) "domain" => (domain.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))), "rrtype" => (rrtype.to_string(), None), "rrhost" => (rrhost.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))), "rrvalue" => (rrvalue.to_string(), None), "rrdistance" => (rrdistance.to_string(), None), "rrttl" => (rrttl.to_string(), None) ])?), e => Err(e) } } } } Operation::DnsDeleteRecord { domain, rrtype, rrhost } => { let id_check = Operation::DnsRecordId { domain: domain.to_string(), rrtype: rrtype.clone(), rrhost: rrhost.to_string() }.call(api_key); match id_check { Ok(id) => send_api_request(&self.url(api_key, &hashmap![type(&str, (String, Option<(&[Range], &str)>)) "domain" => (domain.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))), "rrid" => (id.get_string().to_string(), None) ])?), _ => id_check } } Operation::DnsUpdateRecord { domain, rrid, rrhost, rrvalue, rrdistance, rrttl, } => { send_api_request(&self.url(api_key, &hashmap![type(&str, (String, Option<(&[Range], &str)>)) "domain" => (domain.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))), "rrid" => (rrid.to_string(), Some((FILTER_LETTER_NUMBER, ""))), "rrhost" => (rrhost.to_string(), Some((FILTER_LETTER_NUMBER, ALLOWLIST_SYMBOLS))), "rrvalue" => (rrvalue.to_string(), None), "rrdistance" => (rrdistance.to_string(), None), "rrttl" => (rrttl.to_string(), None) ])?) } Operation::DnsRecordId { domain, rrtype, rrhost } => { let record_result: Result = Operation::DnsListRecords { domain: domain.to_string() }.call(api_key); if record_result.is_err() { return record_result; } let record: Response = record_result.unwrap(); let xml: roxmltree::Document = roxmltree::Document::parse(&record.get_string().as_str()).unwrap(); let domain_str: String = if rrhost.len() > 0 { format!("{}.{}", rrhost, domain.as_str()) } else { domain.to_string() }; match find_record_id(domain_str.as_str(), rrtype.to_string().as_str(), &xml) { Some(id) => Ok(Response::with_string(id)), None => Err(NError::RecordNotFound(format!("{} for {}.{}", rrtype.to_string(), rrhost, domain.as_str()))) } } _ => Err(NError::NyiOpError(self.to_string())), } } fn url(&self, api_key: &str, args: &HashMap<&str, (String, Option<(&[Range], &str)>)>) -> Result { let mut params: String = String::new(); for (k, v) in args { if v.0.len() == 0 || v.1.is_some() && !check_string(&v.0, v.1.unwrap().0, v.1.unwrap().1) { return Err(NError::InvalidArgError(format!("{} - {}: {}", self.to_string(), k, v.0))); } params.push_str(&format!("&{}{}{}", k, KEY_VALUE_SEP, v.0)) } Ok(format!("{}{}?version={}&type={}&key={}{}", BASE_URL, self, API_VERSION, API_TYPE, api_key, params)) } } // input constants pub(crate) const KEY_VALUE_SEP: &str = "="; // filter constants pub(crate) type Range = (char, char); pub(crate) const FILTER_LETTER_NUMBER: &[Range; 2] = &[('a', 'z'), ('0', '9')]; #[allow(dead_code)] pub(crate) const FILTER_LETTER: &[Range; 1] = &[('a', 'z')]; pub(crate) const ALLOWLIST_SYMBOLS: &str = "._-";