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" )
}
}
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-26 23:38:09 +02:00
func addWatchDomain ( domain string ) {
2016-04-26 23:49:39 +02:00
domain = strings . ToLower ( domain )
watchDomains = append ( watchDomains , domain )
watchDomainSuffixes = append ( watchDomainSuffixes , "." + domain )
if dot := strings . IndexRune ( domain , '.' ) ; dot != - 1 {
// also look for wildcard names that could match
// TODO: support exotic wildcards (wildcards besides "*.<DOMAIN>") in case there are CAs that issue them (there are) and clients that support them (less clear)
watchDomains = append ( watchDomains , "*" + domain [ dot : ] )
}
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-04-26 23:38:09 +02:00
addWatchDomain ( domain )
asciiDomain , err := idna . ToASCII ( domain )
if err != nil {
return fmt . Errorf ( "Invalid domain `%s': %s" , domain , err )
}
if asciiDomain != domain {
addWatchDomain ( asciiDomain )
}
unicodeDomain , err := idna . ToUnicode ( domain )
if err != nil {
return fmt . Errorf ( "Invalid domain `%s': %s" , domain , err )
}
if unicodeDomain != domain {
addWatchDomain ( unicodeDomain )
}
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 {
dnsNameLower := strings . ToLower ( dnsName )
for _ , domain := range watchDomains {
if dnsNameLower == domain {
return true
}
}
for _ , domainSuffix := range watchDomainSuffixes {
if strings . HasSuffix ( dnsNameLower , domainSuffix ) {
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-04-23 05:58:33 +02:00
// If there's any sort of parse error related to the identifiers, report
// the certificate because we can't say for sure it doesn't match a domain
// we care about (fail safe behavior). Treat common names as DNS names
// because many TLS clients do.
if info . ParseError != nil ||
info . CertInfo . CommonNamesParseError != nil ||
info . CertInfo . DNSNamesParseError != nil ||
anyDnsNameMatches ( info . CertInfo . CommonNames ) ||
anyDnsNameMatches ( info . CertInfo . DNSNames ) {
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
}