diff --git a/remoting_client/cli/README.md b/remoting_client/cli/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8f6404d3ce815e0faa3e5cba65b294f11ebc7160 --- /dev/null +++ b/remoting_client/cli/README.md @@ -0,0 +1,38 @@ +# Project Title +Command line for ISPConfig remote user REST API using either smart functions or raw methods. + +## Getting Started +This tool can be used and packaged stand alone, without ISPConfig itself. It is designed to have as few dependencies as possible. + +The script has two main modes: method wrapping and functions. Method wrapping already works nicely, and simply makes properly formatted requests with curl, +making it a handy tool for custom requests, testing, automation and scripting. Functions in turn are combinations of methods and checks that act more like an intelligent tool and does not require the user +to understand JSON. + +If you want to script using functions, consider using -q. + +Example method: +ispconfig-cli -m login -j credentials.json +{"code":"ok","message":"","response":"dc39619b0ac9694cb85e93d8b3ac8258"} + +Example function: +ispconfig-cli -f "dns_a_add example.com. www 192.168.0.2" +DNS zone example.com. has id 1. +DNS A www exists with id 228, updating... +Updated records: 1 + +The script uses an optional config file, making commands short as above. + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for +notes on how to deploy the project on a live system. + +### Dependencies +1) jq +2) curl + +### Installing +1) Place this script in your path, for example in your ~/bin folder on many distros. +2) Make it executable ```chmod 755 ispconfig-cli``` +3) Optionally create a config file in /etc/ispconfig-cli.conf or ~/.ispconfig-cli + +## Details on usage +Run the script without arguments for the full list of functionality and config file creation. diff --git a/remoting_client/cli/ispconfig-cli b/remoting_client/cli/ispconfig-cli new file mode 100755 index 0000000000000000000000000000000000000000..91f56caafca34e2d0cb99077c7adbe97599011bc --- /dev/null +++ b/remoting_client/cli/ispconfig-cli @@ -0,0 +1,407 @@ +#!/bin/bash + +# Command line for ISPConfig remote user REST API using either smart functions or raw methods. +# Author: Johan Ehnberg, johan@molnix.com + +set -e + +### Variables + +VERSION=2018-04-23 +PROGNAME=$(basename $0) +DATESTAMP=`date +%s` + +### Common commands + +# usage +usage() { + echo "Command line for ISPConfig remote user REST API using either smart functions or raw methods." + echo + echo "Usage: $PROGNAME -h/-f /-m [options] ..." + echo + echo "Common options:" + echo + echo " -a " + echo " Config file. Options are read in the following order: system configuration, user configuration, the file specified in this option, other command line options. The last occurrence overrides previous ones." + echo + echo " -e " + echo " API endpoint url such as https://myserver.example.com:8080/remote/json.php ." + echo + echo " -h" + echo " Show this help text." + echo + echo " -i " + echo " Server id (defaults to 1)." + echo + echo " -k" + echo " Do not validate server certificate (for self-signed certificates)." + echo + echo " -q" + echo " Quiet, only outputs results." + echo + echo " -v" + echo " Verbose, outputs all info where available." + echo + echo "Functions combine several methods to carry out common tasks. Function options:" + echo + echo " -c " + echo " Client user name to use." + echo + echo " -f " + echo " Function to perform, such as dns_a_add. Cannot be used with -m, -d, -j or -s." + echo + echo " -p " + echo " Password of the remote user specified in ISPConfig." + echo + echo " -u " + echo " Name of the remote user specified in IPSConfig." + echo + echo " Available functions:" + echo + echo " clients List all clients" + echo " dns_a_add Add or update a DNS A record" + echo " dns_a_delete Delete a DNS A record" + echo " dns_rr List all records for a zone" + echo " dns_zones List all zones" + echo " log_in Create a session (not needed by default)" + echo " log_out Log out a session (not needed by default)" + echo + echo "Using a method wraps the raw request on the command line. Method options:" + echo + echo " -d " + echo " Read JSON data from command line. Use escapes and quotes! Cannot be used with -j or -s." + echo + echo " -j " + echo " Read JSON data from file. Cannot be used with -d or -s." + echo + echo " -m " + echo " Raw method to use such as dns_a_add. Cannot be used with -f. Requires one of -d, -j or -s." + echo + echo " -s" + echo " Read JSON data from stdin. Cannot be used with -d or -j." + echo + echo "For details on methods, see:" + echo "https://git.ispconfig.org/ispconfig/ispconfig3/tree/master/remoting_client/API-docs" + echo + echo "Config files are bash files that can contain the following variables (with examples):" + echo + echo "remote_user=myuser # see -u" + echo "remote_password=mypassword # see -p" + echo "remote_url=https://myserver.example.com:8080/remote/json.php # see -e" + echo "client_user=myclient # see -c" + echo "server_id=1 # see -i" + echo "ssl_validate=off # see -k" + echo + echo "Example uses:" + echo " 1. Update a DNS A record or update if it already exists" + echo " ispconfig-cli -f \"dns_a_add example.com. johnscomputer 192.168.0.99\"" + echo +} + +# message verbosity message +message() { + if [ $1 -le $VERBOSITY ]; then + MESSAGE="${MESSAGE}${2}" + fi +} + +# fail dump +fail() { + echo -e "$MESSAGE" + echo "Something went wrong. Here is the last response:" + echo $1 + log_out + exit 1 +} + +# restCall method data +restCall() { + curl $CURLK -sS -X POST -H "Content-Type: application/json" -H "Cache-Control: no-cache" -d "${2}" "${remote_url}?${1}" +} + +# method method +method() { + restCall $1 "$JSONDATA" +} + +### Functions + +# List all clients +# clients +clients() { + clientGet | jq .response +} + +# Add or update an A record +# dns_a_add zone name ip +dns_a_add() { + dns_zone_id $1 + dns_a_id $1 $2 + if [[ $dns_a_id =~ ^-?[0-9]+$ ]]; then + message 2 "DNS A $2 exists with id $dns_a_id, updating rows: " + message 1 "`dnsUpdateAByIdAndIp $dns_a_id $3 | jq -r .response`" + elif [[ $dns_a_id == "" ]]; then + message 2 "DNS A $2 does not exist, created ID: " + message 1 "`dnsAddAByZoneAndNameAndIp $dns_zone_id $2 $3 | jq -r .response`\n" + else + fail $dns_a_id + fi +} + +# Delete an A record if it exists +# dns_a_delete zone name +dns_a_delete() { + dns_zone_id $1 + dns_a_id $1 $2 + if [[ $dns_a_id =~ ^-?[0-9]+$ ]]; then + message 2 "DNS A $2 has id $dns_a_id, deleted rows: " + message 1 "`dnsDeleteAById $dns_a_id | jq -r '.response'`\n" + else + message 2 "DNS A $2 does not exist, skipping.\n" + fi +} + +# List all records for a zone +# dns_rr zone +dns_rr() { + dns_zone_id $1 + dnsGetRrByZone $dns_zone_id | jq .response +} + +# List all zones +# dns_zones +dns_zones() { + dnsGetZones | jq .response +} + +# Log in +log_in() { + session_id=`restCall login "{\"username\": \"${remote_user}\",\"password\": \"${remote_password}\"}" | jq -r '.response'` + if [[ $session_id == "false" ]]; then + fail "Login failed!" + else + message 3 "Logged in with session_id $session_id as $remote_user.\n" + client_id $client_user + fi +} + +# Log out +# log_out session +log_out() { + if [[ `restCall logout "{\"session_id\": \"${1}\"}" |jq -r .response` == "true" ]]; then + message 3 "Logged out session $1 successfully.\n" + else + fail "Logging out failed!" + fi +} + +### ID helpers +# Due to bash limitations, id's should only be set in _id functions below. +# Do not run subshells anywhere else. +# This is due to subshell nesting will otherwise lose the variables. + +# Get client id +# client_id client_user +client_id() { + client_id=`clients | jq -r ".[] | select(.username == \"${1}\") | .client_id"` + message 3 "Client $1 has id $client_id.\n" +} + +# Get dns A id +# dns_a_id zone name +dns_a_id() { + dns_a_id=`dns_rr $1 | jq -r ".[] | select(.name == \"${2}\") | .id"` + message 3 "DNS A $2 has id $dns_a_id.\n" +} + +# Get zone id +# dns_zone_id zone +dns_zone_id() { + dns_zone_id=`dns_zones | jq -r ".[] | select(.origin == \"${1}\") | .id"` + message 3 "DNS zone $1 has id $dns_zone_id.\n" +} + +### Methods + +# Get clients +clientGet() { + restCall client_get "{\"session_id\": \"${session_id}\",\"client_id\":{}}" +} + +# Add A by zone and name and IP +dnsAddAByZoneAndNameAndIp() { + restCall dns_a_add "{\"session_id\": \"${session_id}\",\"client_id\": \"${client_id}\",\"params\": {\"server_id\": ${SERVER},\"zone\": \"${1}\",\"name\": \"${2}\",\"type\": \"a\",\"data\": \"${3}\",\"aux\": \"0\", \"ttl\": \"3600\", \"active\": \"y\", \"stamp\": \"${datestamp}\", \"serial\": \"1\"}}" +} + +# Delete A by id +dnsDeleteAById() { + restCall dns_a_delete "{\"session_id\": \"${session_id}\",\"primary_id\": \"${1}\"}" +} + +# Get RR by id +dnsGetRrByZone() { + restCall dns_rr_get_all_by_zone "{\"session_id\": \"${session_id}\",\"zone_id\": \"${1}\"}" +} + +# Get zones +dnsGetZones() { + restCall dns_zone_get_by_user "{\"session_id\": \"${session_id}\",\"client_id\": \"${client_id}\",\"server_id\": ${SERVER}}" +} + +# Update A by id and IP +dnsUpdateAByIdAndIp() { + restCall dns_a_update "{\"session_id\": \"${session_id}\",\"client_id\": \"${client_id}\",\"primary_id\": \"${1}\",\"params\": {\"data\": \"${2}\", \"stamp\": \"${datestamp}\"}}" +} + +### Run + +# Check dependencies +if ! [ -x "$(command -v curl)" ]; then + echo 'Error: curl is not installed.' >&2 + exit 1 +fi +if ! [ -x "$(command -v jq)" ]; then + echo 'Error: jq is not installed.' >&2 + exit 1 +fi + +# Check config files +if [ -r /etc/ispconfig-cli.conf ]; then + . /etc/ispconfig-cli.conf +fi +if [ -r ~/.ispconfig-cli ]; then + . ~/.ispconfig-cli +fi + +# Check command line +if [[ $1 == "" ]]; then + usage + exit 1 +fi +SERVER=1 +VERBOSITY=2 +while getopts :a:e:hi:kqvc:f:p:u:d:j:m:s opt; do + case $opt in + a) + if [[ -e $OPTARG ]]; then + source $OPTARG + else + echo "Config file $OPTARG not found!" + exit 1 + fi + ;; + e) + remote_url=$OPTARG + ;; + h) + usage + exit 1 + ;; + i) + SERVER=$OPTARG + ;; + k) + $ssl_validate=off + ;; + q) + if [[ $VERBOSITY == "2" ]]; then + VERBOSITY=1 + else + echo "-q and -v cannot be specified at the same time!" + exit 1 + fi + ;; + v) + if [[ $VERBOSITY == "2" ]]; then + VERBOSITY=3 + else + echo "-q and -v cannot be specified at the same time!" + exit 1 + fi + ;; + c) + client_user=$OPTARG + ;; + f) + if [[ $METHOD == "" ]]; then + FUNCTION=$OPTARG + else + echo "Function and method cannot be specified at the same time!" + exit 1 + fi + ;; + p) + remote_password=$OPTARG + ;; + u) + remote_user=$OPTARG + ;; + d) + if [[ $JSONMODE == "" ]]; then + JSONMODE=cli + JSONDATA=$OPTARG + else + echo "You can only use one JSON data source!" + exit 1 + fi + ;; + j) + if [[ $JSONMODE == "" ]]; then + JSONMODE=fil + JSONDATA=`cat $OPTARG` + else + echo "You can only use one JSON data source!" + exit 1 + fi + ;; + m) + if [[ $FUNCTION == "" ]]; then + METHOD=$OPTARG + else + echo "Function and method cannot be specified at the same time!" + exit 1 + fi + ;; + s) + if [[ $JSONMODE == "" ]]; then + JSONMODE=std + JSONDATA=`cat` + else + echo "You can only use one JSON data source!" + exit 1 + fi + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit 1 + ;; + esac +done +if [[ $remote_url == "" ]]; then + echo "No url provided!" + exit 1 +fi +if [[ $ssl_validate == "off" ]]; then + CURLK="-k" +fi +if [[ $METHOD != "" ]]; then + method $METHOD +elif [[ $FUNCTION == log_* ]]; then + $FUNCTION +elif [[ $FUNCTION != "" ]]; then + log_in + $FUNCTION + log_out $session_id +else + echo "Neither method nor functions specified!" + exit 1 +fi + +echo -e $MESSAGE + +exit 0 +