// file: check_usage_adam.go // Version 0.3 (30/08/2013) // // check_usage_adam is a Nagios plugin made by Edwin van Ree (support at vanree.com) // to monitor Adam Internet download usage. // // I have used the Google Go progamming 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 plugin uses the Adam Internet XML API via HTTPS to check the amount of download limit left. // see also http://www.adam.com.au/info/api. User must first create a security token in their members area. // // 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. Adam Internet API version 1.0 // // changelog: // Version 0.3 (30/08/2013) added days until rollover in usage_percent and compile notice // Version 0.2 (21/08/2013) different account type failed to decode properly // Version 0.1 (14/08/2013) initial release (kindly started from Herwig Grimm's check_cisco_ucs.go) // // todo: // // flags: // -t= Adam Internet security token (like AABBCCDDEEFF) // -u= Adam account name is checked to make sure we check the correct customer // -c= Critical level value, default = 90 // -w= Warning level value, default = 70 // -i= Info to retrieve, values of are: // usage_percent Calculate the used percentage of the downloaded traffic, return value set according the given critical and warning levels // usage_used Return the used amount in MB, return value always OK // usage_quota Return the quota amount in MB, return value always OK // startdate Return the quota start date time, return value always OK // planspeed Return the plan speed string, return value always OK // ADSL_Sync Return the ADSL Up/Down speed, return value always OK // ADSL_SNR Return the ADSL Up/Down SNR, return value always OK // ADSL_Att Return the ADSL Up/Down Attenuation, return value always OK // IPv4 Return the current IPv4 address, return value always OK // IPv4_check Compare the current IPv4 address with the given one (via -IP), return value is OK when equal and CRITICAL when different // -IP= IP address to check // -d= 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: // // $ ./check_usage_adam -t=AABBCCDDEEFF -u=accountname -i=usage_percent -w=70 -c=90 // Adam check - WARNING - Used: 75% // package main import ( "bytes" "crypto/tls" "encoding/xml" "flag" "fmt" "io" "log" "net/http" "os" "path" "strings" "strconv" "time" ) const ( version = "0.1" debug_Error = 1 debug_Warning = 2 debug_Info = 3 ) type ( ConnectionInformation struct { XMLName xml.Name `xml:"ConnectionInformation"` Name string `xml:"Name"` Version string `xml:"Version"` Disclaimer string `xml:"Disclaimer"` PreviousVersion string `xml:"PreviousVersion"` PreviousVersionURI string `xml:"PreviousVersionURI"` NextVersion string `xml:"NextVersion"` NextVersionURI string `xml:"NextVersionURI"` } UsageBucket struct { XMLName xml.Name `xml:"Bucket"` Desc string `xml:"desc,attr"` Quota string `xml:"Quota"` Datablocks string `xml:"Datablocks"` Usage string `xml:"Usage"` } Usage struct { XMLName xml.Name `xml:"Usage"` Bucket []UsageBucket LastUsageUpdate string `xml:"LastUsageUpdate"` } ADSL struct { XMLName xml.Name `xml:"ADSL"` SyncStatus string `xml:"SyncStatus"` SyncUp string `xml:"SyncUp"` SyncDown string `xml:"SyncDown"` SNRUp string `xml:"SNRUp"` SNRDown string `xml:"SNRDown"` AttenuationUp string `xml:"AttenuationUp"` AttenuationDown string `xml:"AttenuationDown"` } IPAddresses struct { XMLName xml.Name `xml:"IPAddresses"` IPv4Address string `xml:"IPv4Address"` } Day struct { XMLName xml.Name `xml:"Day"` Date string `xml:"date,attr"` Bucket []string `xml:"Bucket"` Terminations string `xml:"Terminations"` } DailySummary struct { XMLName xml.Name `xml:"DailySummary"` Day []Day } Account struct { XMLName xml.Name `xml:"Account"` AccountType string `xml:"type,attr"` Username string `xml:"username,attr"` QuotaStartDate string `xml:"QuotaStartDate"` PlanName string `xml:"PlanName"` PlanType string `xml:"PlanType"` PlanSpeed string `xml:"PlanSpeed"` ByteQuota string `xml:"ByteQuota"` NewsgroupByteQuota string `xml:"NewsgroupByteQuota"` SessionStart string `xml:"SessionStart"` Usage []Usage ADSL []ADSL IPAddresses []IPAddresses DailySummary []DailySummary } Customer struct { XMLName xml.Name `xml:"Customer"` Description string `xml:"description,attr"` Account []Account } Response struct { XMLName xml.Name `xml:"Response"` ConnectionInformation []ConnectionInformation Customer []Customer } ) var ( token string debug int showEnv bool showVersion bool proxyString string critical int warning int info string username string percentF float32 IPaddress string ) func debugPrintf(level int, format string, a ...interface{}) { if level <= debug { log.Printf(format, a...) } } func init() { flag.StringVar(&token, "t", "", "Adam Internet security token") 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", "usage_percent", "Info requested") flag.StringVar(&IPaddress, "IP", "", "IP address to check") flag.IntVar(&critical, "c", 90, "critical level") flag.IntVar(&warning, "w", 70, "warning level") flag.StringVar(&username, "u", "", "Adam Internet user name") } type CharsetISO88591er struct { r io.ByteReader buf *bytes.Buffer } func NewCharsetISO88591(r io.Reader) *CharsetISO88591er { buf := bytes.Buffer{} return &CharsetISO88591er{r.(io.ByteReader), &buf} } func (cs *CharsetISO88591er) Read(p []byte) (n int, err error) { for _ = range p { if r, err := cs.r.ReadByte(); err != nil { break } else { cs.buf.WriteRune(rune(r)) } } return cs.buf.Read(p) } func isCharset(charset string, names []string) bool { charset = strings.ToLower(charset) for _, n := range names { if charset == strings.ToLower(n) { return true } } return false } func IsCharsetISO88591(charset string) bool { // http://www.iana.org/assignments/character-sets // (last updated 2010-11-04) names := []string{ // Name "ISO_8859-1:1987", // Alias (preferred MIME name) "ISO-8859-1", // Aliases "iso-ir-100", "ISO_8859-1", "latin1", "l1", "IBM819", "CP819", "csISOLatin1", } return isCharset(charset, names) } func CharsetReader(charset string, input io.Reader) (io.Reader, error) { if IsCharsetISO88591(charset) { return NewCharsetISO88591(input), nil } return input, nil } 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) } client := &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } url := "https://members.adam.com.au/api/" debugPrintf(debug_Info, "url: %s\n", url) req, err := http.NewRequest("GET", url, nil) req.SetBasicAuth("", token) resp, err := client.Do(req) if err != nil { log.Fatal(err) } decoder := xml.NewDecoder(resp.Body) decoder.CharsetReader = CharsetReader xmlResponse := &Response{} err = decoder.Decode(&xmlResponse) if err != nil { fmt.Printf("Decoder error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "http status code: %s\n", resp.Status) resp.Body.Close() debugPrintf(debug_Info, "Response: \n%#v\n", xmlResponse) foundUsername := xmlResponse.Customer[0].Account[0].Username if username != foundUsername { fmt.Printf("Username mismatch: %s. Specified: %s\n", foundUsername, username) os.Exit(3) } output := "Adam check - " ret_val := 0 switch info { case "usage_percent": downloadQuota, err := strconv.ParseInt(xmlResponse.Customer[0].Account[0].Usage[0].Bucket[0].Quota, 10, 64) if err != nil { fmt.Printf("Quota conversion error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Download Quota : %d\n", downloadQuota) quotaStartDate := xmlResponse.Customer[0].Account[0].QuotaStartDate debugPrintf(debug_Info, "Quota Start Date: %s\n", quotaStartDate) const longForm = "2006-01-02T15:04:05-0700" startDate, err := time.Parse(longForm, quotaStartDate) if err != nil { fmt.Printf("Start Date conversion error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Start Date: %v\n", startDate) rolloverDate := startDate.AddDate(0, 1, 0) 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) downloadUsage, err := strconv.ParseInt(xmlResponse.Customer[0].Account[0].Usage[0].Bucket[0].Usage, 10, 64) if err != nil { fmt.Printf("Usage conversion error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Download Usage : %d\n", downloadUsage) if downloadQuota == 0 { output += "UNKNOWN - Quota was 0" ret_val = 3 } 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 "usage_used": downloadUsage, err := strconv.ParseInt(xmlResponse.Customer[0].Account[0].Usage[0].Bucket[0].Usage, 10, 64) if err != nil { fmt.Printf("Usage conversion error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Download Usage : %d\n", downloadUsage) output += "OK - Used: " output += fmt.Sprintf("%d MB", downloadUsage / 1000000) ret_val = 0 case "usage_quota": downloadQuota, err := strconv.ParseInt(xmlResponse.Customer[0].Account[0].Usage[0].Bucket[0].Quota, 10, 64) if err != nil { fmt.Printf("Quota conversion error: %v\n", err) os.Exit(3) } debugPrintf(debug_Info, "Download Quota : %d\n", downloadQuota) output += "OK - Quota: " output += fmt.Sprintf("%d MB", downloadQuota / 1000000) ret_val = 0 case "startdate": quotaStartDate := xmlResponse.Customer[0].Account[0].QuotaStartDate debugPrintf(debug_Info, "Quota Start Date: %s\n", quotaStartDate) output += "OK - Start Date: " output += fmt.Sprintf("%s", quotaStartDate) ret_val = 0 case "planspeed": planspeed := xmlResponse.Customer[0].Account[0].PlanSpeed debugPrintf(debug_Info, "Plan Speed: %s\n", planspeed) output += "OK - Plan Speed: " output += fmt.Sprintf("%s", planspeed) ret_val = 0 case "ADSL_Sync": SyncUp := xmlResponse.Customer[0].Account[0].ADSL[0].SyncUp SyncDown := xmlResponse.Customer[0].Account[0].ADSL[0].SyncDown debugPrintf(debug_Info, "Sync Up/Down: %s / %s\n", SyncUp, SyncDown) output += "OK - Sync Up/Down: " output += fmt.Sprintf("%s / %s", SyncUp, SyncDown) ret_val = 0 case "ADSL_SNR": SNRUp := xmlResponse.Customer[0].Account[0].ADSL[0].SNRUp SNRDown := xmlResponse.Customer[0].Account[0].ADSL[0].SNRDown debugPrintf(debug_Info, "SNR Up/Down: %s / %s\n", SNRUp, SNRDown) output += "OK - SNR Up/Down: " output += fmt.Sprintf("%s / %s", SNRUp, SNRDown) ret_val = 0 case "ADSL_Att": AttUp := xmlResponse.Customer[0].Account[0].ADSL[0].AttenuationUp AttDown := xmlResponse.Customer[0].Account[0].ADSL[0].AttenuationDown debugPrintf(debug_Info, "Att Up/Down: %s / %s\n", AttUp, AttDown) output += "OK - Att Up/Down: " output += fmt.Sprintf("%s / %s", AttUp, AttDown) ret_val = 0 case "IPv4": IPv4 := xmlResponse.Customer[0].Account[0].IPAddresses[0].IPv4Address debugPrintf(debug_Info, "IPv4: %s\n", IPv4) output += "OK - IPv4: " output += fmt.Sprintf("%s", IPv4) ret_val = 0 case "IPv4_check": IPv4 := xmlResponse.Customer[0].Account[0].IPAddresses[0].IPv4Address debugPrintf(debug_Info, "IPv4: %s\n", IPv4) if IPv4 == IPaddress { output += "OK" ret_val = 0 } else { output += "CRITICAL" ret_val = 2 } output += " - IPv4: " output += fmt.Sprintf("%s", IPv4) output += " check: " output += fmt.Sprintf("%s", IPaddress) } fmt.Printf("%s\n", output) os.Exit(ret_val) }