1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160package main
import (
"bufio"
"crypto/x509"
"encoding/pem"
"flag"
"fmt"
"io"
"os"
"regexp"
"strings"
"time"
"path/filepath"
)
const (
LETSENCRYPTDIR = "/etc/letsencrypt"
)
var (
Version = "development"
CheckAllDomains = false
)
func main() {
version := flag.Bool("version", false, "version")
letsencryptdir := flag.String("letsencryptdir", LETSENCRYPTDIR, "Root directory for lestencrypt installation")
domain := flag.String("domain", "", "Domain to be checked (otherwise all domains will be checked)")
flag.Parse()
if *version {
fmt.Println("Version:", Version)
os.Exit(0)
}
if *letsencryptdir != LETSENCRYPTDIR {
fmt.Println("Using directory:", *letsencryptdir)
}
if *domain == "" {
// fmt.Println("Checking all domains.")
CheckAllDomains = true
}
if CheckAllDomains {
configFiles := getListOfConfigs(fmt.Sprintf("%s/renewal", *letsencryptdir))
// fmt.Println("configFiles:", configFiles)
for _, config := range configFiles {
config = fmt.Sprintf("%s/renewal/%s", *letsencryptdir, config)
cert := getCertificatePathFromConfig(config)
certX509 := getCertificateX509(cert)
days := getRemainingDays(certX509)
domain := getDomainNameFromConfigFile(config)
printRemainingDays(domain, days)
}
} else {
configFile := fmt.Sprintf("%s/renewal/%s.conf", *letsencryptdir, *domain)
certificate := getCertificatePathFromConfig(configFile)
// fmt.Println("certificate:", certificate)
certX509 := getCertificateX509(certificate)
days := getRemainingDays(certX509)
printRemainingDays(*domain, days)
}
}
func getListOfConfigs(directory string) []string {
files, err := os.ReadDir(directory)
if err != nil {
panic(fmt.Sprintf("Failed to read from directory: %v", directory))
}
var configFiles []string
for _, file := range files {
if grep(".conf", file.Name()) {
configFiles = append(configFiles, file.Name())
}
}
return configFiles
}
func grep(pattern, text string) bool {
re := regexp.MustCompile(pattern)
return re.MatchString(text)
}
func getCertificatePathFromConfig(configFile string) string {
file, err := os.Open(configFile)
if err != nil {
panic(fmt.Sprintf("Failed to open file: %v", configFile))
}
defer file.Close()
var certificate = ""
rd := bufio.NewReader(file)
for {
line, _, err := rd.ReadLine()
if err != nil {
if err == io.EOF {
break
}
}
strLine := string(line)
if grep("^cert", strLine) {
strLine = sed(" ", "", strLine)
certificate = strings.Split(strLine, "=")[1]
}
}
return certificate
}
func sed(before, after, text string) string {
return strings.Replace(text, before, after, -1)
}
func getCertificateX509(certificate string) *x509.Certificate {
file, err := os.Open(certificate)
if err != nil {
panic(fmt.Sprintf("Failed to open file: %v", certificate))
}
defer file.Close()
rd := bufio.NewReader(file)
// 8192 is a good number?
text := make([]byte, 8192)
counter, err := rd.Read(text)
if err != nil {
panic(fmt.Sprintf("Failed to read file: %v", certificate))
}
if counter == 0 {
panic(fmt.Sprintf("Failed to read file (counter is zero): %v", certificate))
}
certPEM, _ := pem.Decode(text)
if certPEM == nil {
panic(fmt.Sprintf("Failed to decode PEM from file: %v", certificate))
}
cert, err := x509.ParseCertificate(certPEM.Bytes)
if certPEM == nil {
panic(fmt.Sprintf("Failed to parse as X509 from file: %v", certificate))
}
return cert
}
func getRemainingDays(cert *x509.Certificate) string {
expiration := cert.NotAfter
remaining := expiration.Sub(time.Now())
return fmt.Sprintf("%d", int(remaining.Hours()/24))
}
func printRemainingDays(domain , days string) {
fmt.Printf("%s=%s\n", domain, days)
}
func getDomainNameFromConfigFile(config string) string {
fileName := filepath.Base(config)
return sed(".conf", "", fileName)
}