Compare commits
9 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
cdfa3c8e09 |
|||
|
5e37669ba6 |
|||
|
f9c209e576 |
|||
|
0281eea177 |
|||
| b27f7773ca | |||
| 56a09ca38f | |||
| e1f6b29a03 | |||
| 892cd62f3e | |||
| 827162dde9 |
19 changed files with 349 additions and 229 deletions
|
|
@ -9,7 +9,7 @@ when:
|
||||||
- renovate/*
|
- renovate/*
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &golang_image 'golang:1.22'
|
- &golang_image 'golang:1.24'
|
||||||
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- test
|
- test
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ when:
|
||||||
- renovate/*
|
- renovate/*
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
"Check sehll scripts":
|
"Check shell scripts":
|
||||||
image: pipelinecomponents/shellcheck
|
image: pipelinecomponents/shellcheck
|
||||||
commands:
|
commands:
|
||||||
- shellcheck ./bin/*.sh
|
- shellcheck ./bin/*.sh
|
||||||
|
|
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -1,5 +1,15 @@
|
||||||
[Unreleased]
|
[Unreleased]
|
||||||
|
|
||||||
|
## v2.0.0
|
||||||
|
### Changed
|
||||||
|
- new project archirecture
|
||||||
|
|
||||||
|
## v1.4.0
|
||||||
|
### Added
|
||||||
|
- add logger and option `-v`
|
||||||
|
### Fixed
|
||||||
|
- fix: render days and date when the value is later than danger value
|
||||||
|
|
||||||
## v1.3.0
|
## v1.3.0
|
||||||
### Added
|
### Added
|
||||||
- add manpage for Debian
|
- add manpage for Debian
|
||||||
|
|
|
||||||
12
Makefile
12
Makefile
|
|
@ -50,7 +50,7 @@ $(BIN_LINUX_AMD64):
|
||||||
GOOS=$(GO_OS_LINUX) \
|
GOOS=$(GO_OS_LINUX) \
|
||||||
CGO_ENABLED=$(CGO_ENABLED) \
|
CGO_ENABLED=$(CGO_ENABLED) \
|
||||||
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
||||||
-o $(BIN_LINUX_AMD64) .
|
-o $(BIN_LINUX_AMD64) cmd/main.go
|
||||||
|
|
||||||
.PHONY: $(BIN_LINUX_ARM64)
|
.PHONY: $(BIN_LINUX_ARM64)
|
||||||
$(BIN_LINUX_ARM64):
|
$(BIN_LINUX_ARM64):
|
||||||
|
|
@ -59,7 +59,7 @@ $(BIN_LINUX_ARM64):
|
||||||
GOOS=$(GO_OS_LINUX) \
|
GOOS=$(GO_OS_LINUX) \
|
||||||
CGO_ENABLED=$(CGO_ENABLED) \
|
CGO_ENABLED=$(CGO_ENABLED) \
|
||||||
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
||||||
-o $(BIN_LINUX_ARM64) .
|
-o $(BIN_LINUX_ARM64) cmd/main.go
|
||||||
|
|
||||||
.PHONY: $(BIN_WIN_AMD64)
|
.PHONY: $(BIN_WIN_AMD64)
|
||||||
$(BIN_WIN_AMD64):
|
$(BIN_WIN_AMD64):
|
||||||
|
|
@ -68,7 +68,7 @@ $(BIN_WIN_AMD64):
|
||||||
GOOS=$(GO_OS_WIN) \
|
GOOS=$(GO_OS_WIN) \
|
||||||
CGO_ENABLED=$(CGO_ENABLED) \
|
CGO_ENABLED=$(CGO_ENABLED) \
|
||||||
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
||||||
-o $(BIN_WIN_AMD64) .
|
-o $(BIN_WIN_AMD64) cmd/main.go
|
||||||
|
|
||||||
.PHONY: $(BIN_WIN_ARM64)
|
.PHONY: $(BIN_WIN_ARM64)
|
||||||
$(BIN_WIN_ARM64):
|
$(BIN_WIN_ARM64):
|
||||||
|
|
@ -77,7 +77,7 @@ $(BIN_WIN_ARM64):
|
||||||
GOOS=$(GO_OS_WIN) \
|
GOOS=$(GO_OS_WIN) \
|
||||||
CGO_ENABLED=$(CGO_ENABLED) \
|
CGO_ENABLED=$(CGO_ENABLED) \
|
||||||
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
||||||
-o $(BIN_WIN_ARM64) .
|
-o $(BIN_WIN_ARM64) cmd/main.go
|
||||||
|
|
||||||
.PHONY: $(BIN_DARWIN_AMD64)
|
.PHONY: $(BIN_DARWIN_AMD64)
|
||||||
$(BIN_DARWIN_AMD64):
|
$(BIN_DARWIN_AMD64):
|
||||||
|
|
@ -86,7 +86,7 @@ $(BIN_DARWIN_AMD64):
|
||||||
GOOS=$(GO_OS_DARWIN) \
|
GOOS=$(GO_OS_DARWIN) \
|
||||||
CGO_ENABLED=$(CGO_ENABLED) \
|
CGO_ENABLED=$(CGO_ENABLED) \
|
||||||
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
||||||
-o $(BIN_DARWIN_AMD64) .
|
-o $(BIN_DARWIN_AMD64) cmd/main.go
|
||||||
|
|
||||||
.PHONY: $(BIN_DARWIN_ARM64)
|
.PHONY: $(BIN_DARWIN_ARM64)
|
||||||
$(BIN_DARWIN_ARM64):
|
$(BIN_DARWIN_ARM64):
|
||||||
|
|
@ -95,7 +95,7 @@ $(BIN_DARWIN_ARM64):
|
||||||
GOOS=$(GO_OS_DARWIN) \
|
GOOS=$(GO_OS_DARWIN) \
|
||||||
CGO_ENABLED=$(CGO_ENABLED) \
|
CGO_ENABLED=$(CGO_ENABLED) \
|
||||||
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
$(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \
|
||||||
-o $(BIN_DARWIN_ARM64) .
|
-o $(BIN_DARWIN_ARM64) cmd/main.go
|
||||||
|
|
||||||
.PHONY: $(MAN_OUTPUT)
|
.PHONY: $(MAN_OUTPUT)
|
||||||
$(MAN_OUTPUT):
|
$(MAN_OUTPUT):
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
package checker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func FormatDomain(domain string) string {
|
|
||||||
elements := strings.Split(domain, ":")
|
|
||||||
|
|
||||||
if len(elements) == 1 {
|
|
||||||
return fmt.Sprintf("%s:443", elements[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return domain
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckCertificate(domain string) Domain {
|
|
||||||
now := time.Now()
|
|
||||||
conn, err := tls.Dial("tcp", FormatDomain(domain), nil)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
date := conn.ConnectionState().PeerCertificates[0].NotAfter
|
|
||||||
daysLeft := date.Sub(now).Hours() / 24
|
|
||||||
|
|
||||||
return Domain{
|
|
||||||
Name: domain,
|
|
||||||
DaysLeft: math.Floor(daysLeft),
|
|
||||||
Date: date.Format(time.DateTime),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Domain{Name: domain, Failed: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckCertificates(domains []string) []Domain {
|
|
||||||
values := []Domain{}
|
|
||||||
|
|
||||||
for _, domain := range domains {
|
|
||||||
values = append(values, CheckCertificate(domain))
|
|
||||||
}
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
package checker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/likexian/whois"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RdapResponseData struct {
|
|
||||||
Events []struct {
|
|
||||||
EventAction string `json:"eventAction"`
|
|
||||||
EventDate string `json:"eventDate"`
|
|
||||||
} `json:"events"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RdapServices struct {
|
|
||||||
Services [][][]string `json:"services"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetRdapServices() map[string]string {
|
|
||||||
response, _ := http.Get("https://data.iana.org/rdap/dns.json")
|
|
||||||
values := make(map[string]string)
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
var data RdapServices
|
|
||||||
json.NewDecoder(response.Body).Decode(&data)
|
|
||||||
|
|
||||||
for _, value := range data.Services {
|
|
||||||
for _, tld := range value[0] {
|
|
||||||
values[tld] = value[1][0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractTld(domain string) string {
|
|
||||||
elements := strings.Split(domain, ".")
|
|
||||||
|
|
||||||
if len(elements) == 1 {
|
|
||||||
return elements[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(elements[1:], ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func RdapCheck(domain, service string) Domain {
|
|
||||||
url := fmt.Sprintf("%sdomain/%s?jscard=1", service, domain)
|
|
||||||
response, _ := http.Get(url)
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
defer response.Body.Close()
|
|
||||||
var data RdapResponseData
|
|
||||||
json.NewDecoder(response.Body).Decode(&data)
|
|
||||||
|
|
||||||
for _, event := range data.Events {
|
|
||||||
if event.EventAction == "expiration" {
|
|
||||||
date, _ := time.Parse(time.RFC3339, event.EventDate)
|
|
||||||
daysLeft := date.Sub(now).Hours() / 24
|
|
||||||
|
|
||||||
return Domain{
|
|
||||||
Name: domain,
|
|
||||||
DaysLeft: math.Floor(daysLeft),
|
|
||||||
Date: date.Format(time.DateTime),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Domain{Name: domain, Failed: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WhoisCheck(domain string) Domain {
|
|
||||||
domainFailed := Domain{Name: domain, Failed: true}
|
|
||||||
result, err := whois.Whois(domain)
|
|
||||||
if err != nil {
|
|
||||||
return domainFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
formats := []string{
|
|
||||||
"expiration date",
|
|
||||||
"expiry date",
|
|
||||||
"expires on",
|
|
||||||
"paid-till",
|
|
||||||
"renewal",
|
|
||||||
"expires",
|
|
||||||
"domain_datebilleduntil",
|
|
||||||
"expiration",
|
|
||||||
"registry expiry",
|
|
||||||
"registrar registration expiration",
|
|
||||||
}
|
|
||||||
|
|
||||||
result = strings.ToLower(result)
|
|
||||||
|
|
||||||
for _, format := range formats {
|
|
||||||
r, _ := regexp.Compile(fmt.Sprintf(`%s\s*:?\s*([^\s]+)`, format))
|
|
||||||
|
|
||||||
for i, match := range r.FindStringSubmatch(result) {
|
|
||||||
if i%2 == 1 {
|
|
||||||
date, err := time.Parse(time.RFC3339, strings.ToUpper(match))
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
daysLeft := date.Sub(now).Hours() / 24
|
|
||||||
|
|
||||||
return Domain{
|
|
||||||
Name: domain,
|
|
||||||
DaysLeft: math.Floor(daysLeft),
|
|
||||||
Date: date.Format(time.DateTime),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domainFailed
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckDomains(domains []string) []Domain {
|
|
||||||
values := []Domain{}
|
|
||||||
services := GetRdapServices()
|
|
||||||
|
|
||||||
for _, domain := range domains {
|
|
||||||
tld := ExtractTld(domain)
|
|
||||||
service := services[tld]
|
|
||||||
|
|
||||||
if service != "" {
|
|
||||||
values = append(values, RdapCheck(domain, service))
|
|
||||||
} else {
|
|
||||||
values = append(values, WhoisCheck(domain))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
package checker
|
|
||||||
|
|
||||||
type Domain struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
DaysLeft float64 `json:"days"`
|
|
||||||
Date string `json:"date"`
|
|
||||||
Failed bool `json:"failed"`
|
|
||||||
}
|
|
||||||
14
cmd/main.go
Normal file
14
cmd/main.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"gitnet.fr/deblan/expiration-check/internal/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := cmd.App().Run(os.Args); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package main
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"gitnet.fr/deblan/expiration-check/checker"
|
"gitnet.fr/deblan/expiration-check/pkg/checker"
|
||||||
"gitnet.fr/deblan/expiration-check/render"
|
"gitnet.fr/deblan/expiration-check/pkg/logger"
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NormalizeFormat(format string) string {
|
func NormalizeFormat(format string) string {
|
||||||
|
|
@ -33,6 +34,11 @@ func App() *cli.App {
|
||||||
Value: "table",
|
Value: "table",
|
||||||
Usage: "output format: table, csv, tsv, html, json, markdown",
|
Usage: "output format: table, csv, tsv, html, json, markdown",
|
||||||
},
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Required: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cli.App{
|
return &cli.App{
|
||||||
|
|
@ -45,6 +51,8 @@ func App() *cli.App {
|
||||||
Usage: "Checks certificate",
|
Usage: "Checks certificate",
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
logger.Get().SetVerbose(c.Bool("verbose"))
|
||||||
|
|
||||||
render.Render(
|
render.Render(
|
||||||
checker.CheckCertificates(c.StringSlice("domain")),
|
checker.CheckCertificates(c.StringSlice("domain")),
|
||||||
30, 14,
|
30, 14,
|
||||||
|
|
@ -60,6 +68,8 @@ func App() *cli.App {
|
||||||
Aliases: []string{"d", "domains"},
|
Aliases: []string{"d", "domains"},
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
|
logger.Get().SetVerbose(c.Bool("verbose"))
|
||||||
|
|
||||||
render.Render(
|
render.Render(
|
||||||
checker.CheckDomains(c.StringSlice("domain")),
|
checker.CheckDomains(c.StringSlice("domain")),
|
||||||
30, 14,
|
30, 14,
|
||||||
12
main.go
12
main.go
|
|
@ -1,12 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if err := App().Run(os.Args); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
38
pkg/checker/certificate.go
Normal file
38
pkg/checker/certificate.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package checker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/formatter"
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckCertificate(domain string) *model.Result {
|
||||||
|
conn, err := tls.Dial("tcp", formatter.ToDomainAndHttpPort(domain), nil)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
date := conn.ConnectionState().PeerCertificates[0].NotAfter
|
||||||
|
|
||||||
|
result := model.NewResult(
|
||||||
|
domain,
|
||||||
|
math.Floor(date.Sub(time.Now()).Hours()/24),
|
||||||
|
date.Format(time.DateTime),
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.NewResultFailed(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckCertificates(domains []string) []*model.Result {
|
||||||
|
var values []*model.Result
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
values = append(values, CheckCertificate(domain))
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
53
pkg/checker/domain.go
Normal file
53
pkg/checker/domain.go
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package checker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/extractor"
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/logger"
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/model"
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/rdap"
|
||||||
|
"gitnet.fr/deblan/expiration-check/pkg/whois"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckDomains(domains []string) []*model.Result {
|
||||||
|
var err error
|
||||||
|
var date *time.Time
|
||||||
|
var values []*model.Result
|
||||||
|
|
||||||
|
services, err := rdap.GetRdapServices()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Get().Logf("GetRdapServices failed: error=%v", err)
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
tld := extractor.ExtractTld(domain)
|
||||||
|
service, ok := services[tld]
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
date, err = rdap.GetExpiration(domain, service)
|
||||||
|
} else {
|
||||||
|
date, err = whois.GetExpiration(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Get().Logf("CheckDomain: domain=%s error=%s", domain, err)
|
||||||
|
|
||||||
|
values = append(values, model.NewResultFailed(domain))
|
||||||
|
} else {
|
||||||
|
daysLeft := math.Floor(date.Sub(time.Now()).Hours() / 24)
|
||||||
|
|
||||||
|
values = append(values, model.NewResult(
|
||||||
|
domain,
|
||||||
|
daysLeft,
|
||||||
|
date.Format(time.DateTime),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values
|
||||||
|
}
|
||||||
13
pkg/extractor/tld.go
Normal file
13
pkg/extractor/tld.go
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package extractor
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func ExtractTld(domain string) string {
|
||||||
|
elements := strings.Split(domain, ".")
|
||||||
|
|
||||||
|
if len(elements) == 1 {
|
||||||
|
return elements[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(elements[1:], ".")
|
||||||
|
}
|
||||||
16
pkg/formatter/http_domain.go
Normal file
16
pkg/formatter/http_domain.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package formatter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ToDomainAndHttpPort(domain string) string {
|
||||||
|
elements := strings.Split(domain, ":")
|
||||||
|
|
||||||
|
if len(elements) == 1 {
|
||||||
|
return fmt.Sprintf("%s:443", elements[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain
|
||||||
|
}
|
||||||
27
pkg/logger/logger.go
Normal file
27
pkg/logger/logger.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
package logger
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var logger *Logger
|
||||||
|
|
||||||
|
func Get() *Logger {
|
||||||
|
if logger == nil {
|
||||||
|
logger = new(Logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) SetVerbose(value bool) {
|
||||||
|
l.Verbose = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Logf(format string, v ...any) {
|
||||||
|
if l.Verbose {
|
||||||
|
log.Printf(format, v...)
|
||||||
|
}
|
||||||
|
}
|
||||||
24
pkg/model/result.go
Normal file
24
pkg/model/result.go
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
DaysLeft *float64 `json:"days"`
|
||||||
|
Date *string `json:"date"`
|
||||||
|
Failed bool `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResult(name string, daysLeft float64, date string) *Result {
|
||||||
|
return &Result{
|
||||||
|
Name: name,
|
||||||
|
DaysLeft: &daysLeft,
|
||||||
|
Date: &date,
|
||||||
|
Failed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResultFailed(name string) *Result {
|
||||||
|
return &Result{
|
||||||
|
Name: name,
|
||||||
|
Failed: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
75
pkg/rdap/rdap.go
Normal file
75
pkg/rdap/rdap.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package rdap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RdapResponseData struct {
|
||||||
|
Events []struct {
|
||||||
|
EventAction string `json:"eventAction"`
|
||||||
|
EventDate string `json:"eventDate"`
|
||||||
|
} `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RdapServices struct {
|
||||||
|
Services [][][]string `json:"services"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRdapServices() (map[string]string, error) {
|
||||||
|
response, err := http.Get("https://data.iana.org/rdap/dns.json")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
values := make(map[string]string)
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
var data RdapServices
|
||||||
|
|
||||||
|
json.NewDecoder(response.Body).Decode(&data)
|
||||||
|
|
||||||
|
for _, value := range data.Services {
|
||||||
|
for _, tld := range value[0] {
|
||||||
|
values[tld] = value[1][0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetExpiration(domain, service string) (*time.Time, error) {
|
||||||
|
url := fmt.Sprintf("%sdomain/%s?jscard=1", service, domain)
|
||||||
|
response, err := http.Get(url)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
var data RdapResponseData
|
||||||
|
|
||||||
|
json.NewDecoder(response.Body).Decode(&data)
|
||||||
|
|
||||||
|
actions := []string{"expiration", "Record expires"}
|
||||||
|
|
||||||
|
for _, event := range data.Events {
|
||||||
|
for _, action := range actions {
|
||||||
|
if action == event.EventAction {
|
||||||
|
date, err := time.Parse(time.RFC3339, event.EventDate)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &date, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Expiration date not found")
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/jedib0t/go-pretty/v6/table"
|
||||||
"github.com/jedib0t/go-pretty/v6/text"
|
"github.com/jedib0t/go-pretty/v6/text"
|
||||||
"gitnet.fr/deblan/expiration-check/checker"
|
"gitnet.fr/deblan/expiration-check/pkg/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RenderColor(value string, c text.Colors, format string) string {
|
func RenderColor(value string, c text.Colors, format string) string {
|
||||||
|
|
@ -19,7 +19,7 @@ func RenderColor(value string, c text.Colors, format string) string {
|
||||||
return c.Sprint(value)
|
return c.Sprint(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Render(values []checker.Domain, warning, danger float64, format string) {
|
func Render(values []*model.Result, warning, danger float64, format string) {
|
||||||
sort.SliceStable(values, func(i, j int) bool {
|
sort.SliceStable(values, func(i, j int) bool {
|
||||||
if values[i].Failed && values[j].Failed {
|
if values[i].Failed && values[j].Failed {
|
||||||
return values[i].Name < values[j].Name
|
return values[i].Name < values[j].Name
|
||||||
|
|
@ -33,7 +33,7 @@ func Render(values []checker.Domain, warning, danger float64, format string) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return values[i].DaysLeft < values[j].DaysLeft
|
return *values[i].DaysLeft < *values[j].DaysLeft
|
||||||
})
|
})
|
||||||
|
|
||||||
if format == "json" {
|
if format == "json" {
|
||||||
|
|
@ -64,15 +64,15 @@ func Render(values []checker.Domain, warning, danger float64, format string) {
|
||||||
var days string
|
var days string
|
||||||
var date string
|
var date string
|
||||||
|
|
||||||
if value.DaysLeft <= danger {
|
if *value.DaysLeft <= danger {
|
||||||
days = failed
|
days = RenderColor(fmt.Sprintf("%.0f", *value.DaysLeft), text.Colors{0, text.FgRed}, format)
|
||||||
date = failed
|
date = RenderColor(*value.Date, text.Colors{0, text.FgRed}, format)
|
||||||
} else if value.DaysLeft <= warning {
|
} else if *value.DaysLeft <= warning {
|
||||||
days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgYellow}, format)
|
days = RenderColor(fmt.Sprintf("%.0f", *value.DaysLeft), text.Colors{0, text.FgYellow}, format)
|
||||||
date = RenderColor(value.Date, text.Colors{0, text.FgYellow}, format)
|
date = RenderColor(*value.Date, text.Colors{0, text.FgYellow}, format)
|
||||||
} else {
|
} else {
|
||||||
days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgGreen}, format)
|
days = RenderColor(fmt.Sprintf("%.0f", *value.DaysLeft), text.Colors{0, text.FgGreen}, format)
|
||||||
date = RenderColor(value.Date, text.Colors{0, text.FgGreen}, format)
|
date = RenderColor(*value.Date, text.Colors{0, text.FgGreen}, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.AppendRow(table.Row{
|
t.AppendRow(table.Row{
|
||||||
47
pkg/whois/whois.go
Normal file
47
pkg/whois/whois.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package whois
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
w "github.com/likexian/whois"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetExpiration(domain string) (*time.Time, error) {
|
||||||
|
result, err := w.Whois(domain)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result = strings.ToLower(result)
|
||||||
|
formats := []string{
|
||||||
|
"expiration date",
|
||||||
|
"expiry date",
|
||||||
|
"expires on",
|
||||||
|
"paid-till",
|
||||||
|
"renewal",
|
||||||
|
"expires",
|
||||||
|
"domain_datebilleduntil",
|
||||||
|
"expiration",
|
||||||
|
"registry expiry",
|
||||||
|
"registrar registration expiration",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range formats {
|
||||||
|
r, _ := regexp.Compile(fmt.Sprintf(`%s\s*:?\s*([^\s]+)`, format))
|
||||||
|
|
||||||
|
for i, match := range r.FindStringSubmatch(result) {
|
||||||
|
if i%2 == 1 {
|
||||||
|
if date, err := time.Parse(time.RFC3339, strings.ToUpper(match)); err == nil {
|
||||||
|
return &date, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("Expiration date not found")
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue