// 	file: check_usage_internode.go
// 	Version 0.3 (05/08/2016)
//
// check_usage_internode is a Nagios plugin made by Edwin van Ree (support at vanree.com)
// to monitor Internode Internet download usage.
//
// I have used the Google Go programming language because of no need to install any libraries.
// This script needs to be compiled with go build check_uage_adam.go on your target platform (e.g. Ubuntu).
//
// The plug-in uses the Internode Internet XML API via HTTPS to check the amount of download limit left.
// Note: If you wish to create your own usage meter interface, do not copy the
//       interface from this program, please contact Internode via
//       http://www.internode.on.net/contact/support/ for the API document.
//
// This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
// It may be used, redistributed and/or modified under the terms of the GNU
// General Public Licence (see http://www.fsf.org/licensing/licenses/gpl.txt).
//
// tested with:
// 	1. Internode Internet API version 1.5
//
// changelog:
//  Version 0.3 (05/08/2016) allow quota of 0 to be unlimited
//  Version 0.2 (30/08/2013) added days until rollover in usage_percent and compile notice
// 	Version 0.1 (21/08/2013) initial release (kindly started from check_usage_adam.go)
//
// todo:
//  add history bit
//
// flags:
// 	-u=<username>		Internode Internet user name
//  -p=<account name>	Internode Internet password
//  -c=<critical level> Critical level value, default = 90
//  -w=<warning level>  Warning level value, default = 70
//  -i=<info>   		Info to retrieve, values of <info> are:
//		services			List of services, use this command first to determine your SERVICE_ID (SID) for info below
//		resources			List of resources for given SID
//		usage_percent		Calculate the used percentage of the downloaded traffic, return value set according the given critical and warning levels
//		usage				Return the quota and used amount in MB + percentage gone + rollover date and plan interval, return value always OK
//		service_plan		Return the service plan info (plan, carrier, speed, rating & cost), return value always OK
//		service_excess		Return the service excess info (cost, charged, shaped, restrict), return value always OK
//  -SID=<sid>			Service ID number for detailed info requests
//	-d=<level>			print debug, level: 1 errors only, 2 warnings and 3 informational messages
//	-E 					print environment variables for debug purpose
//	-V					print plugin version
//
// usage examples:
//
//  Get list of services on the given Internode account:
// 	$ go run check_usage_internode -u=username -p=password
// 	Internode - OK - U=username,Services=1:(1234567)
//
//  Get usage info for the given Internode account and service:
// 	$ go run check_usage_internode -u=username -p=password -SID=1234567 -i=usage
// 	Internode - OK - U=username,SID=1234567,Usage:(quota=300000MB, used=150000MB, gone=50%, rollover=2013-10-01, interval=Monthly)
//
package main

import (
	"crypto/tls"
	"encoding/xml"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"time"
)

const (
	version       = "0.1"
	debug_Error   = 1
	debug_Warning = 2
	debug_Info	  = 3
)

type (
	Services struct {
		XMLName					xml.Name	`xml:"services"`
		Count					string		`xml:"count,attr"`
		Service					[]string	`xml:"service"`
	}
	
	API1 struct {
		XMLName					xml.Name	`xml:"api"`
		Services				Services
	}
	
	Internode1 struct {
		XMLName					xml.Name	`xml:"internode"`
		API						API1
	}

	Resources struct {
		XMLName					xml.Name	`xml:"resources"`
		Count					string		`xml:"count,attr"`
		Resource				[]string	`xml:"resource"`
	}
	
	API2 struct {
		XMLName					xml.Name	`xml:"api"`
		Service					string		`xml:"service"`
		Resources				Resources
	}
	
	Internode2 struct {
		XMLName					xml.Name	`xml:"internode"`
		API						API2
	}

	Traffic struct {
		XMLName					xml.Name	`xml:"traffic"`
		Name					string		`xml:"name,attr"`
		Unit					string		`xml:"unit,attr"`
		Rollover				string		`xml:"rollover,attr"`
		PlanInterval			string		`xml:"plan-interval,attr"`
		Quota					string		`xml:"quota,attr"`
		Value					string		`xml:",innerxml"`
	}
	
	API3 struct {
		XMLName					xml.Name	`xml:"api"`
		Service					string		`xml:"service"`
		Traffic					Traffic
	}
	
	Internode3 struct {
		XMLName					xml.Name	`xml:"internode"`
		API						API3
	}
	
	Usage struct {
		XMLName					xml.Name	`xml:"usage"`
		Day						string		`xml:"day,attr"`
		Traffic					Traffic
	}
	
	UsageList struct {
		XMLName					xml.Name	`xml:"usagelist"`
		Usage					[]Usage
	}
	
	API4 struct {
		XMLName					xml.Name	`xml:"api"`
		Service					string		`xml:"service"`
		UsageList				UsageList
	}
	
	Internode4 struct {
		XMLName					xml.Name	`xml:"internode"`
		API						API4
	}
	
	Service struct {
		XMLName					xml.Name	`xml:"service"`
		Type					string		`xml:"type,attr"`
		Request					string		`xml:"request,attr"`
		ID						string		`xml:"id"`
		Username				string		`xml:"username"`
		Quota					string		`xml:"quota"`
		Plan					string		`xml:"plan"`
		Carrier					string		`xml:"carrier"`
		Speed					string		`xml:"speed"`
		UsageRating				string		`xml:"usage-rating"`
		Rollover				string		`xml:"rollover"`
		ExcessCost				string		`xml:"excess-cost"`
		ExcessCharged			string		`xml:"excess-charged"`
		ExcessShaped			string		`xml:"excess-shaped"`
		ExcessRestrictAccess	string		`xml:"excess-restrict-access"`
		PlanInterval			string		`xml:"plan-interval"`
		PlanCost				string		`xml:"plan-cost"`
	}
	
	API5 struct {
		XMLName					xml.Name	`xml:"api"`
		Service					Service
	}

	Internode5 struct {
		XMLName					xml.Name	`xml:"internode"`
		API						API5
	}
)

var (
	user	     	string
	password	 	string
	debug        	int
	showEnv      	bool
	showVersion  	bool
	proxyString  	string
	critical	 	int
	warning		 	int
	info		 	string
	percentF	 	float32
	IPaddress	 	string
	SID			 	string
	count		 	int64
	i			 	int64
	downloadUsage	int64
	downloadQuota	int64
	err				error
)

func debugPrintf(level int, format string, a ...interface{}) {
	if level <= debug {
		log.Printf(format, a...)
	}
}

func init() {
	flag.StringVar(&user, "u", "<must be specified>", "Internode Internet username")
	flag.StringVar(&password, "p", "<must be specified>", "Internode Internet password")
	flag.IntVar(&debug, "d", 0, "print debug, level: 1 errors only, 2 warnings and 3 informational messages")
	flag.BoolVar(&showEnv, "E", false, "print environment variables for debug purpose")
	flag.BoolVar(&showVersion, "V", false, "print plugin version")
	flag.StringVar(&proxyString, "P", "", "proxy URL")
	flag.StringVar(&info, "i", "services", "Info requested")
	flag.StringVar(&IPaddress, "IP", "<must be specified>", "IP address to check")
	flag.IntVar(&critical, "c", 90, "critical level")
	flag.IntVar(&warning, "w", 70, "warning level")
	flag.StringVar(&SID, "SID", "<must be specified>", "Service ID to check")
}

func SendRequest(reqType int) (body []byte) {
	// build the URL for the request type
	url := "https://customer-webtools-api.internode.on.net/api/v1.5/"
	switch reqType {
		case 1:											// Services
		case 2:											// Resources
			url += fmt.Sprintf("%s", SID)
		case 3:											// Usage
			url += fmt.Sprintf("%s", SID)
			url += "/usage"
		case 4:											// History
			url += fmt.Sprintf("%s", SID)
			url += "/history"
		case 5:											// Service
			url += fmt.Sprintf("%s", SID)
			url += "/service"
	}
	debugPrintf(debug_Info, "url: %s\n", url)

	client := &http.Client{
		Transport: &http.Transport{
			Proxy:           http.ProxyFromEnvironment,
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		},
	}

	req, err := http.NewRequest("GET", url, nil)
	req.SetBasicAuth(user, password)
	req.Header.Set("User-Agent", "NagiosCheckUsage/0.01")
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}

	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	
	resp.Body.Close()

	debugPrintf(debug_Info, "http status code: %s\n", resp.Status)
	debugPrintf(debug_Info, "http body: \n%s\n", body)
	return body
}

func main() {
	flag.Parse()

	// send errors to Stdout instead to Stderr
	// http://nagiosplug.sourceforge.net/developer-guidelines.html#PLUGOUTPUT
	log.SetOutput(os.Stdout)
	if showEnv {
		log.Printf("** environment variables start **\n")
		for _, v := range os.Environ() {
			log.Printf("%s\n", v)
		}
		log.Printf("** environment variables end **\n")
	}
	if showVersion {
		fmt.Printf("%s version: %s\n", path.Base(os.Args[0]), version)
		os.Exit(0)
	}

	output := "Internode - "
	ret_val := 0

	switch info {
		case "services":
			body := SendRequest(1)
			xmlResponse := &Internode1{}
			err := xml.Unmarshal(body, &xmlResponse)
			if err != nil {
				fmt.Printf("Decoder error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse)

			debugPrintf(debug_Info, "# Services: %d\n", xmlResponse.API.Services.Count)
			
			output += "OK - U="
			output += fmt.Sprintf("%s", user)
			output += ",Services="
			count, err = strconv.ParseInt(xmlResponse.API.Services.Count, 10, 32)
			if err != nil {
				fmt.Printf("Count conversion error: %v\n", err)
				os.Exit(3)
			}
			output += fmt.Sprintf("%d", count)
			output += ":("
			for i = 0; i < count; i++ {
				if i > 0 {output += ","}
				output += fmt.Sprintf("%s", xmlResponse.API.Services.Service[i])
			}
			output += ")"

		case "resources":
			body := SendRequest(2)
			xmlResponse := &Internode2{}
			err := xml.Unmarshal([]byte(body), &xmlResponse)
			if err != nil {
				fmt.Printf("Decoder error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse)

			debugPrintf(debug_Info, "# Services: %d\n", xmlResponse.API.Resources.Count)
			
			output += "OK - U="
			output += fmt.Sprintf("%s", user)
			output += ",SID="
			output += fmt.Sprintf("%s", SID)
			output += ",Resources="
			count, err = strconv.ParseInt(xmlResponse.API.Resources.Count, 10, 32)
			if err != nil {
				fmt.Printf("Count conversion error: %v\n", err)
				os.Exit(3)
			}
			output += fmt.Sprintf("%d", count)
			output += ":("
			for i = 0; i < count; i++ {
				if i > 0 {output += ","}
				output += fmt.Sprintf("%s", xmlResponse.API.Resources.Resource[i])
			}
			output += ")"

		case "usage":
			body :=SendRequest(3)
			xmlResponse := &Internode3{}
			err := xml.Unmarshal([]byte(body), &xmlResponse)
			if err != nil {
				fmt.Printf("Decoder error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse)

			output += "OK - U="
			output += fmt.Sprintf("%s", user)
			output += ",SID="
			output += fmt.Sprintf("%s", SID)
			downloadUsage, err = strconv.ParseInt(xmlResponse.API.Traffic.Value, 10, 64)
			if err != nil {
				fmt.Printf("Usage conversion error: %v\n", err)
				os.Exit(3)
			}
			downloadQuota, err = strconv.ParseInt(xmlResponse.API.Traffic.Quota, 10, 64)
			if err != nil {
				fmt.Printf("Quota conversion error: %v\n", err)
				os.Exit(3)
			}
			output += ",Usage:(quota="
			if downloadQuota == 0 {
				output += "unlimited "
			} else {
				output += fmt.Sprintf("%d", downloadQuota / 1000000)
			}
			output += "MB, used="
			output += fmt.Sprintf("%d", downloadUsage / 1000000)
			output += "MB, gone="
			if downloadQuota == 0 {
				output += "N/A"
			} else {
				percentF = float32(downloadUsage) / float32(downloadQuota) * 100.0
				percent := int(percentF)
				output += fmt.Sprintf("%d", percent )
			}
			output += "%, rollover="
			output += fmt.Sprintf("%s", xmlResponse.API.Traffic.Rollover)
			output += ", interval="
			output += fmt.Sprintf("%s", xmlResponse.API.Traffic.PlanInterval)
			output += ")"

		case "usage_percent":
			body := SendRequest(3)
			xmlResponse := &Internode3{}
			err = xml.Unmarshal([]byte(body), &xmlResponse)
			if err != nil {
				fmt.Printf("Decoder error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse)

			downloadUsage, err = strconv.ParseInt(xmlResponse.API.Traffic.Value, 10, 64)
			if err != nil {
				fmt.Printf("Usage conversion error: %v\n", err)
				os.Exit(3)
			}
			
			rolloverString := xmlResponse.API.Traffic.Rollover
			debugPrintf(debug_Info, "Rollover string: %s\n", rolloverString)
			const longForm = "2006-01-02"
			rolloverDate, err := time.Parse(longForm, rolloverString)
			if err != nil {
				fmt.Printf("Rollover Date conversion error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Rollover Date: %v\n", rolloverDate)
			timeToGo := rolloverDate.Sub(time.Now())
			daysToGo := timeToGo.Hours() / 24
			debugPrintf(debug_Info, "Time to go: %v\n", daysToGo)

			downloadQuota, err = strconv.ParseInt(xmlResponse.API.Traffic.Quota, 10, 64)
			if err != nil {
				fmt.Printf("Quota conversion error: %v\n", err)
				os.Exit(3)
			}
			
			if downloadQuota == 0 {
				output += "OK - Quota was 0 (UNLIMITED)"
				ret_val = 0
			} else {
				percentF = float32(downloadUsage) / float32(downloadQuota) * 100.0
				percent := int(percentF)
				debugPrintf(debug_Info, "Download Percent: %d\n", percent)
				switch {
					case percent >= critical: 
						output += "CRITICAL"
						ret_val = 2
					case percent >= warning:
						output += "WARNING"
						ret_val = 1
					default:
						output += "OK"
						ret_val = 0
				}
				output += " - Used: "
				output += fmt.Sprintf("%d", percent)
				output += "% ("
				output += fmt.Sprintf("%3.1f", daysToGo)
				output += " days to go)"
			}

		case "service_plan":
			body :=SendRequest(5)
			xmlResponse := &Internode5{}
			err := xml.Unmarshal([]byte(body), &xmlResponse)
			if err != nil {
				fmt.Printf("Decoder error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse)

			output += "OK - U="
			output += fmt.Sprintf("%s", user)
			output += ",SID="
			output += fmt.Sprintf("%s", SID)
			output += ",Service:(plan="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.Plan)
			output += ", carrier="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.Carrier)
			output += ", speed="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.Speed)
			output += ", rating="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.UsageRating)
			output += ", cost="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.PlanCost)
			output += ")"

		case "service_excess":
			body :=SendRequest(5)
			xmlResponse := &Internode5{}
			err := xml.Unmarshal([]byte(body), &xmlResponse)
			if err != nil {
				fmt.Printf("Decoder error: %v\n", err)
				os.Exit(3)
			}
			debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse)

			output += "OK - U="
			output += fmt.Sprintf("%s", user)
			output += ",SID="
			output += fmt.Sprintf("%s", SID)
			output += ",Service-excess:(cost="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessCost)
			output += ", charged="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessCharged)
			output += ", shaped="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessShaped)
			output += ", restrict="
			output += fmt.Sprintf("%s", xmlResponse.API.Service.ExcessRestrictAccess)
			output += ")"

		default:
			fmt.Printf("Unknown Info request name: %s\n", info)
			os.Exit(3)
	}
	
	fmt.Printf("%s\n", output)
	os.Exit(ret_val)
}