// Let's Encrypt client to go!
// CLI application for generating Let's Encrypt certificates using the ACME package.
package main

import (
	"fmt"
	"log"
	"os"
	"path"
	"strings"
	"text/tabwriter"

	"github.com/urfave/cli"
	"github.com/xenolf/lego/acme"
)

// Logger is used to log errors; if nil, the default log.Logger is used.
var Logger *log.Logger

// logger is an helper function to retrieve the available logger
func logger() *log.Logger {
	if Logger == nil {
		Logger = log.New(os.Stderr, "", log.LstdFlags)
	}
	return Logger
}

var gittag string

func main() {
	app := cli.NewApp()
	app.Name = "lego"
	app.Usage = "Let's Encrypt client written in Go"

	version := "0.3.1"
	if strings.HasPrefix(gittag, "v") {
		version = gittag
	}

	app.Version = version

	acme.UserAgent = "lego/" + app.Version

	defaultPath := ""
	cwd, err := os.Getwd()
	if err == nil {
		defaultPath = path.Join(cwd, ".lego")
	}

	app.Before = func(c *cli.Context) error {
		if c.GlobalString("path") == "" {
			logger().Fatal("Could not determine current working directory. Please pass --path.")
		}
		return nil
	}

	app.Commands = []cli.Command{
		{
			Name:   "run",
			Usage:  "Register an account, then create and install a certificate",
			Action: run,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "no-bundle",
					Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
				},
				cli.BoolFlag{
					Name:  "must-staple",
					Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
				},
			},
		},
		{
			Name:   "revoke",
			Usage:  "Revoke a certificate",
			Action: revoke,
		},
		{
			Name:   "renew",
			Usage:  "Renew a certificate",
			Action: renew,
			Flags: []cli.Flag{
				cli.IntFlag{
					Name:  "days",
					Value: 0,
					Usage: "The number of days left on a certificate to renew it.",
				},
				cli.BoolFlag{
					Name:  "reuse-key",
					Usage: "Used to indicate you want to reuse your current private key for the new certificate.",
				},
				cli.BoolFlag{
					Name:  "no-bundle",
					Usage: "Do not create a certificate bundle by adding the issuers certificate to the new certificate.",
				},
				cli.BoolFlag{
					Name:  "must-staple",
					Usage: "Include the OCSP must staple TLS extension in the CSR and generated certificate. Only works if the CSR is generated by lego.",
				},
			},
		},
		{
			Name:   "dnshelp",
			Usage:  "Shows additional help for the --dns global option",
			Action: dnshelp,
		},
	}

	app.Flags = []cli.Flag{
		cli.StringSliceFlag{
			Name:  "domains, d",
			Usage: "Add domains to the process",
		},
		cli.StringFlag{
			Name:  "csr, c",
			Usage: "Certificate signing request filename, if an external CSR is to be used",
		},
		cli.StringFlag{
			Name:  "server, s",
			Value: "https://acme-v01.api.letsencrypt.org/directory",
			Usage: "CA hostname (and optionally :port). The server certificate must be trusted in order to avoid further modifications to the client.",
		},
		cli.StringFlag{
			Name:  "email, m",
			Usage: "Email used for registration and recovery contact.",
		},
		cli.BoolFlag{
			Name:  "accept-tos, a",
			Usage: "By setting this flag to true you indicate that you accept the current Let's Encrypt terms of service.",
		},
		cli.StringFlag{
			Name:  "key-type, k",
			Value: "rsa2048",
			Usage: "Key type to use for private keys. Supported: rsa2048, rsa4096, rsa8192, ec256, ec384",
		},
		cli.StringFlag{
			Name:  "path",
			Usage: "Directory to use for storing the data",
			Value: defaultPath,
		},
		cli.StringSliceFlag{
			Name:  "exclude, x",
			Usage: "Explicitly disallow solvers by name from being used. Solvers: \"http-01\", \"tls-sni-01\".",
		},
		cli.StringFlag{
			Name:  "webroot",
			Usage: "Set the webroot folder to use for HTTP based challenges to write directly in a file in .well-known/acme-challenge",
		},
		cli.StringSliceFlag{
			Name:  "memcached-host",
			Usage: "Set the memcached host(s) to use for HTTP based challenges. Challenges will be written to all specified hosts.",
		},
		cli.StringFlag{
			Name:  "http",
			Usage: "Set the port and interface to use for HTTP based challenges to listen on. Supported: interface:port or :port",
		},
		cli.StringFlag{
			Name:  "tls",
			Usage: "Set the port and interface to use for TLS based challenges to listen on. Supported: interface:port or :port",
		},
		cli.StringFlag{
			Name:  "dns",
			Usage: "Solve a DNS challenge using the specified provider. Disables all other challenges. Run 'lego dnshelp' for help on usage.",
		},
		cli.IntFlag{
			Name:  "http-timeout",
			Usage: "Set the HTTP timeout value to a specific value in seconds. The default is 10 seconds.",
		},
		cli.IntFlag{
			Name:  "dns-timeout",
			Usage: "Set the DNS timeout value to a specific value in seconds. The default is 10 seconds.",
		},
		cli.StringSliceFlag{
			Name:  "dns-resolvers",
			Usage: "Set the resolvers to use for performing recursive DNS queries. Supported: host:port. The default is to use Google's DNS resolvers.",
		},
		cli.BoolFlag{
			Name:  "pem",
			Usage: "Generate a .pem file by concatanating the .key and .crt files together.",
		},
	}

	err = app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

func dnshelp(c *cli.Context) error {
	fmt.Printf(
		`Credentials for DNS providers must be passed through environment variables.

Here is an example bash command using the CloudFlare DNS provider:

  $ CLOUDFLARE_EMAIL=foo@bar.com \
    CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
    lego --dns cloudflare --domains www.example.com --email me@bar.com run

`)

	w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
	fmt.Fprintln(w, "Valid providers and their associated credential environment variables:")
	fmt.Fprintln(w)
	fmt.Fprintln(w, "\tazure:\tAZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, AZURE_TENANT_ID, AZURE_RESOURCE_GROUP")
	fmt.Fprintln(w, "\tauroradns:\tAURORA_USER_ID, AURORA_KEY, AURORA_ENDPOINT")
	fmt.Fprintln(w, "\tcloudflare:\tCLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY")
	fmt.Fprintln(w, "\tdigitalocean:\tDO_AUTH_TOKEN")
	fmt.Fprintln(w, "\tdnsimple:\tDNSIMPLE_EMAIL, DNSIMPLE_OAUTH_TOKEN")
	fmt.Fprintln(w, "\tdnsmadeeasy:\tDNSMADEEASY_API_KEY, DNSMADEEASY_API_SECRET")
	fmt.Fprintln(w, "\texoscale:\tEXOSCALE_API_KEY, EXOSCALE_API_SECRET, EXOSCALE_ENDPOINT")
	fmt.Fprintln(w, "\tgandi:\tGANDI_API_KEY")
	fmt.Fprintln(w, "\tgcloud:\tGCE_PROJECT")
	fmt.Fprintln(w, "\tlinode:\tLINODE_API_KEY")
	fmt.Fprintln(w, "\tmanual:\tnone")
	fmt.Fprintln(w, "\tnamecheap:\tNAMECHEAP_API_USER, NAMECHEAP_API_KEY")
	fmt.Fprintln(w, "\trackspace:\tRACKSPACE_USER, RACKSPACE_API_KEY")
	fmt.Fprintln(w, "\trfc2136:\tRFC2136_TSIG_KEY, RFC2136_TSIG_SECRET,\n\t\tRFC2136_TSIG_ALGORITHM, RFC2136_NAMESERVER")
	fmt.Fprintln(w, "\troute53:\tAWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION")
	fmt.Fprintln(w, "\tdyn:\tDYN_CUSTOMER_NAME, DYN_USER_NAME, DYN_PASSWORD")
	fmt.Fprintln(w, "\tvultr:\tVULTR_API_KEY")
	fmt.Fprintln(w, "\tovh:\tOVH_ENDPOINT, OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY")
	fmt.Fprintln(w, "\tpdns:\tPDNS_API_KEY, PDNS_API_URL")
	fmt.Fprintln(w, "\tdnspod:\tDNSPOD_API_KEY")
	w.Flush()

	fmt.Println(`
For a more detailed explanation of a DNS provider's credential variables,
please consult their online documentation.`)

	return nil
}
