[ref][add] refactored code to use clap and added basic calls
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
*.api_key
|
||||||
|
|||||||
829
Cargo.lock
generated
829
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "namesilo_cli"
|
name = "nscli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Pascal"]
|
authors = ["Pascal"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -7,6 +7,7 @@ edition = "2018"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.0.8", features = ["derive"] }
|
||||||
reqwest = { version = "0.10", features = ["blocking", "json"] } # MIT/Apache-2.0
|
reqwest = { version = "0.10", features = ["blocking", "json"] } # MIT/Apache-2.0
|
||||||
url = { version = "2.1.1" } # MIT/Apache-2.0
|
url = { version = "2.1.1" } # MIT/Apache-2.0
|
||||||
xml-rs = { version = "0.8" } # MIT/Apache-2.0
|
roxmltree = { version = "0.15.0" } # MIT/Apache-2.0
|
||||||
|
|||||||
89
README.md
Normal file
89
README.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<h1 align="center">nscli</h1>
|
||||||
|
|
||||||
|
<h4 align="center">
|
||||||
|
A quickly hacked together CLI Client for the <a href="https://www.namesilo.com/api-reference">NameSilo API</a>.
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
Letsencrypt allows for wildcard and certificates for servers that are not accessible on the web, through a DNS Challenge. This tool makes automation possible for domains on the NameSilo registrar. It does not cover the entirety of the NameSilo API.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>
|
||||||
|
<a href="#getting-started">Getting started</a>
|
||||||
|
• <a href="#letsencrypt">LetsEncrypt</a>
|
||||||
|
• <a href="#roadmap">Roadmap</a>
|
||||||
|
</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```
|
||||||
|
# compile
|
||||||
|
cargo build --package nscli --release
|
||||||
|
# run
|
||||||
|
./target/release/nscli --help
|
||||||
|
|
||||||
|
# development
|
||||||
|
cargo run --package nscli --bin nscli -- help
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Line Interface
|
||||||
|
|
||||||
|
The command line interface of `nscli` is build with [clap](https://docs.rs/clap/latest/clap/):
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: nscli [OPTIONS] <COMMAND>
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
listdomains A list of all active domains within your account
|
||||||
|
getdomaininfo Get essential information on a domain within your account
|
||||||
|
dnslistrecords View all DNS records associated with your domain
|
||||||
|
dnsaddrecord Add a new DNS resource record or update existing
|
||||||
|
dnsupdaterecord Update an existing DNS resource record
|
||||||
|
dnsdeleterecord Delete an existing DNS resource record
|
||||||
|
dnsrecordid [Custom] Get DNS record id if it exists
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-a, --api-key <FILE> [default: .api_key]
|
||||||
|
-h, --help Print help information
|
||||||
|
|
||||||
|
Process finished with exit code 0
|
||||||
|
|
||||||
|
```
|
||||||
|
> **Note:** As duplicate resource records are out of scope for this tool, the following commands deviate from their NameSilo API counterpart:
|
||||||
|
> - `dnsaddrecord`: does not create duplicate entries, but instead updates a matching record if it already exists
|
||||||
|
> - `dnsdeleterecord`: takes a subdomain and record type as parameter and will delete the first matching one
|
||||||
|
> - `dnsrecordid`: is not part of the NameSilo API
|
||||||
|
|
||||||
|
## LetsEncrypt
|
||||||
|
|
||||||
|
The [pre and post validation hook](https://eff-certbot.readthedocs.io/en/stable/using.html#hooks) of `certbot` can be used to add `nscli` to the certification process.
|
||||||
|
```bash
|
||||||
|
certbot certonly --manual --preferred-challenges=dns --manual-auth-hook /path/to/pre.sh --manual-cleanup-hook /path/to/post.sh -d example.com
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# pre hook example
|
||||||
|
./nscli dnsaddrecord --domain $CERTBOT_DOMAIN \
|
||||||
|
--rrtype txt \
|
||||||
|
--rrhost _acme-challenge \
|
||||||
|
--rrvalue $CERTBOT_VALIDATION
|
||||||
|
# wait for the change to propagate
|
||||||
|
sleep 60
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# post hook example
|
||||||
|
./nscli dnsdeleterecord --domain $CERTBOT_DOMAIN \
|
||||||
|
--rrtype txt \
|
||||||
|
--rrhost _acme-challenge
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
* Add optional `rrid` param to `dnsdeleterecord`
|
||||||
|
* Properly parse response `xml` and make output available in digestible form on `stdout`
|
||||||
|
* Add more API calls
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
// operation constants
|
|
||||||
pub(crate) const REGISTERDOMAIN: &str = "registerDomain"; // Register a new domain name
|
|
||||||
pub(crate) const REGISTERDOMAINDROP: &str = "registerDomainDrop"; // Register a new domain name using drop-catching
|
|
||||||
pub(crate) const RENEWDOMAIN: &str = "renewDomain"; // Renew a domain name
|
|
||||||
pub(crate) const TRANSFERDOMAIN: &str = "transferDomain"; // Transfer a domain name into your NameSilo account
|
|
||||||
pub(crate) const CHECKTRANSFERSTATUS: &str = "checkTransferStatus"; // Check the status of a domain transfer
|
|
||||||
pub(crate) const TRANSFERUPDATECHANGEEPPCODE: &str = "transferUpdateChangeEPPCode"; // Add/Change the EPP code for a domain transfer
|
|
||||||
pub(crate) const TRANSFERUPDATERESENDADMINEMAIL: &str = "transferUpdateResendAdminEmail"; // Update a Transfer to Re-Send the Admin Verification Email
|
|
||||||
pub(crate) const TRANSFERUPDATERESUBMITTOREGISTRY: &str = "transferUpdateResubmitToRegistry"; // Update a Transfer to Re-Submit the transfer to the registry
|
|
||||||
pub(crate) const CHECKREGISTERAVAILABILITY: &str = "checkRegisterAvailability"; // Determine if up to 200 domains can be registered at this time
|
|
||||||
pub(crate) const CHECKTRANSFERAVAILABILITY: &str = "checkTransferAvailability"; // Determine if up to 200 domains can be transferred into your account at this time
|
|
||||||
pub(crate) const LISTDOMAINS: &str = "listDomains"; // A list of all active domains within your account
|
|
||||||
pub(crate) const GETDOMAININFO: &str = "getDomainInfo"; // Get essential information on a domain within your account
|
|
||||||
pub(crate) const CONTACTLIST: &str = "contactList"; // View all contact profiles in your account
|
|
||||||
pub(crate) const CONTACTADD: &str = "contactAdd"; // Add a contact profile to your account
|
|
||||||
pub(crate) const CONTACTUPDATE: &str = "contactUpdate"; // Update a contact profile in account
|
|
||||||
pub(crate) const CONTACTDELETE: &str = "contactDelete"; // Delete a contact profile in account
|
|
||||||
pub(crate) const CONTACTDOMAINASSOCIATE: &str = "contactDomainAssociate"; // Associate contact profiles with a domain
|
|
||||||
pub(crate) const CHANGENAMESERVERS: &str = "changeNameServers"; // Change the NameServers for up to 200 domains
|
|
||||||
pub(crate) const DNSLISTRECORDS: &str = "dnsListRecords"; // View all DNS records associated with your domain
|
|
||||||
pub(crate) const DNSADDRECORD: &str = "dnsAddRecord"; // Add a new DNS resource record
|
|
||||||
pub(crate) const DNSUPDATERECORD: &str = "dnsUpdateRecord"; // Update an existing DNS resource record
|
|
||||||
pub(crate) const DNSDELETERECORD: &str = "dnsDeleteRecord"; // Delete an existing DNS resource record
|
|
||||||
pub(crate) const DNSSECLISTRECORDS: &str = "dnsSecListRecords"; // View all DS (DNSSEC) records associated with your domain
|
|
||||||
pub(crate) const DNSSECADDRECORD: &str = "dnsSecAddRecord"; // Add a DS record (DNSSEC) to your domain
|
|
||||||
pub(crate) const DNSSECDELETERECORD: &str = "dnsSecDeleteRecord"; // Delete a DS record (DNSSEC) from your domain
|
|
||||||
pub(crate) const PORTFOLIOLIST: &str = "portfolioList"; // List the active portfolios within your account
|
|
||||||
pub(crate) const PORTFOLIOADD: &str = "portfolioAdd"; // Add a portfolio to your account
|
|
||||||
pub(crate) const PORTFOLIODELETE: &str = "portfolioDelete"; // Delete a portfolio from your account
|
|
||||||
pub(crate) const PORTFOLIODOMAINASSOCIATE: &str = "portfolioDomainAssociate"; // Add up to 200 domains to a portfolio
|
|
||||||
pub(crate) const LISTREGISTEREDNAMESERVERS: &str = "listRegisteredNameServers"; // List the Registered NameServers associated with one of your domains
|
|
||||||
pub(crate) const ADDREGISTEREDNAMESERVER: &str = "addRegisteredNameServer"; // Add a Registered NameServer for one of your domains
|
|
||||||
pub(crate) const MODIFYREGISTEREDNAMESERVER: &str = "modifyRegisteredNameServer"; // Modify a Registered NameServer
|
|
||||||
pub(crate) const DELETEREGISTEREDNAMESERVER: &str = "deleteRegisteredNameServer"; // Delete a Registered NameServer
|
|
||||||
pub(crate) const ADDPRIVACY: &str = "addPrivacy"; // Add WHOIS Privacy to a domain
|
|
||||||
pub(crate) const REMOVEPRIVACY: &str = "removePrivacy"; // Remove WHOIS Privacy from a domain
|
|
||||||
pub(crate) const ADDAUTORENEWAL: &str = "addAutoRenewal"; // Set your domain to be auto-renewed
|
|
||||||
pub(crate) const REMOVEAUTORENEWAL: &str = "removeAutoRenewal"; // Remove the auto-renewal setting from your domain
|
|
||||||
pub(crate) const RETRIEVEAUTHCODE: &str = "retrieveAuthCode"; // Have the EPP authorization code for the domain emailed to the administrative contact
|
|
||||||
pub(crate) const DOMAINFORWARD: &str = "domainForward"; // Forward your domain
|
|
||||||
pub(crate) const DOMAINFORWARDSUBDOMAIN: &str = "domainForwardSubDomain"; // Forward a sub-domain
|
|
||||||
pub(crate) const DOMAINFORWARDSUBDOMAINDELETE: &str = "domainForwardSubDomainDelete"; // Delete a sub-domain forward
|
|
||||||
pub(crate) const DOMAINLOCK: &str = "domainLock"; // Lock your domain
|
|
||||||
pub(crate) const DOMAINUNLOCK: &str = "domainUnlock"; // Unlock your domain
|
|
||||||
pub(crate) const LISTEMAILFORWARDS: &str = "listEmailForwards"; // List all email forwards for your domain
|
|
||||||
pub(crate) const CONFIGUREEMAILFORWARD: &str = "configureEmailForward"; // Add or modify an email forward for your domain
|
|
||||||
pub(crate) const DELETEEMAILFORWARD: &str = "deleteEmailForward"; // Delete an email forward for your domain
|
|
||||||
pub(crate) const REGISTRANTVERIFICATIONSTATUS: &str = "registrantVerificationStatus"; // See the verification status of any Registrant email addresses
|
|
||||||
pub(crate) const EMAILVERIFICATION: &str = "emailVerification"; // Verify a Registrant email address
|
|
||||||
pub(crate) const GETACCOUNTBALANCE: &str = "getAccountBalance"; // View your NameSilo account funds balance
|
|
||||||
pub(crate) const ADDACCOUNTFUNDS: &str = "addAccountFunds"; // Increase your NameSilo account funds
|
|
||||||
pub(crate) const MARKETPLACEACTIVESALESOVERVIEW: &str = "marketplaceActiveSalesOverview"; // Returns a list and specifics for all of your active Marketplace sales
|
|
||||||
pub(crate) const MARKETPLACEADDORMODIFYSALE: &str = "marketplaceAddOrModifySale"; // Allows you to add a new Marketplace sale or modify and existing sale
|
|
||||||
pub(crate) const MARKETPLACELANDINGPAGEUPDATE: &str = "marketplaceLandingPageUpdate"; // Allows you to update the appearance of your Marketplace Landing Page
|
|
||||||
pub(crate) const GETPRICES: &str = "getPrices"; // Returns our price list customized optionally based upon your account's specific pricing
|
|
||||||
pub(crate) const LISTORDERS: &str = "listOrders"; // Returns a list of all orders placed in your account
|
|
||||||
pub(crate) const ORDERDETAILS: &str = "orderDetails"; // Provides details for the provided order number
|
|
||||||
|
|
||||||
// input constants
|
|
||||||
pub(crate) const KEY_VALUE_SEP: &str = "=";
|
|
||||||
|
|
||||||
// url constants
|
|
||||||
const BASE_URL: &'static str = "https://www.namesilo.com/api/";
|
|
||||||
const API_VERSION: &'static str = "1";
|
|
||||||
const API_TYPE: &'static str = "xml";
|
|
||||||
pub(crate) fn api_url(operation: &str, api_key: &str, args: &HashMap<String, String>) -> String {
|
|
||||||
let mut params = String::new();
|
|
||||||
for (k, v) in args {
|
|
||||||
params.push_str(&format!("&{}={}", k, v))
|
|
||||||
}
|
|
||||||
format!("{}{}?version={}&type={}&key={}{}", BASE_URL, operation, API_VERSION, API_TYPE, api_key, params )
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter constants
|
|
||||||
pub(crate) type Range = (char, char);
|
|
||||||
pub(crate) const FILTER_LETTER_NUMBER: &[Range; 2] = &[('a','z'), ('0','9')];
|
|
||||||
pub(crate) const FILTER_LETTER: &[Range; 1] = &[('a','z')];
|
|
||||||
pub(crate) const WHITELIST_SYMBOLS: &str = "._-";
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
use crate::lib::nerror::NError;
|
|
||||||
use crate::lib::utils::{parse_args, check_args, send_api_request, Response};
|
|
||||||
use crate::api::constants::*;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
pub(crate) fn list_domains(api_key: &str, _: &str) -> Result<Response, NError> {
|
|
||||||
|
|
||||||
let result: String = send_api_request(LISTDOMAINS, api_key, &HashMap::new())?;
|
|
||||||
|
|
||||||
Ok(Response::with_string(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_domain_info(api_key: &str, args: &str) -> Result<Response, NError> {
|
|
||||||
let args: HashMap<String, String> = parse_args(args,
|
|
||||||
(FILTER_LETTER, ""),
|
|
||||||
(FILTER_LETTER_NUMBER, WHITELIST_SYMBOLS));
|
|
||||||
|
|
||||||
// check args, return error otherwise
|
|
||||||
check_args(&args, &["domain"], &[])?;
|
|
||||||
|
|
||||||
let result: String = send_api_request(GETDOMAININFO, api_key, &args)?;
|
|
||||||
|
|
||||||
Ok(Response::with_string(result))
|
|
||||||
}
|
|
||||||
@@ -1,3 +1 @@
|
|||||||
pub mod constants;
|
|
||||||
pub mod functions;
|
|
||||||
pub mod operations;
|
pub mod operations;
|
||||||
@@ -1,75 +1,446 @@
|
|||||||
use crate::api::functions::*;
|
|
||||||
use crate::api::constants::*;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use clap::{arg, Subcommand};
|
||||||
|
use std::fmt;
|
||||||
use crate::lib::nerror::NError;
|
use crate::lib::nerror::NError;
|
||||||
use crate::lib::utils::Response;
|
use crate::lib::xml::*;
|
||||||
|
use crate::lib::utils::{send_api_request, check_string, Response};
|
||||||
|
|
||||||
pub(crate) type Operation = fn(&str, &str) -> Result<Response, NError>;
|
// url constants
|
||||||
|
const BASE_URL: &'static str = "https://www.namesilo.com/api/";
|
||||||
|
const API_VERSION: &'static str = "1";
|
||||||
|
const API_TYPE: &'static str = "xml";
|
||||||
|
|
||||||
pub fn get_operation(op_name: &str) -> Result<Operation, NError> {
|
// operation constants
|
||||||
let mut operation_mapping: HashMap<String, Result<Operation, NError>> = hashmap![type(String, Result<Operation, NError>)
|
#[derive(Subcommand)]
|
||||||
REGISTERDOMAIN.to_lowercase() => Err(NError::NyiOpError(REGISTERDOMAIN.to_string())),
|
#[clap(rename_all = "lower")]
|
||||||
REGISTERDOMAINDROP.to_lowercase() => Err(NError::NyiOpError(REGISTERDOMAINDROP.to_string())),
|
pub(crate) enum Operation {
|
||||||
RENEWDOMAIN.to_lowercase() => Err(NError::NyiOpError(RENEWDOMAIN.to_string())),
|
// Register a new domain name
|
||||||
TRANSFERDOMAIN.to_lowercase() => Err(NError::NyiOpError(TRANSFERDOMAIN.to_string())),
|
#[command(skip)]
|
||||||
CHECKTRANSFERSTATUS.to_lowercase() => Err(NError::NyiOpError(CHECKTRANSFERSTATUS.to_string())),
|
RegisterDomain,
|
||||||
TRANSFERUPDATECHANGEEPPCODE.to_lowercase() => Err(NError::NyiOpError(TRANSFERUPDATECHANGEEPPCODE.to_string())),
|
// Register a new domain name using drop-catching
|
||||||
TRANSFERUPDATERESENDADMINEMAIL.to_lowercase() => Err(NError::NyiOpError(TRANSFERUPDATERESENDADMINEMAIL.to_string())),
|
#[command(skip)]
|
||||||
TRANSFERUPDATERESUBMITTOREGISTRY.to_lowercase() => Err(NError::NyiOpError(TRANSFERUPDATERESUBMITTOREGISTRY.to_string())),
|
RegisterDomainDrop,
|
||||||
CHECKREGISTERAVAILABILITY.to_lowercase() => Err(NError::NyiOpError(CHECKREGISTERAVAILABILITY.to_string())),
|
// Renew a domain name
|
||||||
CHECKTRANSFERAVAILABILITY.to_lowercase() => Err(NError::NyiOpError(CHECKTRANSFERAVAILABILITY.to_string())),
|
#[command(skip)]
|
||||||
LISTDOMAINS.to_lowercase() => Ok(list_domains),
|
RenewDomain,
|
||||||
GETDOMAININFO.to_lowercase() => Ok(get_domain_info),
|
// Transfer a domain name into your NameSilo account
|
||||||
CONTACTLIST.to_lowercase() => Err(NError::NyiOpError(CONTACTLIST.to_string())),
|
#[command(skip)]
|
||||||
CONTACTADD.to_lowercase() => Err(NError::NyiOpError(CONTACTADD.to_string())),
|
TransferDomain,
|
||||||
CONTACTUPDATE.to_lowercase() => Err(NError::NyiOpError(CONTACTUPDATE.to_string())),
|
// Check the status of a domain transfer
|
||||||
CONTACTDELETE.to_lowercase() => Err(NError::NyiOpError(CONTACTDELETE.to_string())),
|
#[command(skip)]
|
||||||
CONTACTDOMAINASSOCIATE.to_lowercase() => Err(NError::NyiOpError(CONTACTDOMAINASSOCIATE.to_string())),
|
CheckTransferStatus,
|
||||||
CHANGENAMESERVERS.to_lowercase() => Err(NError::NyiOpError(CHANGENAMESERVERS.to_string())),
|
// Add/Change the EPP code for a domain transfer
|
||||||
DNSLISTRECORDS.to_lowercase() => Err(NError::NyiOpError(DNSLISTRECORDS.to_string())),
|
#[command(skip)]
|
||||||
DNSADDRECORD.to_lowercase() => Err(NError::NyiOpError(DNSADDRECORD.to_string())),
|
TransferUpdateChangeEPPCode,
|
||||||
DNSUPDATERECORD.to_lowercase() => Err(NError::NyiOpError(DNSUPDATERECORD.to_string())),
|
// Update a Transfer to Re-Send the Admin Verification Email
|
||||||
DNSDELETERECORD.to_lowercase() => Err(NError::NyiOpError(DNSDELETERECORD.to_string())),
|
#[command(skip)]
|
||||||
DNSSECLISTRECORDS.to_lowercase() => Err(NError::NyiOpError(DNSSECLISTRECORDS.to_string())),
|
TransferUpdateResendAdminEmail,
|
||||||
DNSSECADDRECORD.to_lowercase() => Err(NError::NyiOpError(DNSSECADDRECORD.to_string())),
|
// Update a Transfer to Re-Submit the transfer to the registry
|
||||||
DNSSECDELETERECORD.to_lowercase() => Err(NError::NyiOpError(DNSSECDELETERECORD.to_string())),
|
#[command(skip)]
|
||||||
PORTFOLIOLIST.to_lowercase() => Err(NError::NyiOpError(PORTFOLIOLIST.to_string())),
|
TransferUpdateResubmitToRegistry,
|
||||||
PORTFOLIOADD.to_lowercase() => Err(NError::NyiOpError(PORTFOLIOADD.to_string())),
|
// Determine if up to 200 domains can be registered at this time
|
||||||
PORTFOLIODELETE.to_lowercase() => Err(NError::NyiOpError(PORTFOLIODELETE.to_string())),
|
#[command(skip)]
|
||||||
PORTFOLIODOMAINASSOCIATE.to_lowercase() => Err(NError::NyiOpError(PORTFOLIODOMAINASSOCIATE.to_string())),
|
CheckRegisterAvailability,
|
||||||
LISTREGISTEREDNAMESERVERS.to_lowercase() => Err(NError::NyiOpError(LISTREGISTEREDNAMESERVERS.to_string())),
|
// Determine if up to 200 domains can be transferred into your account at this time
|
||||||
ADDREGISTEREDNAMESERVER.to_lowercase() => Err(NError::NyiOpError(ADDREGISTEREDNAMESERVER.to_string())),
|
#[command(skip)]
|
||||||
MODIFYREGISTEREDNAMESERVER.to_lowercase() => Err(NError::NyiOpError(MODIFYREGISTEREDNAMESERVER.to_string())),
|
CheckTransferAvailability,
|
||||||
DELETEREGISTEREDNAMESERVER.to_lowercase() => Err(NError::NyiOpError(DELETEREGISTEREDNAMESERVER.to_string())),
|
/// A list of all active domains within your account
|
||||||
ADDPRIVACY.to_lowercase() => Err(NError::NyiOpError(ADDPRIVACY.to_string())),
|
ListDomains,
|
||||||
REMOVEPRIVACY.to_lowercase() => Err(NError::NyiOpError(REMOVEPRIVACY.to_string())),
|
/// Get essential information on a domain within your account
|
||||||
ADDAUTORENEWAL.to_lowercase() => Err(NError::NyiOpError(ADDAUTORENEWAL.to_string())),
|
GetDomainInfo {
|
||||||
REMOVEAUTORENEWAL.to_lowercase() => Err(NError::NyiOpError(REMOVEAUTORENEWAL.to_string())),
|
#[arg(short, long)]
|
||||||
RETRIEVEAUTHCODE.to_lowercase() => Err(NError::NyiOpError(RETRIEVEAUTHCODE.to_string())),
|
/// TLD String [example.com]
|
||||||
DOMAINFORWARD.to_lowercase() => Err(NError::NyiOpError(DOMAINFORWARD.to_string())),
|
domain: String,
|
||||||
DOMAINFORWARDSUBDOMAIN.to_lowercase() => Err(NError::NyiOpError(DOMAINFORWARDSUBDOMAIN.to_string())),
|
},
|
||||||
DOMAINFORWARDSUBDOMAINDELETE.to_lowercase() => Err(NError::NyiOpError(DOMAINFORWARDSUBDOMAINDELETE.to_string())),
|
// View all contact profiles in your account
|
||||||
DOMAINLOCK.to_lowercase() => Err(NError::NyiOpError(DOMAINLOCK.to_string())),
|
#[command(skip)]
|
||||||
DOMAINUNLOCK.to_lowercase() => Err(NError::NyiOpError(DOMAINUNLOCK.to_string())),
|
ContactList,
|
||||||
LISTEMAILFORWARDS.to_lowercase() => Err(NError::NyiOpError(LISTEMAILFORWARDS.to_string())),
|
// Add a contact profile to your account
|
||||||
CONFIGUREEMAILFORWARD.to_lowercase() => Err(NError::NyiOpError(CONFIGUREEMAILFORWARD.to_string())),
|
#[command(skip)]
|
||||||
DELETEEMAILFORWARD.to_lowercase() => Err(NError::NyiOpError(DELETEEMAILFORWARD.to_string())),
|
ContactAdd,
|
||||||
REGISTRANTVERIFICATIONSTATUS.to_lowercase() => Err(NError::NyiOpError(REGISTRANTVERIFICATIONSTATUS.to_string())),
|
// Update a contact profile in account
|
||||||
EMAILVERIFICATION.to_lowercase() => Err(NError::NyiOpError(EMAILVERIFICATION.to_string())),
|
#[command(skip)]
|
||||||
GETACCOUNTBALANCE.to_lowercase() => Err(NError::NyiOpError(GETACCOUNTBALANCE.to_string())),
|
ContactUpdate,
|
||||||
ADDACCOUNTFUNDS.to_lowercase() => Err(NError::NyiOpError(ADDACCOUNTFUNDS.to_string())),
|
// Delete a contact profile in account
|
||||||
MARKETPLACEACTIVESALESOVERVIEW.to_lowercase() => Err(NError::NyiOpError(MARKETPLACEACTIVESALESOVERVIEW.to_string())),
|
#[command(skip)]
|
||||||
MARKETPLACEADDORMODIFYSALE.to_lowercase() => Err(NError::NyiOpError(MARKETPLACEADDORMODIFYSALE.to_string())),
|
ContactDelete,
|
||||||
MARKETPLACELANDINGPAGEUPDATE.to_lowercase() => Err(NError::NyiOpError(MARKETPLACELANDINGPAGEUPDATE.to_string())),
|
// Associate contact profiles with a domain
|
||||||
GETPRICES.to_lowercase() => Err(NError::NyiOpError(GETPRICES.to_string())),
|
#[command(skip)]
|
||||||
LISTORDERS.to_lowercase() => Err(NError::NyiOpError(LISTORDERS.to_string())),
|
ContactDomainAssociate,
|
||||||
ORDERDETAILS.to_lowercase() => Err(NError::NyiOpError(ORDERDETAILS.to_string()))
|
// 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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
let op: Option<Result<Operation, NError>> = operation_mapping.remove(&op_name.to_lowercase());
|
#[derive(clap::ValueEnum, Clone)]
|
||||||
match op {
|
/// Resource Type
|
||||||
Some(v) => v,
|
pub(crate) enum ResourceType {
|
||||||
None => Err(NError::InvalidOpError(op_name.to_string()))
|
/// 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<Response, NError> {
|
||||||
|
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<Response, NError> = 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<String, NError> {
|
||||||
|
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 = "._-";
|
||||||
|
|||||||
20
src/lib/cli.rs
Normal file
20
src/lib/cli.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
use crate::api::operations::*;
|
||||||
|
use crate::lib::nerror::NError;
|
||||||
|
use crate::lib::utils::{Response, KeyPath};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// A quickly hacked together CLI Client for the NameSilo API
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
#[command(subcommand)]
|
||||||
|
operation: Operation,
|
||||||
|
#[arg(short, long, default_value_t = KeyPath::new(PathBuf::from(".api_key")))]
|
||||||
|
#[clap(value_name="FILE")]
|
||||||
|
api_key: KeyPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cli() -> Result<Response, NError> {
|
||||||
|
let args = Args::parse();
|
||||||
|
args.operation.call(&args.api_key.get_key()?)
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
#[macro_use]
|
|
||||||
macro_rules! hashmap {
|
macro_rules! hashmap {
|
||||||
($( $key: expr => $val: expr ),*) => {{
|
($( $key: expr => $val: expr ),*) => {{
|
||||||
let mut map = ::std::collections::HashMap::new();
|
let mut map = ::std::collections::HashMap::new();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod nerror;
|
pub mod nerror;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
pub mod cli;
|
||||||
|
pub mod xml;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub enum NError {
|
pub enum NError {
|
||||||
ReqwestError(reqwest::Error),
|
ReqwestError(reqwest::Error),
|
||||||
ParserError(url::ParseError),
|
ParserError(url::ParseError),
|
||||||
@@ -8,7 +9,8 @@ pub enum NError {
|
|||||||
InvalidArgError(String),
|
InvalidArgError(String),
|
||||||
MissArgError(String),
|
MissArgError(String),
|
||||||
InvalidOpError(String),
|
InvalidOpError(String),
|
||||||
NyiOpError(String)
|
NyiOpError(String),
|
||||||
|
RecordNotFound(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for NError {
|
impl fmt::Display for NError {
|
||||||
@@ -17,10 +19,11 @@ impl fmt::Display for NError {
|
|||||||
NError::ReqwestError(e) => <dyn Error as fmt::Display>::fmt(e, f),
|
NError::ReqwestError(e) => <dyn Error as fmt::Display>::fmt(e, f),
|
||||||
NError::ParserError(e) => <dyn Error as fmt::Display>::fmt(e, f),
|
NError::ParserError(e) => <dyn Error as fmt::Display>::fmt(e, f),
|
||||||
NError::ResponseError(e) => write!(f, "Server replied with Status Code [{}]", e),
|
NError::ResponseError(e) => write!(f, "Server replied with Status Code [{}]", e),
|
||||||
NError::InvalidArgError(arg) => write!(f, "Wrong argument [{}]", arg),
|
NError::InvalidArgError(arg) => write!(f, "Invalid argument [{}]", arg),
|
||||||
NError::MissArgError(arg) => write!(f, "Missing argument [{}]", arg),
|
NError::MissArgError(arg) => write!(f, "Missing argument [{}]", arg),
|
||||||
NError::InvalidOpError(op) => write!(f, "Operation [{}] does not exist", op),
|
NError::InvalidOpError(op) => write!(f, "Operation [{}] does not exist", op),
|
||||||
NError::NyiOpError(op) => write!(f, "Operation [{}] is not yet implemented", op)
|
NError::NyiOpError(op) => write!(f, "Operation [{}] is not yet implemented", op),
|
||||||
|
NError::RecordNotFound(rec) => write!(f, "Record [{}] does not exist", rec),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,10 +34,11 @@ impl fmt::Debug for NError {
|
|||||||
NError::ReqwestError(e) => <dyn Error as fmt::Debug>::fmt(e, f),
|
NError::ReqwestError(e) => <dyn Error as fmt::Debug>::fmt(e, f),
|
||||||
NError::ParserError(e) => <dyn Error as fmt::Debug>::fmt(e, f),
|
NError::ParserError(e) => <dyn Error as fmt::Debug>::fmt(e, f),
|
||||||
NError::ResponseError(e) => write!(f, "Server replied with Status Code [{}]", e),
|
NError::ResponseError(e) => write!(f, "Server replied with Status Code [{}]", e),
|
||||||
NError::InvalidArgError(arg) => write!(f, "Wrong argument [{}]", arg),
|
NError::InvalidArgError(arg) => write!(f, "Invalid argument [{}]", arg),
|
||||||
NError::MissArgError(arg) => write!(f, "Missing argument [{}]", arg),
|
NError::MissArgError(arg) => write!(f, "Missing argument [{}]", arg),
|
||||||
NError::InvalidOpError(op) => write!(f, "Operation [{}] does not exist", op),
|
NError::InvalidOpError(op) => write!(f, "Operation [{}] does not exist", op),
|
||||||
NError::NyiOpError(op) => write!(f, "Operation [{}] is not yet implemented", op)
|
NError::NyiOpError(op) => write!(f, "Operation [{}] is not yet implemented", op),
|
||||||
|
NError::RecordNotFound(rec) => write!(f, "Record [{}] does not exist", rec),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/lib/utils.rs
100
src/lib/utils.rs
@@ -1,12 +1,67 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::api::constants::*;
|
use std::{fmt, fs};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use crate::api::operations::*;
|
||||||
use crate::lib::nerror::NError;
|
use crate::lib::nerror::NError;
|
||||||
use reqwest::Url;
|
use reqwest::Url;
|
||||||
use reqwest::StatusCode;
|
use reqwest::StatusCode;
|
||||||
|
|
||||||
|
// PathBuf wrapper to impl Display
|
||||||
|
pub(crate) struct KeyPath(PathBuf);
|
||||||
|
|
||||||
|
impl KeyPath {
|
||||||
|
pub(crate) fn new(path: PathBuf) -> KeyPath {
|
||||||
|
KeyPath(path)
|
||||||
|
}
|
||||||
|
pub(crate) fn get_key(&self) -> Result<String, NError> {
|
||||||
|
match fs::read_to_string(&self.0) {
|
||||||
|
Ok(key) => Ok(key),
|
||||||
|
Err(e) => Err(NError::InvalidArgError(format!("api_key: {} ({})", self, e.to_string())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KeyPath {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0.display())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::Deref for KeyPath {
|
||||||
|
type Target = PathBuf;
|
||||||
|
fn deref(self: &'_ Self) -> &'_ Self::Target
|
||||||
|
{
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::ops::DerefMut for KeyPath {
|
||||||
|
fn deref_mut(self: &'_ mut Self) -> &'_ mut Self::Target
|
||||||
|
{
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for KeyPath {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
KeyPath(self.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for KeyPath {
|
||||||
|
type Err = NError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(KeyPath(std::path::PathBuf::from(s)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn parse_args(args: &str, key_filter: (&[Range], &str), value_filter: (&[Range], &str)) -> HashMap<String, String> {
|
pub(crate) fn parse_args(args: &str, key_filter: (&[Range], &str), value_filter: (&[Range], &str)) -> HashMap<String, String> {
|
||||||
let args_vector: Vec<&str> = args.split_ascii_whitespace().collect();
|
let args_vector: Vec<&str> = args.split_ascii_whitespace().collect();
|
||||||
if args_vector.is_empty() { return HashMap::new() }
|
if args_vector.is_empty() { return HashMap::new(); }
|
||||||
let mut args_map: HashMap<String, String> = HashMap::new();
|
let mut args_map: HashMap<String, String> = HashMap::new();
|
||||||
for arg in args_vector {
|
for arg in args_vector {
|
||||||
let kv: Vec<&str> = arg.split(KEY_VALUE_SEP).collect();
|
let kv: Vec<&str> = arg.split(KEY_VALUE_SEP).collect();
|
||||||
@@ -17,7 +72,13 @@ pub(crate) fn parse_args(args: &str, key_filter: (&[Range], &str), value_filter:
|
|||||||
args_map
|
args_map
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn sanitize_string(s: &str, ranges: &[Range], whitelist: &str) -> String {
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn parse_arg(arg: &str, value_filter: (&[Range], &str)) -> String {
|
||||||
|
sanitize_string(arg, &value_filter.0, &value_filter.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn sanitize_string(s: &str, ranges: &[Range], allowlist: &str) -> String {
|
||||||
let s_lower: String = s.to_lowercase();
|
let s_lower: String = s.to_lowercase();
|
||||||
let mut result: String = String::new();
|
let mut result: String = String::new();
|
||||||
|
|
||||||
@@ -30,15 +91,35 @@ pub(crate) fn sanitize_string(s: &str, ranges: &[Range], whitelist: &str) -> Str
|
|||||||
};
|
};
|
||||||
|
|
||||||
for c in s_lower.chars() {
|
for c in s_lower.chars() {
|
||||||
if whitelist.contains(c) || in_range(c) {
|
if allowlist.contains(c) || in_range(c) {
|
||||||
result.push(c);
|
result.push(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_string(s: &String, ranges: &[Range], allowlist: &str) -> bool {
|
||||||
|
let s_lower: String = s.to_lowercase();
|
||||||
|
|
||||||
|
// helper function to check if c is in any range
|
||||||
|
let in_range = |c: char| {
|
||||||
|
for range in ranges {
|
||||||
|
if c >= range.0 && c <= range.1 { return true; }
|
||||||
|
}
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
for c in s_lower.chars() {
|
||||||
|
if !(allowlist.contains(c) || in_range(c)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn check_args(args: &HashMap<String, String>, required: &[&str], optional: &[&str]) -> Result<(), NError> {
|
pub(crate) fn check_args(args: &HashMap<String, String>, required: &[&str], optional: &[&str]) -> Result<(), NError> {
|
||||||
|
|
||||||
// check if all required fields are present
|
// check if all required fields are present
|
||||||
@@ -68,13 +149,14 @@ pub(crate) fn send_request(url: Url) -> Result<String, NError> {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn send_api_request(operation: &str, api_key: &str, args: &HashMap<String, String>) -> Result<String, NError> {
|
pub(crate) fn send_api_request(url: &String) -> Result<Response, NError> {
|
||||||
let url: String = api_url(operation, api_key, &args);
|
println!("Request: {}", url);
|
||||||
Ok(send_request(reqwest::Url::parse(url.as_str())?)?)
|
Ok(Response::with_string(send_request(reqwest::Url::parse(url.as_str())?)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Response {
|
pub struct Response {
|
||||||
string: String
|
string: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Response {
|
impl Response {
|
||||||
|
|||||||
18
src/lib/xml.rs
Normal file
18
src/lib/xml.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
pub(crate) fn find_node<'a>(node: &'a roxmltree::Node<'a, 'a>, tag_name: &'a str) -> Option<roxmltree::Node<'a, 'a>> {
|
||||||
|
node.descendants().find(
|
||||||
|
|n| n.has_tag_name(tag_name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn find_record_id(domain: &str, resource_type: &str, xml: &roxmltree::Document) -> Option<String> {
|
||||||
|
let root = xml.root();
|
||||||
|
let reply_node = find_node(&root, "reply")?;
|
||||||
|
for node in reply_node.descendants() {
|
||||||
|
if node.has_tag_name("resource_record") {
|
||||||
|
if find_node(&node, "type")?.text()? == resource_type && find_node(&node, "host")?.text()? == domain {
|
||||||
|
return Some(find_node(&node, "record_id")?.text()?.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
17
src/main.rs
17
src/main.rs
@@ -1,22 +1,13 @@
|
|||||||
|
extern crate core;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod lib;
|
mod lib;
|
||||||
mod api;
|
mod api;
|
||||||
|
|
||||||
use crate::lib::nerror::NError;
|
use crate::lib::nerror::NError;
|
||||||
use crate::api::operations::get_operation;
|
use crate::lib::cli::cli;
|
||||||
|
|
||||||
fn main() -> Result<(), NError> {
|
fn main() -> Result<(), NError> {
|
||||||
|
println!("{:#?}", cli()?);
|
||||||
let api_key = "";
|
|
||||||
let domain = "";
|
|
||||||
|
|
||||||
let result = get_operation("listDomains")?(api_key, "")?;
|
|
||||||
|
|
||||||
println!("result: {}", result.get_string());
|
|
||||||
|
|
||||||
let result = get_operation("getDomainInfo")?(api_key, domain)?;
|
|
||||||
|
|
||||||
println!("result: {}", result.get_string());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user