2016-02-05 03:45:37 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"bufio"
|
2016-02-09 19:28:52 +01:00
|
|
|
"strings"
|
2016-02-05 03:45:37 +01:00
|
|
|
|
2016-04-26 23:38:09 +02:00
|
|
|
"golang.org/x/net/idna"
|
|
|
|
|
2016-02-05 03:45:37 +01:00
|
|
|
"src.agwa.name/ctwatch"
|
2016-02-18 19:44:56 +01:00
|
|
|
"src.agwa.name/ctwatch/ct"
|
2016-02-05 03:45:37 +01:00
|
|
|
"src.agwa.name/ctwatch/cmd"
|
|
|
|
)
|
|
|
|
|
2016-03-08 16:09:26 +01:00
|
|
|
func DefaultStateDir () string {
|
|
|
|
if envVar := os.Getenv("CTWATCH_STATE_DIR"); envVar != "" {
|
|
|
|
return envVar
|
|
|
|
} else {
|
|
|
|
return cmd.DefaultStateDir("ctwatch")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 21:49:26 +02:00
|
|
|
func trimTrailingDots (value string) string {
|
|
|
|
length := len(value)
|
|
|
|
for length > 0 && value[length - 1] == '.' {
|
|
|
|
length--
|
|
|
|
}
|
|
|
|
return value[0:length]
|
|
|
|
}
|
|
|
|
|
2016-03-08 16:09:26 +01:00
|
|
|
var stateDir = flag.String("state_dir", DefaultStateDir(), "Directory for storing state")
|
2016-02-09 19:28:52 +01:00
|
|
|
var watchDomains []string
|
|
|
|
var watchDomainSuffixes []string
|
|
|
|
|
2016-04-29 18:02:03 +02:00
|
|
|
func addWatchDomain (asciiDomain string) {
|
|
|
|
watchDomains = append(watchDomains, asciiDomain)
|
|
|
|
watchDomainSuffixes = append(watchDomainSuffixes, "." + asciiDomain)
|
|
|
|
}
|
|
|
|
|
2016-04-26 23:38:09 +02:00
|
|
|
func setWatchDomains (domains []string) error {
|
2016-02-09 19:28:52 +01:00
|
|
|
for _, domain := range domains {
|
2016-02-22 23:18:56 +01:00
|
|
|
if domain == "." { // "." as in root zone (matches everything)
|
|
|
|
watchDomains = []string{}
|
|
|
|
watchDomainSuffixes = []string{""}
|
|
|
|
break
|
|
|
|
} else {
|
2016-05-01 21:49:26 +02:00
|
|
|
asciiDomain, err := idna.ToASCII(strings.ToLower(trimTrailingDots(domain)))
|
2016-04-26 23:38:09 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Invalid domain `%s': %s", domain, err)
|
|
|
|
}
|
|
|
|
|
2016-04-29 18:02:03 +02:00
|
|
|
addWatchDomain(asciiDomain)
|
2016-04-29 06:26:59 +02:00
|
|
|
|
2016-04-29 18:02:03 +02:00
|
|
|
// Also monitor DNS names that _might_ match this domain (wildcards,
|
|
|
|
// label redactions, and unparseable labels).
|
|
|
|
// For example, if we're monitoring sub.example.com, also monitor:
|
|
|
|
// *.example.com
|
|
|
|
// ?.example.com
|
2016-05-04 20:43:02 +02:00
|
|
|
// <unparsable>.example.com
|
|
|
|
// TODO: support for wildcards that are not the entire label (e.g. ac-*.fr)
|
2016-04-29 18:02:03 +02:00
|
|
|
var parentDomain string
|
2016-04-29 06:26:59 +02:00
|
|
|
if dot := strings.IndexRune(asciiDomain, '.'); dot != -1 {
|
2016-04-29 18:02:03 +02:00
|
|
|
parentDomain = asciiDomain[dot:]
|
2016-04-26 23:38:09 +02:00
|
|
|
}
|
2016-04-29 18:02:03 +02:00
|
|
|
addWatchDomain("*" + parentDomain)
|
|
|
|
addWatchDomain("?" + parentDomain)
|
2016-05-04 20:43:02 +02:00
|
|
|
addWatchDomain(ctwatch.UnparsableDNSLabelPlaceholder + parentDomain)
|
2016-02-22 23:18:56 +01:00
|
|
|
}
|
2016-02-09 19:28:52 +01:00
|
|
|
}
|
2016-04-26 23:38:09 +02:00
|
|
|
return nil
|
2016-02-09 19:28:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func dnsNameMatches (dnsName string) bool {
|
|
|
|
for _, domain := range watchDomains {
|
2016-04-29 06:26:59 +02:00
|
|
|
if dnsName == domain {
|
2016-02-09 19:28:52 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, domainSuffix := range watchDomainSuffixes {
|
2016-04-29 06:26:59 +02:00
|
|
|
if strings.HasSuffix(dnsName, domainSuffix) {
|
2016-02-09 19:28:52 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func anyDnsNameMatches (dnsNames []string) bool {
|
|
|
|
for _, dnsName := range dnsNames {
|
|
|
|
if dnsNameMatches(dnsName) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func processEntry (scanner *ctwatch.Scanner, entry *ct.LogEntry) {
|
|
|
|
info := ctwatch.EntryInfo{
|
|
|
|
LogUri: scanner.LogUri,
|
|
|
|
Entry: entry,
|
2016-03-17 00:58:00 +01:00
|
|
|
IsPrecert: ctwatch.IsPrecert(entry),
|
|
|
|
FullChain: ctwatch.GetFullChain(entry),
|
2016-02-09 19:28:52 +01:00
|
|
|
}
|
|
|
|
|
2016-03-18 00:34:53 +01:00
|
|
|
info.CertInfo, info.ParseError = ctwatch.MakeCertInfoFromLogEntry(entry)
|
2016-02-09 19:28:52 +01:00
|
|
|
|
2016-05-03 20:58:59 +02:00
|
|
|
if info.CertInfo != nil {
|
|
|
|
info.Identifiers, info.IdentifiersParseError = info.CertInfo.ParseIdentifiers()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fail safe behavior: if info.Identifiers is nil (which is caused by a
|
|
|
|
// parse error), report the certificate because we can't say for sure it
|
|
|
|
// doesn't match a domain we care about. We try very hard to make sure
|
|
|
|
// parsing identifiers always succeeds, so false alarms should be rare.
|
|
|
|
if info.Identifiers == nil || anyDnsNameMatches(info.Identifiers.DNSNames) {
|
2016-04-23 05:58:33 +02:00
|
|
|
cmd.LogEntry(&info)
|
2016-02-09 19:28:52 +01:00
|
|
|
}
|
|
|
|
}
|
2016-02-05 05:16:25 +01:00
|
|
|
|
2016-02-05 03:45:37 +01:00
|
|
|
func main() {
|
|
|
|
flag.Parse()
|
|
|
|
|
2016-02-05 17:13:11 +01:00
|
|
|
if flag.NArg() == 0 {
|
|
|
|
fmt.Fprintf(os.Stderr, "Usage: %s [flags] domain ...\n", os.Args[0])
|
|
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
|
|
fmt.Fprintf(os.Stderr, "To read domain list from stdin, use '-'. To monitor all domains, use '.'.\n")
|
|
|
|
fmt.Fprintf(os.Stderr, "See '%s -help' for a list of valid flags.\n", os.Args[0])
|
|
|
|
os.Exit(2)
|
|
|
|
}
|
|
|
|
|
2016-02-05 05:16:25 +01:00
|
|
|
if flag.NArg() == 1 && flag.Arg(0) == "-" {
|
2016-02-05 17:13:11 +01:00
|
|
|
var domains []string
|
2016-02-05 03:45:37 +01:00
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
for scanner.Scan() {
|
|
|
|
domains = append(domains, scanner.Text())
|
|
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
2016-02-05 05:16:25 +01:00
|
|
|
fmt.Fprintf(os.Stderr, "%s: Error reading standard input: %s\n", os.Args[0], err)
|
2016-02-22 23:45:50 +01:00
|
|
|
os.Exit(1)
|
2016-02-05 03:45:37 +01:00
|
|
|
}
|
2016-04-26 23:38:09 +02:00
|
|
|
if err := setWatchDomains(domains); err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2016-02-05 03:45:37 +01:00
|
|
|
} else {
|
2016-04-26 23:38:09 +02:00
|
|
|
if err := setWatchDomains(flag.Args()); err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "%s: %s\n", os.Args[0], err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2016-02-05 03:45:37 +01:00
|
|
|
}
|
|
|
|
|
2016-02-09 19:28:52 +01:00
|
|
|
cmd.Main(*stateDir, processEntry)
|
2016-02-05 03:45:37 +01:00
|
|
|
}
|