Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
ruanhaishen
Acme.Sh
Commits
f8029e2e
Commit
f8029e2e
authored
Mar 08, 2016
by
neil
Browse files
minor
parent
cd441d45
Changes
1
Hide whitespace changes
Inline
Side-by-side
le.sh
View file @
f8029e2e
#!/usr/bin/env bash
VER
=
1.1.8
PROJECT
=
"https://github.com/Neilpang/le"
DEFAULT_CA
=
"https://acme-v01.api.letsencrypt.org"
DEFAULT_AGREEMENT
=
"https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
STAGE_CA
=
"https://acme-staging.api.letsencrypt.org"
VTYPE_HTTP
=
"http-01"
VTYPE_DNS
=
"dns-01"
if
[
-z
"
$AGREEMENT
"
]
;
then
AGREEMENT
=
"
$DEFAULT_AGREEMENT
"
fi
_debug
()
{
if
[
-z
"
$DEBUG
"
]
;
then
return
fi
if
[
-z
"
$2
"
]
;
then
echo
$1
else
echo
"
$1
"
=
"
$2
"
fi
}
_info
()
{
if
[
-z
"
$2
"
]
;
then
echo
"
$1
"
else
echo
"
$1
"
=
"
$2
"
fi
}
_err
()
{
if
[
-z
"
$2
"
]
;
then
echo
"
$1
"
>
&2
else
echo
"
$1
"
=
"
$2
"
>
&2
fi
return
1
}
_h2b
()
{
hex
=
$(
cat
)
i
=
1
j
=
2
while
[
'1'
]
;
do
h
=
$(
printf
$hex
|
cut
-c
$i
-
$j
)
if
[
-z
"
$h
"
]
;
then
break
;
fi
printf
"
\x
$h
"
let
"i+=2"
let
"j+=2"
done
}
_base64
()
{
openssl
base64
-e
|
tr
-d
'\n'
}
#domain [2048]
createAccountKey
()
{
_info
"Creating account key"
if
[
-z
"
$1
"
]
;
then
echo
Usage: createAccountKey account-domain
[
2048]
return
fi
account
=
$1
length
=
$2
if
[[
"
$length
"
==
"ec-"
*
]]
;
then
length
=
2048
fi
if
[
-z
"
$2
"
]
;
then
_info
"Use default length 2048"
length
=
2048
fi
_initpath
if
[
-f
"
$ACCOUNT_KEY_PATH
"
]
;
then
_info
"Account key exists, skip"
return
else
#generate account key
openssl genrsa
$length
>
"
$ACCOUNT_KEY_PATH
"
fi
}
#domain length
createDomainKey
()
{
_info
"Creating domain key"
if
[
-z
"
$1
"
]
;
then
echo
Usage: createDomainKey domain
[
2048]
return
fi
domain
=
$1
length
=
$2
isec
=
""
if
[[
"
$length
"
==
"ec-"
*
]]
;
then
isec
=
"1"
length
=
$(
printf
$length
|
cut
-d
'-'
-f
2-100
)
eccname
=
"
$length
"
fi
if
[
-z
"
$length
"
]
;
then
if
[
"
$isec
"
]
;
then
length
=
256
else
length
=
2048
fi
fi
_info
"Use length
$length
"
if
[
"
$isec
"
]
;
then
if
[
"
$length
"
==
"256"
]
;
then
eccname
=
"prime256v1"
fi
if
[
"
$length
"
==
"384"
]
;
then
eccname
=
"secp384r1"
fi
if
[
"
$length
"
==
"521"
]
;
then
eccname
=
"secp521r1"
fi
_info
"Using ec name:
$eccname
"
fi
_initpath
$domain
if
[
!
-f
"
$CERT_KEY_PATH
"
]
||
(
[
"
$FORCE
"
]
&&
!
[
"
$IS_RENEW
"
]
)
;
then
#generate account key
if
[
"
$isec
"
]
;
then
openssl ecparam
-name
$eccname
-genkey
2>/dev/null
>
"
$CERT_KEY_PATH
"
else
openssl genrsa
$length
2>/dev/null
>
"
$CERT_KEY_PATH
"
fi
else
if
[
"
$IS_RENEW
"
]
;
then
_info
"Domain key exists, skip"
return
0
else
_err
"Domain key exists, do you want to overwrite the key?"
_err
"Set FORCE=1, and try again."
return
1
fi
fi
}
# domain domainlist
createCSR
()
{
_info
"Creating csr"
if
[
-z
"
$1
"
]
;
then
echo
Usage:
$0
domain
[
domainlist]
return
fi
domain
=
$1
_initpath
$domain
domainlist
=
$2
if
[
-f
"
$CSR_PATH
"
]
&&
[
"
$IS_RENEW
"
]
&&
!
[
"
$FORCE
"
]
;
then
_info
"CSR exists, skip"
return
fi
if
[
-z
"
$domainlist
"
]
;
then
#single domain
_info
"Single domain"
$domain
openssl req
-new
-sha256
-key
"
$CERT_KEY_PATH
"
-subj
"/CN=
$domain
"
>
"
$CSR_PATH
"
else
alt
=
"DNS:
$(
echo
$domainlist
|
sed
"s/,/,DNS:/g"
)
"
#multi
_info
"Multi domain"
"
$alt
"
printf
"[ req_distinguished_name ]
\n
[ req ]
\n
distinguished_name = req_distinguished_name
\n
[SAN]
\n
subjectAltName=
$alt
"
>
"
$DOMAIN_SSL_CONF
"
openssl req
-new
-sha256
-key
"
$CERT_KEY_PATH
"
-subj
"/CN=
$domain
"
-reqexts
SAN
-config
"
$DOMAIN_SSL_CONF
"
-out
"
$CSR_PATH
"
fi
}
_b64
()
{
__n
=
$(
cat
)
echo
$__n
|
tr
'/+'
'_-'
|
tr
-d
'= '
}
_time2str
()
{
#BSD
if
date
-u
-d
@
$1
2>/dev/null
;
then
return
fi
#Linux
if
date
-u
-r
$1
2>/dev/null
;
then
return
fi
}
_send_signed_request
()
{
url
=
$1
payload
=
$2
needbase64
=
$3
_debug url
$url
_debug payload
"
$payload
"
CURL_HEADER
=
"
$LE_WORKING_DIR
/curl.header"
dp
=
"
$LE_WORKING_DIR
/curl.dump"
CURL
=
"curl --silent --dump-header
$CURL_HEADER
"
if
[
"
$DEBUG
"
]
;
then
CURL
=
"
$CURL
--trace-ascii
$dp
"
fi
payload64
=
$(
echo
-n
$payload
| _base64 | _b64
)
_debug payload64
$payload64
nonceurl
=
"
$API
/directory"
nonce
=
"
$(
$CURL
-I
$nonceurl
|
grep
-o
"^Replay-Nonce:.*$"
|
tr
-d
"
\r\n
"
|
cut
-d
' '
-f
2
)
"
_debug nonce
"
$nonce
"
protected
=
"
$(
printf
"
$HEADERPLACE
"
|
sed
"s/NONCE/
$nonce
/"
)
"
_debug protected
"
$protected
"
protected64
=
"
$(
printf
"
$protected
"
| _base64 | _b64
)
"
_debug protected64
"
$protected64
"
sig
=
$(
echo
-n
"
$protected64
.
$payload64
"
| openssl dgst
-sha256
-sign
$ACCOUNT_KEY_PATH
| _base64 | _b64
)
_debug sig
"
$sig
"
body
=
"{
\"
header
\"
:
$HEADER
,
\"
protected
\"
:
\"
$protected64
\"
,
\"
payload
\"
:
\"
$payload64
\"
,
\"
signature
\"
:
\"
$sig
\"
}"
_debug body
"
$body
"
if
[
"
$needbase64
"
]
;
then
response
=
"
$(
$CURL
-X
POST
--data
"
$body
"
$url
| _base64
)
"
else
response
=
"
$(
$CURL
-X
POST
--data
"
$body
"
$url
)
"
fi
responseHeaders
=
"
$(
cat
$CURL_HEADER
)
"
_debug responseHeaders
"
$responseHeaders
"
_debug response
"
$response
"
code
=
"
$(
grep
^HTTP
$CURL_HEADER
|
tail
-1
|
cut
-d
" "
-f
2 |
tr
-d
"
\r\n
"
)
"
_debug code
$code
}
_get
()
{
url
=
"
$1
"
_debug url
$url
response
=
"
$(
curl
--silent
$url
)
"
ret
=
$?
_debug response
"
$response
"
code
=
"
$(
echo
$response
|
grep
-o
'"status":[0-9]\+'
|
cut
-d
:
-f
2
)
"
_debug code
$code
return
$ret
}
#setopt "file" "opt" "=" "value" [";"]
_setopt
()
{
__conf
=
"
$1
"
__opt
=
"
$2
"
__sep
=
"
$3
"
__val
=
"
$4
"
__end
=
"
$5
"
if
[
-z
"
$__opt
"
]
;
then
echo
usage: _setopt
'"file" "opt" "=" "value" [";"]'
return
fi
if
[
!
-f
"
$__conf
"
]
;
then
touch
"
$__conf
"
fi
if
grep
-H
-n
"^
$__opt$__sep
"
"
$__conf
"
>
/dev/null
;
then
_debug OK
if
[[
"
$__val
"
==
*
"&"
*
]]
;
then
__val
=
"
$(
echo
$__val
|
sed
's/&/\\&/g'
)
"
fi
text
=
"
$(
cat
$__conf
)
"
printf
"
$text
"
|
sed
"s|^
$__opt$__sep
.*
$|$__opt$__sep$__val$__end
|"
>
"
$__conf
"
elif
grep
-H
-n
"^#
$__opt$__sep
"
"
$__conf
"
>
/dev/null
;
then
if
[[
"
$__val
"
==
*
"&"
*
]]
;
then
__val
=
"
$(
echo
$__val
|
sed
's/&/\\&/g'
)
"
fi
text
=
"
$(
cat
$__conf
)
"
printf
"
$text
"
|
sed
"s|^#
$__opt$__sep
.*
$|$__opt$__sep$__val$__end
|"
>
"
$__conf
"
else
_debug APP
echo
"
$__opt$__sep$__val$__end
"
>>
"
$__conf
"
fi
_debug
"
$(
grep
-H
-n
"^
$__opt$__sep
"
$__conf
)
"
}
#_savedomainconf key value
#save to domain.conf
_savedomainconf
()
{
key
=
"
$1
"
value
=
"
$2
"
if
[
"
$DOMAIN_CONF
"
]
;
then
_setopt
$DOMAIN_CONF
"
$key
"
"="
"
$value
"
else
_err
"DOMAIN_CONF is empty, can not save
$key
=
$value
"
fi
}
#_saveaccountconf key value
_saveaccountconf
()
{
key
=
"
$1
"
value
=
"
$2
"
if
[
"
$ACCOUNT_CONF_PATH
"
]
;
then
_setopt
$ACCOUNT_CONF_PATH
"
$key
"
"="
"
$value
"
else
_err
"ACCOUNT_CONF_PATH is empty, can not save
$key
=
$value
"
fi
}
_startserver
()
{
content
=
"
$1
"
_NC
=
"nc -q 1"
if
nc
-h
2>&1 |
grep
"nmap.org/ncat"
>
/dev/null
;
then
_NC
=
"nc"
fi
# while true ; do
if
[
"
$DEBUG
"
]
;
then
echo
-e
-n
"HTTP/1.1 200 OK
\r\n\r\n
$content
"
|
$_NC
-l
-p
$Le_HTTPPort
-vv
else
echo
-e
-n
"HTTP/1.1 200 OK
\r\n\r\n
$content
"
|
$_NC
-l
-p
$Le_HTTPPort
>
/dev/null
fi
# done
}
_stopserver
()
{
pid
=
"
$1
"
}
_initpath
()
{
if
[
-z
"
$LE_WORKING_DIR
"
]
;
then
LE_WORKING_DIR
=
$HOME
/.le
fi
if
[
-z
"
$ACCOUNT_CONF_PATH
"
]
;
then
ACCOUNT_CONF_PATH
=
"
$LE_WORKING_DIR
/account.conf"
fi
if
[
-f
"
$ACCOUNT_CONF_PATH
"
]
;
then
source
"
$ACCOUNT_CONF_PATH
"
fi
if
[
-z
"
$API
"
]
;
then
if
[
-z
"
$STAGE
"
]
;
then
API
=
"
$DEFAULT_CA
"
else
API
=
"
$STAGE_CA
"
_info
"Using stage api:
$API
"
fi
fi
if
[
-z
"
$ACME_DIR
"
]
;
then
ACME_DIR
=
"/home/.acme"
fi
if
[
-z
"
$APACHE_CONF_BACKUP_DIR
"
]
;
then
APACHE_CONF_BACKUP_DIR
=
"
$LE_WORKING_DIR
/"
fi
domain
=
"
$1
"
mkdir
-p
"
$LE_WORKING_DIR
"
if
[
-z
"
$ACCOUNT_KEY_PATH
"
]
;
then
ACCOUNT_KEY_PATH
=
"
$LE_WORKING_DIR
/account.key"
fi
if
[
-z
"
$domain
"
]
;
then
return
0
fi
domainhome
=
"
$LE_WORKING_DIR
/
$domain
"
mkdir
-p
"
$domainhome
"
if
[
-z
"
$DOMAIN_PATH
"
]
;
then
DOMAIN_PATH
=
"
$domainhome
"
fi
if
[
-z
"
$DOMAIN_CONF
"
]
;
then
DOMAIN_CONF
=
"
$domainhome
/
$Le_Domain
.conf"
fi
if
[
-z
"
$DOMAIN_SSL_CONF
"
]
;
then
DOMAIN_SSL_CONF
=
"
$domainhome
/
$Le_Domain
.ssl.conf"
fi
if
[
-z
"
$CSR_PATH
"
]
;
then
CSR_PATH
=
"
$domainhome
/
$domain
.csr"
fi
if
[
-z
"
$CERT_KEY_PATH
"
]
;
then
CERT_KEY_PATH
=
"
$domainhome
/
$domain
.key"
fi
if
[
-z
"
$CERT_PATH
"
]
;
then
CERT_PATH
=
"
$domainhome
/
$domain
.cer"
fi
if
[
-z
"
$CA_CERT_PATH
"
]
;
then
CA_CERT_PATH
=
"
$domainhome
/ca.cer"
fi
}
_apachePath
()
{
httpdroot
=
"
$(
apachectl
-V
|
grep
HTTPD_ROOT
=
|
cut
-d
=
-f
2 |
tr
-d
'"'
)
"
httpdconfname
=
"
$(
apachectl
-V
|
grep
SERVER_CONFIG_FILE
=
|
cut
-d
=
-f
2 |
tr
-d
'"'
)
"
httpdconf
=
"
$httpdroot
/
$httpdconfname
"
if
[
!
-f
$httpdconf
]
;
then
_err
"Apache Config file not found"
$httpdconf
return
1
fi
return
0
}
_restoreApache
()
{
if
[
-z
"
$usingApache
"
]
;
then
return
0
fi
_initpath
if
!
_apachePath
;
then
return
1
fi
if
[
!
-f
"
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
]
;
then
_debug
"No config file to restore."
return
0
fi
cp
-p
"
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
"
$httpdconf
"
if
!
apachectl
-t
;
then
_err
"Sorry, restore apache config error, please contact me."
return
1
;
fi
rm
-f
"
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
return
0
}
_setApache
()
{
_initpath
if
!
_apachePath
;
then
return
1
fi
#backup the conf
_debug
"Backup apache config file"
$httpdconf
cp
-p
$httpdconf
$APACHE_CONF_BACKUP_DIR
/
_info
"JFYI, Config file
$httpdconf
is backuped to
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
_info
"In case there is an error that can not be restored automatically, you may try restore it yourself."
_info
"The backup file will be deleted on sucess, just forget it."
#add alias
echo
"
Alias /.well-known/acme-challenge
$ACME_DIR
<Directory
$ACME_DIR
>
Require all granted
</Directory>
"
>>
$httpdconf
if
!
apachectl
-t
;
then
_err
"Sorry, apache config error, please contact me."
_restoreApache
return
1
;
fi
if
[
!
-d
"
$ACME_DIR
"
]
;
then
mkdir
-p
"
$ACME_DIR
"
chmod
755
"
$ACME_DIR
"
fi
if
!
apachectl graceful
;
then
_err
"Sorry, apachectl graceful error, please contact me."
_restoreApache
return
1
;
fi
usingApache
=
"1"
return
0
}
_clearup
()
{
_stopserver
$serverproc
serverproc
=
""
_restoreApache
}
# webroot removelevel tokenfile
_clearupwebbroot
()
{
__webroot
=
"
$1
"
if
[
-z
"
$__webroot
"
]
;
then
_debug
"no webroot specified, skip"
return
0
fi
if
[
"
$2
"
==
'1'
]
;
then
_debug
"remove
$__webroot
/.well-known"
rm
-rf
"
$__webroot
/.well-known"
elif
[
"
$2
"
==
'2'
]
;
then
_debug
"remove
$__webroot
/.well-known/acme-challenge"
rm
-rf
"
$__webroot
/.well-known/acme-challenge"
elif
[
"
$2
"
==
'3'
]
;
then
_debug
"remove
$__webroot
/.well-known/acme-challenge/
$3
"
rm
-rf
"
$__webroot
/.well-known/acme-challenge/
$3
"
else
_info
"Skip for removelevel:
$2
"
fi
return
0
}
issue
()
{
if
[
-z
"
$2
"
]
;
then
_err
"Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no"
return
1
fi
Le_Webroot
=
"
$1
"
Le_Domain
=
"
$2
"
Le_Alt
=
"
$3
"
Le_Keylength
=
"
$4
"
Le_RealCertPath
=
"
$5
"
Le_RealKeyPath
=
"
$6
"
Le_RealCACertPath
=
"
$7
"
Le_ReloadCmd
=
"
$8
"
_initpath
$Le_Domain
if
[
-f
"
$DOMAIN_CONF
"
]
;
then
Le_NextRenewTime
=
$(
grep
"^Le_NextRenewTime="
"
$DOMAIN_CONF
"
|
cut
-d
'='
-f
2
)
if
[
-z
"
$FORCE
"
]
&&
[
"
$Le_NextRenewTime
"
]
&&
[
"
$(
date
-u
"+%s"
)
"
-lt
"
$Le_NextRenewTime
"
]
;
then
_info
"Skip, Next renewal time is:
$(
grep
"^Le_NextRenewTimeStr"
"
$DOMAIN_CONF
"
|
cut
-d
'='
-f
2
)
"
return
2
fi
fi
if
[
"
$Le_Alt
"
==
"no"
]
;
then
Le_Alt
=
""
fi
if
[
"
$Le_Keylength
"
==
"no"
]
;
then
Le_Keylength
=
""
fi
if
[
"
$Le_RealCertPath
"
==
"no"
]
;
then
Le_RealCertPath
=
""
fi
if
[
"
$Le_RealKeyPath
"
==
"no"
]
;
then
Le_RealKeyPath
=
""
fi
if
[
"
$Le_RealCACertPath
"
==
"no"
]
;
then
Le_RealCACertPath
=
""
fi
if
[
"
$Le_ReloadCmd
"
==
"no"
]
;
then
Le_ReloadCmd
=
""
fi
_setopt
"
$DOMAIN_CONF
"
"Le_Domain"
"="
"
$Le_Domain
"
_setopt
"
$DOMAIN_CONF
"
"Le_Alt"
"="
"
$Le_Alt
"
_setopt
"
$DOMAIN_CONF
"
"Le_Webroot"
"="
"
$Le_Webroot
"
_setopt
"
$DOMAIN_CONF
"
"Le_Keylength"
"="
"
$Le_Keylength
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealCertPath"
"="
"
\"
$Le_RealCertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealCACertPath"
"="
"
\"
$Le_RealCACertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealKeyPath"
"="
"
\"
$Le_RealKeyPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_ReloadCmd"
"="
"
\"
$Le_ReloadCmd
\"
"
if
[
"
$Le_Webroot
"
==
"no"
]
;
then
_info
"Standalone mode."
if
!
command
-v
"nc"
>
/dev/null
;
then
_err
"Please install netcat(nc) tools first."
return
1
fi
if
[
-z
"
$Le_HTTPPort
"
]
;
then
Le_HTTPPort
=
80
fi
_setopt
"
$DOMAIN_CONF
"
"Le_HTTPPort"
"="
"
$Le_HTTPPort
"
netprc
=
"
$(
ss
-ntpl
|
grep
:
$Le_HTTPPort
" "
)
"
if
[
"
$netprc
"
]
;
then
_err
"
$netprc
"
_err
"tcp port
$Le_HTTPPort
is already used by
$(
echo
"
$netprc
"
|
cut
-d
:
-f
4
)
"
_err
"Please stop it first"
return
1
fi
fi
if
[
"
$Le_Webroot
"
==
"apache"
]
;
then
if
!
_setApache
;
then
_err
"set up apache error. Report error to me."
return
1
fi
wellknown_path
=
"
$ACME_DIR
"
else
usingApache
=
""
fi
createAccountKey
$Le_Domain
$Le_Keylength
if
!
createDomainKey
$Le_Domain
$Le_Keylength
;
then
_err
"Create domain key error."
return
1
fi
if
!
createCSR
$Le_Domain
$Le_Alt
;
then
_err
"Create CSR error."
return
1
fi
pub_exp
=
$(
openssl rsa
-in
$ACCOUNT_KEY_PATH
-noout
-text
|
grep
"^publicExponent:"
|
cut
-d
'('
-f
2 |
cut
-d
'x'
-f
2 |
cut
-d
')'
-f
1
)
if
[
"
${#
pub_exp
}
"
==
"5"
]
;
then
pub_exp
=
0
$pub_exp
fi
_debug pub_exp
"
$pub_exp
"
e
=
$(
echo
$pub_exp
| _h2b | _base64
)
_debug e
"
$e
"
modulus
=
$(
openssl rsa
-in
$ACCOUNT_KEY_PATH
-modulus
-noout
|
cut
-d
'='
-f
2
)
n
=
$(
echo
$modulus
| _h2b | _base64 | _b64
)
jwk
=
'{"e": "'
$e
'", "kty": "RSA", "n": "'
$n
'"}'
HEADER
=
'{"alg": "RS256", "jwk": '
$jwk
'}'
HEADERPLACE
=
'{"nonce": "NONCE", "alg": "RS256", "jwk": '
$jwk
'}'
_debug HEADER
"
$HEADER
"
accountkey_json
=
$(
echo
-n
"
$jwk
"
|
tr
-d
' '
)
thumbprint
=
$(
echo
-n
"
$accountkey_json
"
| openssl dgst
-sha256
-binary
| _base64 | _b64
)
_info
"Registering account"
regjson
=
'{"resource": "new-reg", "agreement": "'
$AGREEMENT
'"}'
if
[
"
$ACCOUNT_EMAIL
"
]
;
then
regjson
=
'{"resource": "new-reg", "contact": ["mailto: '
$ACCOUNT_EMAIL
'"], "agreement": "'
$AGREEMENT
'"}'
fi
_send_signed_request
"
$API
/acme/new-reg"
"
$regjson
"
if
[
"
$code
"
==
""
]
||
[
"
$code
"
==
'201'
]
;
then
_info
"Registered"
echo
$response
>
$LE_WORKING_DIR
/account.json
elif
[
"
$code
"
==
'409'
]
;
then
_info
"Already registered"
else
_err
"Register account Error."
_clearup
return
1
fi
vtype
=
"
$VTYPE_HTTP
"
if
[[
"
$Le_Webroot
"
==
"dns"
*
]]
;
then
vtype
=
"
$VTYPE_DNS
"
fi
vlist
=
"
$Le_Vlist
"
# verify each domain
_info
"Verify each domain"
sep
=
'#'
if
[
-z
"
$vlist
"
]
;
then
alldomains
=
$(
echo
"
$Le_Domain
,
$Le_Alt
"
|
tr
','
' '
)
for
d
in
$alldomains
do
_info
"Getting token for domain"
$d
_send_signed_request
"
$API
/acme/new-authz"
"{
\"
resource
\"
:
\"
new-authz
\"
,
\"
identifier
\"
: {
\"
type
\"
:
\"
dns
\"
,
\"
value
\"
:
\"
$d
\"
}}"
if
[
!
-z
"
$code
"
]
&&
[
!
"
$code
"
==
'201'
]
;
then
_err
"new-authz error:
$response
"
_clearup
return
1
fi
entry
=
"
$(
printf
$response
| egrep
-o
'{[^{]*"type":"'
$vtype
'"[^}]*'
)
"
_debug entry
"
$entry
"
token
=
"
$(
printf
"
$entry
"
| egrep
-o
'"token":"[^"]*'
|
cut
-d
:
-f
2 |
tr
-d
'"'
)
"
_debug token
$token
uri
=
"
$(
printf
"
$entry
"
| egrep
-o
'"uri":"[^"]*'
|
cut
-d
:
-f
2,3 |
tr
-d
'"'
)
"
_debug uri
$uri
keyauthorization
=
"
$token
.
$thumbprint
"
_debug keyauthorization
"
$keyauthorization
"
dvlist
=
"
$d$sep$keyauthorization$sep$uri
"
_debug dvlist
"
$dvlist
"
vlist
=
"
$vlist$dvlist
,"
done
#add entry
dnsadded
=
""
ventries
=
$(
echo
"
$vlist
"
|
tr
','
' '
)
for
ventry
in
$ventries
do
d
=
$(
echo
$ventry
|
cut
-d
$sep
-f
1
)
keyauthorization
=
$(
echo
$ventry
|
cut
-d
$sep
-f
2
)
if
[
"
$vtype
"
==
"
$VTYPE_DNS
"
]
;
then
dnsadded
=
'0'
txtdomain
=
"_acme-challenge.
$d
"
_debug txtdomain
"
$txtdomain
"
txt
=
"
$(
echo
-e
-n
$keyauthorization
| openssl dgst
-sha256
-binary
| _base64 | _b64
)
"
_debug txt
"
$txt
"
#dns
#1. check use api
d_api
=
""
if
[
-f
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
"
elif
[
-f
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
.sh"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
.sh"
elif
[
-f
"
$LE_WORKING_DIR
/
$Le_Webroot
"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$Le_Webroot
"
elif
[
-f
"
$LE_WORKING_DIR
/
$Le_Webroot
.sh"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$Le_Webroot
.sh"
elif
[
-f
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
"
elif
[
-f
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
.sh"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
.sh"
fi
_debug d_api
"
$d_api
"
if
[
"
$d_api
"
]
;
then
_info
"Found domain api file:
$d_api
"
else
_err
"Add the following TXT record:"
_err
"Domain:
$txtdomain
"
_err
"TXT value:
$txt
"
_err
"Please be aware that you prepend _acme-challenge. before your domain"
_err
"so the resulting subdomain will be:
$txtdomain
"
continue
fi
if
!
source
$d_api
;
then
_err
"Load file
$d_api
error. Please check your api file and try again."
return
1
fi
addcommand
=
"
$Le_Webroot
-add"
if
!
command
-v
$addcommand
;
then
_err
"It seems that your api file is not correct, it must have a function named:
$Le_Webroot
"
return
1
fi
if
!
$addcommand
$txtdomain
$txt
;
then
_err
"Error add txt for domain:
$txtdomain
"
return
1
fi
dnsadded
=
'1'
fi
done
if
[
"
$dnsadded
"
==
'0'
]
;
then
_setopt
"
$DOMAIN_CONF
"
"Le_Vlist"
"="
"
\"
$vlist
\"
"
_debug
"Dns record not added yet, so, save to
$DOMAIN_CONF
and exit."
_err
"Please add the TXT records to the domains, and retry again."
return
1
fi
fi
if
[
"
$dnsadded
"
==
'1'
]
;
then
_info
"Sleep 60 seconds for the txt records to take effect"
sleep
60
fi
_debug
"ok, let's start to verify"
ventries
=
$(
echo
"
$vlist
"
|
tr
','
' '
)
for
ventry
in
$ventries
do
d
=
$(
echo
$ventry
|
cut
-d
$sep
-f
1
)
keyauthorization
=
$(
echo
$ventry
|
cut
-d
$sep
-f
2
)
uri
=
$(
echo
$ventry
|
cut
-d
$sep
-f
3
)
_info
"Verifying:
$d
"
_debug
"d"
"
$d
"
_debug
"keyauthorization"
"
$keyauthorization
"
_debug
"uri"
"
$uri
"
removelevel
=
""
token
=
""
if
[
"
$vtype
"
==
"
$VTYPE_HTTP
"
]
;
then
if
[
"
$Le_Webroot
"
==
"no"
]
;
then
_info
"Standalone mode server"
_startserver
"
$keyauthorization
"
&
serverproc
=
"
$!
"
sleep
2
_debug serverproc
$serverproc
else
if
[
-z
"
$wellknown_path
"
]
;
then
wellknown_path
=
"
$Le_Webroot
/.well-known/acme-challenge"
fi
_debug wellknown_path
"
$wellknown_path
"
if
[
!
-d
"
$Le_Webroot
/.well-known"
]
;
then
removelevel
=
'1'
elif
[
!
-d
"
$Le_Webroot
/.well-known/acme-challenge"
]
;
then
removelevel
=
'2'
else
removelevel
=
'3'
fi
token
=
"
$(
echo
-e
-n
"
$keyauthorization
"
|
cut
-d
'.'
-f
1
)
"
_debug
"writing token:
$token
to
$wellknown_path
/
$token
"
mkdir
-p
"
$wellknown_path
"
echo
-n
"
$keyauthorization
"
>
"
$wellknown_path
/
$token
"
webroot_owner
=
$(
stat
-c
'%U:%G'
$Le_Webroot
)
_debug
"Changing owner/group of .well-known to
$webroot_owner
"
chown
-R
$webroot_owner
"
$Le_Webroot
/.well-known"
fi
fi
_send_signed_request
$uri
"{
\"
resource
\"
:
\"
challenge
\"
,
\"
keyAuthorization
\"
:
\"
$keyauthorization
\"
}"
if
[
!
-z
"
$code
"
]
&&
[
!
"
$code
"
==
'202'
]
;
then
_err
"
$d
:Challenge error:
$resource
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
fi
while
[
"1"
]
;
do
_debug
"sleep 5 secs to verify"
sleep
5
_debug
"checking"
if
!
_get
$uri
;
then
_err
"
$d
:Verify error:
$resource
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
fi
status
=
$(
echo
$response
| egrep
-o
'"status":"[^"]+"'
|
cut
-d
:
-f
2 |
tr
-d
'"'
)
if
[
"
$status
"
==
"valid"
]
;
then
_info
"Success"
_stopserver
$serverproc
serverproc
=
""
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
break
;
fi
if
[
"
$status
"
==
"invalid"
]
;
then
error
=
$(
echo
$response
| egrep
-o
'"error":{[^}]*}'
|
grep
-o
'"detail":"[^"]*"'
|
cut
-d
'"'
-f
4
)
_err
"
$d
:Verify error:
$error
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
;
fi
if
[
"
$status
"
==
"pending"
]
;
then
_info
"Pending"
else
_err
"
$d
:Verify error:
$response
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
fi
done
done
_clearup
_info
"Verify finished, start to sign."
der
=
"
$(
openssl req
-in
$CSR_PATH
-outform
DER | _base64 | _b64
)
"
_send_signed_request
"
$API
/acme/new-cert"
"{
\"
resource
\"
:
\"
new-cert
\"
,
\"
csr
\"
:
\"
$der
\"
}"
"needbase64"
Le_LinkCert
=
"
$(
grep
-i
-o
'^Location.*$'
$CURL_HEADER
|
tr
-d
"
\r\n
"
|
cut
-d
" "
-f
2
)
"
_setopt
"
$DOMAIN_CONF
"
"Le_LinkCert"
"="
"
$Le_LinkCert
"
if
[
"
$Le_LinkCert
"
]
;
then
echo
-----BEGIN
CERTIFICATE-----
>
"
$CERT_PATH
"
curl
--silent
"
$Le_LinkCert
"
| openssl
base64
-e
>>
"
$CERT_PATH
"
echo
-----END
CERTIFICATE-----
>>
"
$CERT_PATH
"
_info
"Cert success."
cat
"
$CERT_PATH
"
_info
"Your cert is in
$CERT_PATH
"
fi
if
[
-z
"
$Le_LinkCert
"
]
;
then
response
=
"
$(
echo
$response
| openssl
base64
-d
-A
)
"
_err
"Sign failed:
$(
echo
"
$response
"
|
grep
-o
'"detail":"[^"]*"'
)
"
return
1
fi
_setopt
"
$DOMAIN_CONF
"
'Le_Vlist'
'='
"
\"\"
"
Le_LinkIssuer
=
$(
grep
-i
'^Link'
$CURL_HEADER
|
cut
-d
" "
-f
2|
cut
-d
';'
-f
1 |
tr
-d
'<>'
)
_setopt
"
$DOMAIN_CONF
"
"Le_LinkIssuer"
"="
"
$Le_LinkIssuer
"
if
[
"
$Le_LinkIssuer
"
]
;
then
echo
-----BEGIN
CERTIFICATE-----
>
"
$CA_CERT_PATH
"
curl
--silent
"
$Le_LinkIssuer
"
| openssl
base64
-e
>>
"
$CA_CERT_PATH
"
echo
-----END
CERTIFICATE-----
>>
"
$CA_CERT_PATH
"
_info
"The intermediate CA cert is in
$CA_CERT_PATH
"
fi
Le_CertCreateTime
=
$(
date
-u
"+%s"
)
_setopt
"
$DOMAIN_CONF
"
"Le_CertCreateTime"
"="
"
$Le_CertCreateTime
"
Le_CertCreateTimeStr
=
$(
date
-u
)
_setopt
"
$DOMAIN_CONF
"
"Le_CertCreateTimeStr"
"="
"
\"
$Le_CertCreateTimeStr
\"
"
if
[
!
"
$Le_RenewalDays
"
]
;
then
Le_RenewalDays
=
80
fi
_setopt
"
$DOMAIN_CONF
"
"Le_RenewalDays"
"="
"
$Le_RenewalDays
"
let
"Le_NextRenewTime=Le_CertCreateTime+Le_RenewalDays*24*60*60"
_setopt
"
$DOMAIN_CONF
"
"Le_NextRenewTime"
"="
"
$Le_NextRenewTime
"
Le_NextRenewTimeStr
=
$(
_time2str
$Le_NextRenewTime
)
_setopt
"
$DOMAIN_CONF
"
"Le_NextRenewTimeStr"
"="
"
\"
$Le_NextRenewTimeStr
\"
"
installcert
$Le_Domain
"
$Le_RealCertPath
"
"
$Le_RealKeyPath
"
"
$Le_RealCACertPath
"
"
$Le_ReloadCmd
"
}
renew
()
{
Le_Domain
=
"
$1
"
if
[
-z
"
$Le_Domain
"
]
;
then
_err
"Usage:
$0
domain.com"
return
1
fi
_initpath
$Le_Domain
if
[
!
-f
"
$DOMAIN_CONF
"
]
;
then
_info
"
$Le_Domain
is not a issued domain, skip."
return
0
;
fi
source
"
$DOMAIN_CONF
"
if
[
-z
"
$FORCE
"
]
&&
[
"
$Le_NextRenewTime
"
]
&&
[
"
$(
date
-u
"+%s"
)
"
-lt
"
$Le_NextRenewTime
"
]
;
then
_info
"Skip, Next renewal time is:
$Le_NextRenewTimeStr
"
return
2
fi
IS_RENEW
=
"1"
issue
"
$Le_Webroot
"
"
$Le_Domain
"
"
$Le_Alt
"
"
$Le_Keylength
"
"
$Le_RealCertPath
"
"
$Le_RealKeyPath
"
"
$Le_RealCACertPath
"
"
$Le_ReloadCmd
"
local
res
=
$?
IS_RENEW
=
""
return
$res
}
renewAll
()
{
_initpath
_info
"renewAll"
for
d
in
$(
ls
-F
$LE_WORKING_DIR
|
grep
[
^.].
*
[
.].
*
/
$
)
;
do
d
=
$(
echo
$d
|
cut
-d
'/'
-f
1
)
_info
"renew
$d
"
Le_LinkCert
=
""
Le_Domain
=
""
Le_Alt
=
""
Le_Webroot
=
""
Le_Keylength
=
""
Le_LinkIssuer
=
""
Le_CertCreateTime
=
""
Le_CertCreateTimeStr
=
""
Le_RenewalDays
=
""
Le_NextRenewTime
=
""
Le_NextRenewTimeStr
=
""
Le_RealCertPath
=
""
Le_RealKeyPath
=
""
Le_RealCACertPath
=
""
Le_ReloadCmd
=
""
DOMAIN_PATH
=
""
DOMAIN_CONF
=
""
DOMAIN_SSL_CONF
=
""
CSR_PATH
=
""
CERT_KEY_PATH
=
""
CERT_PATH
=
""
CA_CERT_PATH
=
""
ACCOUNT_KEY_PATH
=
""
wellknown_path
=
""
renew
"
$d
"
done
}
installcert
()
{
Le_Domain
=
"
$1
"
if
[
-z
"
$Le_Domain
"
]
;
then
_err
"Usage:
$0
domain.com [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no"
return
1
fi
Le_RealCertPath
=
"
$2
"
Le_RealKeyPath
=
"
$3
"
Le_RealCACertPath
=
"
$4
"
Le_ReloadCmd
=
"
$5
"
_initpath
$Le_Domain
_setopt
"
$DOMAIN_CONF
"
"Le_RealCertPath"
"="
"
\"
$Le_RealCertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealCACertPath"
"="
"
\"
$Le_RealCACertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealKeyPath"
"="
"
\"
$Le_RealKeyPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_ReloadCmd"
"="
"
\"
$Le_ReloadCmd
\"
"
if
[
"
$Le_RealCertPath
"
]
;
then
if
[
-f
"
$Le_RealCertPath
"
]
;
then
cp
-p
"
$Le_RealCertPath
"
"
$Le_RealCertPath
"
.bak
fi
cat
"
$CERT_PATH
"
>
"
$Le_RealCertPath
"
fi
if
[
"
$Le_RealCACertPath
"
]
;
then
if
[
-f
"
$Le_RealCACertPath
"
]
;
then
cp
-p
"
$Le_RealCACertPath
"
"
$Le_RealCACertPath
"
.bak
fi
if
[
"
$Le_RealCACertPath
"
==
"
$Le_RealCertPath
"
]
;
then
echo
""
>>
"
$Le_RealCACertPath
"
cat
"
$CA_CERT_PATH
"
>>
"
$Le_RealCACertPath
"
else
cat
"
$CA_CERT_PATH
"
>
"
$Le_RealCACertPath
"
fi
fi
if
[
"
$Le_RealKeyPath
"
]
;
then
if
[
-f
"
$Le_RealKeyPath
"
]
;
then
cp
-p
"
$Le_RealKeyPath
"
"
$Le_RealKeyPath
"
.bak
fi
cat
"
$CERT_KEY_PATH
"
>
"
$Le_RealKeyPath
"
fi
if
[
"
$Le_ReloadCmd
"
]
;
then
_info
"Run Le_ReloadCmd:
$Le_ReloadCmd
"
(
cd
$DOMAIN_PATH
&&
eval
$Le_ReloadCmd
)
fi
}
installcronjob
()
{
_initpath
_info
"Installing cron job"
if
!
crontab
-l
|
grep
'le.sh cron'
;
then
if
[
-f
"
$LE_WORKING_DIR
/le.sh"
]
;
then
lesh
=
"
\"
$LE_WORKING_DIR
\"
/le.sh"
else
_err
"Can not install cronjob, le.sh not found."
return
1
fi
crontab
-l
|
{
cat
;
echo
"0 0 * * * LE_WORKING_DIR=
\"
$LE_WORKING_DIR
\"
$lesh
cron > /dev/null"
;
}
| crontab -
fi
return
0
}
uninstallcronjob
()
{
_info
"Removing cron job"
cr
=
"
$(
crontab
-l
|
grep
'le.sh cron'
)
"
if
[
"
$cr
"
]
;
then
crontab
-l
|
sed
"/le.sh cron/d"
| crontab -
LE_WORKING_DIR
=
"
$(
echo
"
$cr
"
|
cut
-d
' '
-f
6 |
cut
-d
'='
-f
2 |
tr
-d
'"'
)
"
_info LE_WORKING_DIR
"
$LE_WORKING_DIR
"
fi
_initpath
}
# Detect profile file if not specified as environment variable
_detect_profile
()
{
if
[
-n
"
$PROFILE
"
-a
-f
"
$PROFILE
"
]
;
then
echo
"
$PROFILE
"
return
fi
local
DETECTED_PROFILE
DETECTED_PROFILE
=
''
local
SHELLTYPE
SHELLTYPE
=
"
$(
basename
"/
$SHELL
"
)
"
if
[
"
$SHELLTYPE
"
=
"bash"
]
;
then
if
[
-f
"
$HOME
/.bashrc"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bashrc"
elif
[
-f
"
$HOME
/.bash_profile"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bash_profile"
fi
elif
[
"
$SHELLTYPE
"
=
"zsh"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.zshrc"
fi
if
[
-z
"
$DETECTED_PROFILE
"
]
;
then
if
[
-f
"
$HOME
/.profile"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.profile"
elif
[
-f
"
$HOME
/.bashrc"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bashrc"
elif
[
-f
"
$HOME
/.bash_profile"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bash_profile"
elif
[
-f
"
$HOME
/.zshrc"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.zshrc"
fi
fi
if
[
!
-z
"
$DETECTED_PROFILE
"
]
;
then
echo
"
$DETECTED_PROFILE
"
fi
}
_initconf
()
{
_initpath
if
[
!
-f
"
$ACCOUNT_CONF_PATH
"
]
;
then
echo
"#Account configurations:
#Here are the supported macros, uncomment them to make them take effect.
#ACCOUNT_EMAIL=aaa@aaa.com # the account email used to register account.
#STAGE=1 # Use the staging api
#FORCE=1 # Force to issue cert
#DEBUG=1 # Debug mode
#dns api
#######################
#Cloudflare:
#api key
#CF_Key="
sdfsdfsdfljlbjkljlkjsdfoiwje
"
#account email
#CF_Email="
xxxx@sss.com
"
#######################
#Dnspod.cn:
#api key id
#DP_Id="
1234
"
#api key
#DP_Key="
sADDsdasdgdsf
"
#######################
#Cloudxns.com:
#CX_Key="
1234
"
#
#CX_Secret="
sADDsdasdgdsf
"
"
>
$ACCOUNT_CONF_PATH
fi
}
install
()
{
_initpath
#check if there is sudo installed, AND if the current user is a sudoer.
if
command
-v
sudo
>
/dev/null
;
then
if
[
"
$(
sudo
-n
uptime
2>&1|grep
"load"
|wc
-l
)
"
!=
"0"
]
;
then
SUDO
=
sudo
fi
fi
if
command
-v
yum
>
/dev/null
;
then
YUM
=
"1"
INSTALL
=
"
$SUDO
yum install -y "
elif
command
-v
apt-get
>
/dev/null
;
then
INSTALL
=
"
$SUDO
apt-get install -y "
fi
if
!
command
-v
"curl"
>
/dev/null
;
then
_err
"Please install curl first."
_err
"
$INSTALL
curl"
return
1
fi
if
!
command
-v
"crontab"
>
/dev/null
;
then
_err
"Please install crontab first."
if
[
"
$YUM
"
]
;
then
_err
"
$INSTALL
crontabs"
else
_err
"
$INSTALL
crontab"
fi
return
1
fi
if
!
command
-v
"openssl"
>
/dev/null
;
then
_err
"Please install openssl first."
_err
"
$INSTALL
openssl"
return
1
fi
_info
"Installing to
$LE_WORKING_DIR
"
_info
"Installed to
$LE_WORKING_DIR
/le.sh"
cp
le.sh
$LE_WORKING_DIR
/
chmod
+x
$LE_WORKING_DIR
/le.sh
_profile
=
"
$(
_detect_profile
)
"
if
[
"
$_profile
"
]
;
then
_debug
"Found profile:
$_profile
"
echo
"LE_WORKING_DIR=
$LE_WORKING_DIR
alias le=
\"
$LE_WORKING_DIR
/le.sh
\"
alias le.sh=
\"
$LE_WORKING_DIR
/le.sh
\"
"
>
"
$LE_WORKING_DIR
/le.env"
_setopt
"
$_profile
"
"source
\"
$LE_WORKING_DIR
/le.env
\"
"
_info
"OK, Close and reopen your terminal to start using le"
else
_info
"No profile is found, you will need to go into
$LE_WORKING_DIR
to use le.sh"
fi
mkdir
-p
$LE_WORKING_DIR
/dnsapi
cp
dnsapi/
*
$LE_WORKING_DIR
/dnsapi/
#to keep compatible mv the .acc file to .key file
if
[
-f
"
$LE_WORKING_DIR
/account.acc"
]
;
then
mv
"
$LE_WORKING_DIR
/account.acc"
"
$LE_WORKING_DIR
/account.key"
fi
installcronjob
if
[
!
-f
"
$ACCOUNT_CONF_PATH
"
]
;
then
_initconf
fi
_info OK
}
uninstall
()
{
uninstallcronjob
_initpath
_profile
=
"
$(
_detect_profile
)
"
if
[
"
$_profile
"
]
;
then
sed
-i
/le.env/d
"
$_profile
"
fi
rm
-f
$LE_WORKING_DIR
/le.sh
_info
"The keys and certs are in
$LE_WORKING_DIR
, you can remove them by yourself."
}
cron
()
{
renewAll
}
version
()
{
_info
"
$PROJECT
"
_info
"v
$VER
"
}
showhelp
()
{
version
echo
"Usage: le.sh [command] ...[args]....
Avalible commands:
install:
Install le.sh to your system.
issue:
Issue a cert.
installcert:
Install the issued cert to apache/nginx or any other server.
renew:
Renew a cert.
renewAll:
Renew all the certs.
uninstall:
Uninstall le.sh, and uninstall the cron job.
version:
Show version info.
installcronjob:
Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
uninstallcronjob:
Uninstall the cron job. The 'uninstall' command can do this automatically.
createAccountKey:
Create an account private key, professional use.
createDomainKey:
Create an domain private key, professional use.
createCSR:
Create CSR , professional use.
"
}
if
[
-z
"
$1
"
]
;
then
showhelp
else
"
$@
"
fi
#!/usr/bin/env bash
VER
=
1.1.8
PROJECT
=
"https://github.com/Neilpang/le"
DEFAULT_CA
=
"https://acme-v01.api.letsencrypt.org"
DEFAULT_AGREEMENT
=
"https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
STAGE_CA
=
"https://acme-staging.api.letsencrypt.org"
VTYPE_HTTP
=
"http-01"
VTYPE_DNS
=
"dns-01"
if
[
-z
"
$AGREEMENT
"
]
;
then
AGREEMENT
=
"
$DEFAULT_AGREEMENT
"
fi
_debug
()
{
if
[
-z
"
$DEBUG
"
]
;
then
return
fi
if
[
-z
"
$2
"
]
;
then
echo
$1
else
echo
"
$1
"
=
"
$2
"
fi
}
_info
()
{
if
[
-z
"
$2
"
]
;
then
echo
"
$1
"
else
echo
"
$1
"
=
"
$2
"
fi
}
_err
()
{
if
[
-z
"
$2
"
]
;
then
echo
"
$1
"
>
&2
else
echo
"
$1
"
=
"
$2
"
>
&2
fi
return
1
}
_h2b
()
{
hex
=
$(
cat
)
i
=
1
j
=
2
while
[
'1'
]
;
do
h
=
$(
printf
$hex
|
cut
-c
$i
-
$j
)
if
[
-z
"
$h
"
]
;
then
break
;
fi
printf
"
\x
$h
"
let
"i+=2"
let
"j+=2"
done
}
_base64
()
{
openssl
base64
-e
|
tr
-d
'\n'
}
#domain [2048]
createAccountKey
()
{
_info
"Creating account key"
if
[
-z
"
$1
"
]
;
then
echo
Usage: createAccountKey account-domain
[
2048]
return
fi
account
=
$1
length
=
$2
if
[[
"
$length
"
==
"ec-"
*
]]
;
then
length
=
2048
fi
if
[
-z
"
$2
"
]
;
then
_info
"Use default length 2048"
length
=
2048
fi
_initpath
if
[
-f
"
$ACCOUNT_KEY_PATH
"
]
;
then
_info
"Account key exists, skip"
return
else
#generate account key
openssl genrsa
$length
>
"
$ACCOUNT_KEY_PATH
"
fi
}
#domain length
createDomainKey
()
{
_info
"Creating domain key"
if
[
-z
"
$1
"
]
;
then
echo
Usage: createDomainKey domain
[
2048]
return
fi
domain
=
$1
length
=
$2
isec
=
""
if
[[
"
$length
"
==
"ec-"
*
]]
;
then
isec
=
"1"
length
=
$(
printf
$length
|
cut
-d
'-'
-f
2-100
)
eccname
=
"
$length
"
fi
if
[
-z
"
$length
"
]
;
then
if
[
"
$isec
"
]
;
then
length
=
256
else
length
=
2048
fi
fi
_info
"Use length
$length
"
if
[
"
$isec
"
]
;
then
if
[
"
$length
"
==
"256"
]
;
then
eccname
=
"prime256v1"
fi
if
[
"
$length
"
==
"384"
]
;
then
eccname
=
"secp384r1"
fi
if
[
"
$length
"
==
"521"
]
;
then
eccname
=
"secp521r1"
fi
_info
"Using ec name:
$eccname
"
fi
_initpath
$domain
if
[
!
-f
"
$CERT_KEY_PATH
"
]
||
(
[
"
$FORCE
"
]
&&
!
[
"
$IS_RENEW
"
]
)
;
then
#generate account key
if
[
"
$isec
"
]
;
then
openssl ecparam
-name
$eccname
-genkey
2>/dev/null
>
"
$CERT_KEY_PATH
"
else
openssl genrsa
$length
2>/dev/null
>
"
$CERT_KEY_PATH
"
fi
else
if
[
"
$IS_RENEW
"
]
;
then
_info
"Domain key exists, skip"
return
0
else
_err
"Domain key exists, do you want to overwrite the key?"
_err
"Set FORCE=1, and try again."
return
1
fi
fi
}
# domain domainlist
createCSR
()
{
_info
"Creating csr"
if
[
-z
"
$1
"
]
;
then
echo
Usage:
$0
domain
[
domainlist]
return
fi
domain
=
$1
_initpath
$domain
domainlist
=
$2
if
[
-f
"
$CSR_PATH
"
]
&&
[
"
$IS_RENEW
"
]
&&
!
[
"
$FORCE
"
]
;
then
_info
"CSR exists, skip"
return
fi
if
[
-z
"
$domainlist
"
]
;
then
#single domain
_info
"Single domain"
$domain
openssl req
-new
-sha256
-key
"
$CERT_KEY_PATH
"
-subj
"/CN=
$domain
"
>
"
$CSR_PATH
"
else
alt
=
"DNS:
$(
echo
$domainlist
|
sed
"s/,/,DNS:/g"
)
"
#multi
_info
"Multi domain"
"
$alt
"
printf
"[ req_distinguished_name ]
\n
[ req ]
\n
distinguished_name = req_distinguished_name
\n
[SAN]
\n
subjectAltName=
$alt
"
>
"
$DOMAIN_SSL_CONF
"
openssl req
-new
-sha256
-key
"
$CERT_KEY_PATH
"
-subj
"/CN=
$domain
"
-reqexts
SAN
-config
"
$DOMAIN_SSL_CONF
"
-out
"
$CSR_PATH
"
fi
}
_b64
()
{
__n
=
$(
cat
)
echo
$__n
|
tr
'/+'
'_-'
|
tr
-d
'= '
}
_time2str
()
{
#BSD
if
date
-u
-d
@
$1
2>/dev/null
;
then
return
fi
#Linux
if
date
-u
-r
$1
2>/dev/null
;
then
return
fi
}
_send_signed_request
()
{
url
=
$1
payload
=
$2
needbase64
=
$3
_debug url
$url
_debug payload
"
$payload
"
CURL_HEADER
=
"
$LE_WORKING_DIR
/curl.header"
dp
=
"
$LE_WORKING_DIR
/curl.dump"
CURL
=
"curl --silent --dump-header
$CURL_HEADER
"
if
[
"
$DEBUG
"
]
;
then
CURL
=
"
$CURL
--trace-ascii
$dp
"
fi
payload64
=
$(
echo
-n
$payload
| _base64 | _b64
)
_debug payload64
$payload64
nonceurl
=
"
$API
/directory"
nonce
=
"
$(
$CURL
-I
$nonceurl
|
grep
-o
"^Replay-Nonce:.*$"
|
tr
-d
"
\r\n
"
|
cut
-d
' '
-f
2
)
"
_debug nonce
"
$nonce
"
protected
=
"
$(
printf
"
$HEADERPLACE
"
|
sed
"s/NONCE/
$nonce
/"
)
"
_debug protected
"
$protected
"
protected64
=
"
$(
printf
"
$protected
"
| _base64 | _b64
)
"
_debug protected64
"
$protected64
"
sig
=
$(
echo
-n
"
$protected64
.
$payload64
"
| openssl dgst
-sha256
-sign
$ACCOUNT_KEY_PATH
| _base64 | _b64
)
_debug sig
"
$sig
"
body
=
"{
\"
header
\"
:
$HEADER
,
\"
protected
\"
:
\"
$protected64
\"
,
\"
payload
\"
:
\"
$payload64
\"
,
\"
signature
\"
:
\"
$sig
\"
}"
_debug body
"
$body
"
if
[
"
$needbase64
"
]
;
then
response
=
"
$(
$CURL
-X
POST
--data
"
$body
"
$url
| _base64
)
"
else
response
=
"
$(
$CURL
-X
POST
--data
"
$body
"
$url
)
"
fi
responseHeaders
=
"
$(
cat
$CURL_HEADER
)
"
_debug responseHeaders
"
$responseHeaders
"
_debug response
"
$response
"
code
=
"
$(
grep
^HTTP
$CURL_HEADER
|
tail
-1
|
cut
-d
" "
-f
2 |
tr
-d
"
\r\n
"
)
"
_debug code
$code
}
_get
()
{
url
=
"
$1
"
_debug url
$url
response
=
"
$(
curl
--silent
$url
)
"
ret
=
$?
_debug response
"
$response
"
code
=
"
$(
echo
$response
|
grep
-o
'"status":[0-9]\+'
|
cut
-d
:
-f
2
)
"
_debug code
$code
return
$ret
}
#setopt "file" "opt" "=" "value" [";"]
_setopt
()
{
__conf
=
"
$1
"
__opt
=
"
$2
"
__sep
=
"
$3
"
__val
=
"
$4
"
__end
=
"
$5
"
if
[
-z
"
$__opt
"
]
;
then
echo
usage: _setopt
'"file" "opt" "=" "value" [";"]'
return
fi
if
[
!
-f
"
$__conf
"
]
;
then
touch
"
$__conf
"
fi
if
grep
-H
-n
"^
$__opt$__sep
"
"
$__conf
"
>
/dev/null
;
then
_debug OK
if
[[
"
$__val
"
==
*
"&"
*
]]
;
then
__val
=
"
$(
echo
$__val
|
sed
's/&/\\&/g'
)
"
fi
text
=
"
$(
cat
$__conf
)
"
printf
"
$text
"
|
sed
"s|^
$__opt$__sep
.*
$|$__opt$__sep$__val$__end
|"
>
"
$__conf
"
elif
grep
-H
-n
"^#
$__opt$__sep
"
"
$__conf
"
>
/dev/null
;
then
if
[[
"
$__val
"
==
*
"&"
*
]]
;
then
__val
=
"
$(
echo
$__val
|
sed
's/&/\\&/g'
)
"
fi
text
=
"
$(
cat
$__conf
)
"
printf
"
$text
"
|
sed
"s|^#
$__opt$__sep
.*
$|$__opt$__sep$__val$__end
|"
>
"
$__conf
"
else
_debug APP
echo
"
$__opt$__sep$__val$__end
"
>>
"
$__conf
"
fi
_debug
"
$(
grep
-H
-n
"^
$__opt$__sep
"
$__conf
)
"
}
#_savedomainconf key value
#save to domain.conf
_savedomainconf
()
{
key
=
"
$1
"
value
=
"
$2
"
if
[
"
$DOMAIN_CONF
"
]
;
then
_setopt
$DOMAIN_CONF
"
$key
"
"="
"
$value
"
else
_err
"DOMAIN_CONF is empty, can not save
$key
=
$value
"
fi
}
#_saveaccountconf key value
_saveaccountconf
()
{
key
=
"
$1
"
value
=
"
$2
"
if
[
"
$ACCOUNT_CONF_PATH
"
]
;
then
_setopt
$ACCOUNT_CONF_PATH
"
$key
"
"="
"
$value
"
else
_err
"ACCOUNT_CONF_PATH is empty, can not save
$key
=
$value
"
fi
}
_startserver
()
{
content
=
"
$1
"
_NC
=
"nc -q 1"
if
nc
-h
2>&1 |
grep
"nmap.org/ncat"
>
/dev/null
;
then
_NC
=
"nc"
fi
# while true ; do
if
[
"
$DEBUG
"
]
;
then
echo
-e
-n
"HTTP/1.1 200 OK
\r\n\r\n
$content
"
|
$_NC
-l
-p
$Le_HTTPPort
-vv
else
echo
-e
-n
"HTTP/1.1 200 OK
\r\n\r\n
$content
"
|
$_NC
-l
-p
$Le_HTTPPort
>
/dev/null
fi
# done
}
_stopserver
()
{
pid
=
"
$1
"
}
_initpath
()
{
if
[
-z
"
$LE_WORKING_DIR
"
]
;
then
LE_WORKING_DIR
=
$HOME
/.le
fi
if
[
-z
"
$ACCOUNT_CONF_PATH
"
]
;
then
ACCOUNT_CONF_PATH
=
"
$LE_WORKING_DIR
/account.conf"
fi
if
[
-f
"
$ACCOUNT_CONF_PATH
"
]
;
then
source
"
$ACCOUNT_CONF_PATH
"
fi
if
[
-z
"
$API
"
]
;
then
if
[
-z
"
$STAGE
"
]
;
then
API
=
"
$DEFAULT_CA
"
else
API
=
"
$STAGE_CA
"
_info
"Using stage api:
$API
"
fi
fi
if
[
-z
"
$ACME_DIR
"
]
;
then
ACME_DIR
=
"/home/.acme"
fi
if
[
-z
"
$APACHE_CONF_BACKUP_DIR
"
]
;
then
APACHE_CONF_BACKUP_DIR
=
"
$LE_WORKING_DIR
/"
fi
domain
=
"
$1
"
mkdir
-p
"
$LE_WORKING_DIR
"
if
[
-z
"
$ACCOUNT_KEY_PATH
"
]
;
then
ACCOUNT_KEY_PATH
=
"
$LE_WORKING_DIR
/account.key"
fi
if
[
-z
"
$domain
"
]
;
then
return
0
fi
domainhome
=
"
$LE_WORKING_DIR
/
$domain
"
mkdir
-p
"
$domainhome
"
if
[
-z
"
$DOMAIN_PATH
"
]
;
then
DOMAIN_PATH
=
"
$domainhome
"
fi
if
[
-z
"
$DOMAIN_CONF
"
]
;
then
DOMAIN_CONF
=
"
$domainhome
/
$Le_Domain
.conf"
fi
if
[
-z
"
$DOMAIN_SSL_CONF
"
]
;
then
DOMAIN_SSL_CONF
=
"
$domainhome
/
$Le_Domain
.ssl.conf"
fi
if
[
-z
"
$CSR_PATH
"
]
;
then
CSR_PATH
=
"
$domainhome
/
$domain
.csr"
fi
if
[
-z
"
$CERT_KEY_PATH
"
]
;
then
CERT_KEY_PATH
=
"
$domainhome
/
$domain
.key"
fi
if
[
-z
"
$CERT_PATH
"
]
;
then
CERT_PATH
=
"
$domainhome
/
$domain
.cer"
fi
if
[
-z
"
$CA_CERT_PATH
"
]
;
then
CA_CERT_PATH
=
"
$domainhome
/ca.cer"
fi
}
_apachePath
()
{
httpdroot
=
"
$(
apachectl
-V
|
grep
HTTPD_ROOT
=
|
cut
-d
=
-f
2 |
tr
-d
'"'
)
"
httpdconfname
=
"
$(
apachectl
-V
|
grep
SERVER_CONFIG_FILE
=
|
cut
-d
=
-f
2 |
tr
-d
'"'
)
"
httpdconf
=
"
$httpdroot
/
$httpdconfname
"
if
[
!
-f
$httpdconf
]
;
then
_err
"Apache Config file not found"
$httpdconf
return
1
fi
return
0
}
_restoreApache
()
{
if
[
-z
"
$usingApache
"
]
;
then
return
0
fi
_initpath
if
!
_apachePath
;
then
return
1
fi
if
[
!
-f
"
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
]
;
then
_debug
"No config file to restore."
return
0
fi
cp
-p
"
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
"
$httpdconf
"
if
!
apachectl
-t
;
then
_err
"Sorry, restore apache config error, please contact me."
return
1
;
fi
rm
-f
"
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
return
0
}
_setApache
()
{
_initpath
if
!
_apachePath
;
then
return
1
fi
#backup the conf
_debug
"Backup apache config file"
$httpdconf
cp
-p
$httpdconf
$APACHE_CONF_BACKUP_DIR
/
_info
"JFYI, Config file
$httpdconf
is backuped to
$APACHE_CONF_BACKUP_DIR
/
$httpdconfname
"
_info
"In case there is an error that can not be restored automatically, you may try restore it yourself."
_info
"The backup file will be deleted on sucess, just forget it."
#add alias
echo
"
Alias /.well-known/acme-challenge
$ACME_DIR
<Directory
$ACME_DIR
>
Require all granted
</Directory>
"
>>
$httpdconf
if
!
apachectl
-t
;
then
_err
"Sorry, apache config error, please contact me."
_restoreApache
return
1
;
fi
if
[
!
-d
"
$ACME_DIR
"
]
;
then
mkdir
-p
"
$ACME_DIR
"
chmod
755
"
$ACME_DIR
"
fi
if
!
apachectl graceful
;
then
_err
"Sorry, apachectl graceful error, please contact me."
_restoreApache
return
1
;
fi
usingApache
=
"1"
return
0
}
_clearup
()
{
_stopserver
$serverproc
serverproc
=
""
_restoreApache
}
# webroot removelevel tokenfile
_clearupwebbroot
()
{
__webroot
=
"
$1
"
if
[
-z
"
$__webroot
"
]
;
then
_debug
"no webroot specified, skip"
return
0
fi
if
[
"
$2
"
==
'1'
]
;
then
_debug
"remove
$__webroot
/.well-known"
rm
-rf
"
$__webroot
/.well-known"
elif
[
"
$2
"
==
'2'
]
;
then
_debug
"remove
$__webroot
/.well-known/acme-challenge"
rm
-rf
"
$__webroot
/.well-known/acme-challenge"
elif
[
"
$2
"
==
'3'
]
;
then
_debug
"remove
$__webroot
/.well-known/acme-challenge/
$3
"
rm
-rf
"
$__webroot
/.well-known/acme-challenge/
$3
"
else
_info
"Skip for removelevel:
$2
"
fi
return
0
}
issue
()
{
if
[
-z
"
$2
"
]
;
then
_err
"Usage: le issue webroot|no|apache|dns a.com [www.a.com,b.com,c.com]|no [key-length]|no"
return
1
fi
Le_Webroot
=
"
$1
"
Le_Domain
=
"
$2
"
Le_Alt
=
"
$3
"
Le_Keylength
=
"
$4
"
Le_RealCertPath
=
"
$5
"
Le_RealKeyPath
=
"
$6
"
Le_RealCACertPath
=
"
$7
"
Le_ReloadCmd
=
"
$8
"
_initpath
$Le_Domain
if
[
-f
"
$DOMAIN_CONF
"
]
;
then
Le_NextRenewTime
=
$(
grep
"^Le_NextRenewTime="
"
$DOMAIN_CONF
"
|
cut
-d
'='
-f
2
)
if
[
-z
"
$FORCE
"
]
&&
[
"
$Le_NextRenewTime
"
]
&&
[
"
$(
date
-u
"+%s"
)
"
-lt
"
$Le_NextRenewTime
"
]
;
then
_info
"Skip, Next renewal time is:
$(
grep
"^Le_NextRenewTimeStr"
"
$DOMAIN_CONF
"
|
cut
-d
'='
-f
2
)
"
return
2
fi
fi
if
[
"
$Le_Alt
"
==
"no"
]
;
then
Le_Alt
=
""
fi
if
[
"
$Le_Keylength
"
==
"no"
]
;
then
Le_Keylength
=
""
fi
if
[
"
$Le_RealCertPath
"
==
"no"
]
;
then
Le_RealCertPath
=
""
fi
if
[
"
$Le_RealKeyPath
"
==
"no"
]
;
then
Le_RealKeyPath
=
""
fi
if
[
"
$Le_RealCACertPath
"
==
"no"
]
;
then
Le_RealCACertPath
=
""
fi
if
[
"
$Le_ReloadCmd
"
==
"no"
]
;
then
Le_ReloadCmd
=
""
fi
_setopt
"
$DOMAIN_CONF
"
"Le_Domain"
"="
"
$Le_Domain
"
_setopt
"
$DOMAIN_CONF
"
"Le_Alt"
"="
"
$Le_Alt
"
_setopt
"
$DOMAIN_CONF
"
"Le_Webroot"
"="
"
$Le_Webroot
"
_setopt
"
$DOMAIN_CONF
"
"Le_Keylength"
"="
"
$Le_Keylength
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealCertPath"
"="
"
\"
$Le_RealCertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealCACertPath"
"="
"
\"
$Le_RealCACertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealKeyPath"
"="
"
\"
$Le_RealKeyPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_ReloadCmd"
"="
"
\"
$Le_ReloadCmd
\"
"
if
[
"
$Le_Webroot
"
==
"no"
]
;
then
_info
"Standalone mode."
if
!
command
-v
"nc"
>
/dev/null
;
then
_err
"Please install netcat(nc) tools first."
return
1
fi
if
[
-z
"
$Le_HTTPPort
"
]
;
then
Le_HTTPPort
=
80
fi
_setopt
"
$DOMAIN_CONF
"
"Le_HTTPPort"
"="
"
$Le_HTTPPort
"
netprc
=
"
$(
ss
-ntpl
|
grep
:
$Le_HTTPPort
" "
)
"
if
[
"
$netprc
"
]
;
then
_err
"
$netprc
"
_err
"tcp port
$Le_HTTPPort
is already used by
$(
echo
"
$netprc
"
|
cut
-d
:
-f
4
)
"
_err
"Please stop it first"
return
1
fi
fi
if
[
"
$Le_Webroot
"
==
"apache"
]
;
then
if
!
_setApache
;
then
_err
"set up apache error. Report error to me."
return
1
fi
wellknown_path
=
"
$ACME_DIR
"
else
usingApache
=
""
fi
createAccountKey
$Le_Domain
$Le_Keylength
if
!
createDomainKey
$Le_Domain
$Le_Keylength
;
then
_err
"Create domain key error."
return
1
fi
if
!
createCSR
$Le_Domain
$Le_Alt
;
then
_err
"Create CSR error."
return
1
fi
pub_exp
=
$(
openssl rsa
-in
$ACCOUNT_KEY_PATH
-noout
-text
|
grep
"^publicExponent:"
|
cut
-d
'('
-f
2 |
cut
-d
'x'
-f
2 |
cut
-d
')'
-f
1
)
if
[
"
${#
pub_exp
}
"
==
"5"
]
;
then
pub_exp
=
0
$pub_exp
fi
_debug pub_exp
"
$pub_exp
"
e
=
$(
echo
$pub_exp
| _h2b | _base64
)
_debug e
"
$e
"
modulus
=
$(
openssl rsa
-in
$ACCOUNT_KEY_PATH
-modulus
-noout
|
cut
-d
'='
-f
2
)
n
=
$(
echo
$modulus
| _h2b | _base64 | _b64
)
jwk
=
'{"e": "'
$e
'", "kty": "RSA", "n": "'
$n
'"}'
HEADER
=
'{"alg": "RS256", "jwk": '
$jwk
'}'
HEADERPLACE
=
'{"nonce": "NONCE", "alg": "RS256", "jwk": '
$jwk
'}'
_debug HEADER
"
$HEADER
"
accountkey_json
=
$(
echo
-n
"
$jwk
"
|
tr
-d
' '
)
thumbprint
=
$(
echo
-n
"
$accountkey_json
"
| openssl dgst
-sha256
-binary
| _base64 | _b64
)
_info
"Registering account"
regjson
=
'{"resource": "new-reg", "agreement": "'
$AGREEMENT
'"}'
if
[
"
$ACCOUNT_EMAIL
"
]
;
then
regjson
=
'{"resource": "new-reg", "contact": ["mailto: '
$ACCOUNT_EMAIL
'"], "agreement": "'
$AGREEMENT
'"}'
fi
_send_signed_request
"
$API
/acme/new-reg"
"
$regjson
"
if
[
"
$code
"
==
""
]
||
[
"
$code
"
==
'201'
]
;
then
_info
"Registered"
echo
$response
>
$LE_WORKING_DIR
/account.json
elif
[
"
$code
"
==
'409'
]
;
then
_info
"Already registered"
else
_err
"Register account Error."
_clearup
return
1
fi
vtype
=
"
$VTYPE_HTTP
"
if
[[
"
$Le_Webroot
"
==
"dns"
*
]]
;
then
vtype
=
"
$VTYPE_DNS
"
fi
vlist
=
"
$Le_Vlist
"
# verify each domain
_info
"Verify each domain"
sep
=
'#'
if
[
-z
"
$vlist
"
]
;
then
alldomains
=
$(
echo
"
$Le_Domain
,
$Le_Alt
"
|
tr
','
' '
)
for
d
in
$alldomains
do
_info
"Getting token for domain"
$d
_send_signed_request
"
$API
/acme/new-authz"
"{
\"
resource
\"
:
\"
new-authz
\"
,
\"
identifier
\"
: {
\"
type
\"
:
\"
dns
\"
,
\"
value
\"
:
\"
$d
\"
}}"
if
[
!
-z
"
$code
"
]
&&
[
!
"
$code
"
==
'201'
]
;
then
_err
"new-authz error:
$response
"
_clearup
return
1
fi
entry
=
"
$(
printf
$response
| egrep
-o
'{[^{]*"type":"'
$vtype
'"[^}]*'
)
"
_debug entry
"
$entry
"
token
=
"
$(
printf
"
$entry
"
| egrep
-o
'"token":"[^"]*'
|
cut
-d
:
-f
2 |
tr
-d
'"'
)
"
_debug token
$token
uri
=
"
$(
printf
"
$entry
"
| egrep
-o
'"uri":"[^"]*'
|
cut
-d
:
-f
2,3 |
tr
-d
'"'
)
"
_debug uri
$uri
keyauthorization
=
"
$token
.
$thumbprint
"
_debug keyauthorization
"
$keyauthorization
"
dvlist
=
"
$d$sep$keyauthorization$sep$uri
"
_debug dvlist
"
$dvlist
"
vlist
=
"
$vlist$dvlist
,"
done
#add entry
dnsadded
=
""
ventries
=
$(
echo
"
$vlist
"
|
tr
','
' '
)
for
ventry
in
$ventries
do
d
=
$(
echo
$ventry
|
cut
-d
$sep
-f
1
)
keyauthorization
=
$(
echo
$ventry
|
cut
-d
$sep
-f
2
)
if
[
"
$vtype
"
==
"
$VTYPE_DNS
"
]
;
then
dnsadded
=
'0'
txtdomain
=
"_acme-challenge.
$d
"
_debug txtdomain
"
$txtdomain
"
txt
=
"
$(
echo
-e
-n
$keyauthorization
| openssl dgst
-sha256
-binary
| _base64 | _b64
)
"
_debug txt
"
$txt
"
#dns
#1. check use api
d_api
=
""
if
[
-f
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
"
elif
[
-f
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
.sh"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$d
/
$Le_Webroot
.sh"
elif
[
-f
"
$LE_WORKING_DIR
/
$Le_Webroot
"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$Le_Webroot
"
elif
[
-f
"
$LE_WORKING_DIR
/
$Le_Webroot
.sh"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/
$Le_Webroot
.sh"
elif
[
-f
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
"
elif
[
-f
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
.sh"
]
;
then
d_api
=
"
$LE_WORKING_DIR
/dnsapi/
$Le_Webroot
.sh"
fi
_debug d_api
"
$d_api
"
if
[
"
$d_api
"
]
;
then
_info
"Found domain api file:
$d_api
"
else
_err
"Add the following TXT record:"
_err
"Domain:
$txtdomain
"
_err
"TXT value:
$txt
"
_err
"Please be aware that you prepend _acme-challenge. before your domain"
_err
"so the resulting subdomain will be:
$txtdomain
"
continue
fi
if
!
source
$d_api
;
then
_err
"Load file
$d_api
error. Please check your api file and try again."
return
1
fi
addcommand
=
"
$Le_Webroot
-add"
if
!
command
-v
$addcommand
;
then
_err
"It seems that your api file is not correct, it must have a function named:
$Le_Webroot
"
return
1
fi
if
!
$addcommand
$txtdomain
$txt
;
then
_err
"Error add txt for domain:
$txtdomain
"
return
1
fi
dnsadded
=
'1'
fi
done
if
[
"
$dnsadded
"
==
'0'
]
;
then
_setopt
"
$DOMAIN_CONF
"
"Le_Vlist"
"="
"
\"
$vlist
\"
"
_debug
"Dns record not added yet, so, save to
$DOMAIN_CONF
and exit."
_err
"Please add the TXT records to the domains, and retry again."
return
1
fi
fi
if
[
"
$dnsadded
"
==
'1'
]
;
then
_info
"Sleep 60 seconds for the txt records to take effect"
sleep
60
fi
_debug
"ok, let's start to verify"
ventries
=
$(
echo
"
$vlist
"
|
tr
','
' '
)
for
ventry
in
$ventries
do
d
=
$(
echo
$ventry
|
cut
-d
$sep
-f
1
)
keyauthorization
=
$(
echo
$ventry
|
cut
-d
$sep
-f
2
)
uri
=
$(
echo
$ventry
|
cut
-d
$sep
-f
3
)
_info
"Verifying:
$d
"
_debug
"d"
"
$d
"
_debug
"keyauthorization"
"
$keyauthorization
"
_debug
"uri"
"
$uri
"
removelevel
=
""
token
=
""
if
[
"
$vtype
"
==
"
$VTYPE_HTTP
"
]
;
then
if
[
"
$Le_Webroot
"
==
"no"
]
;
then
_info
"Standalone mode server"
_startserver
"
$keyauthorization
"
&
serverproc
=
"
$!
"
sleep
2
_debug serverproc
$serverproc
else
if
[
-z
"
$wellknown_path
"
]
;
then
wellknown_path
=
"
$Le_Webroot
/.well-known/acme-challenge"
fi
_debug wellknown_path
"
$wellknown_path
"
if
[
!
-d
"
$Le_Webroot
/.well-known"
]
;
then
removelevel
=
'1'
elif
[
!
-d
"
$Le_Webroot
/.well-known/acme-challenge"
]
;
then
removelevel
=
'2'
else
removelevel
=
'3'
fi
token
=
"
$(
echo
-e
-n
"
$keyauthorization
"
|
cut
-d
'.'
-f
1
)
"
_debug
"writing token:
$token
to
$wellknown_path
/
$token
"
mkdir
-p
"
$wellknown_path
"
echo
-n
"
$keyauthorization
"
>
"
$wellknown_path
/
$token
"
webroot_owner
=
$(
stat
-c
'%U:%G'
$Le_Webroot
)
_debug
"Changing owner/group of .well-known to
$webroot_owner
"
chown
-R
$webroot_owner
"
$Le_Webroot
/.well-known"
fi
fi
_send_signed_request
$uri
"{
\"
resource
\"
:
\"
challenge
\"
,
\"
keyAuthorization
\"
:
\"
$keyauthorization
\"
}"
if
[
!
-z
"
$code
"
]
&&
[
!
"
$code
"
==
'202'
]
;
then
_err
"
$d
:Challenge error:
$resource
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
fi
while
[
"1"
]
;
do
_debug
"sleep 5 secs to verify"
sleep
5
_debug
"checking"
if
!
_get
$uri
;
then
_err
"
$d
:Verify error:
$resource
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
fi
status
=
$(
echo
$response
| egrep
-o
'"status":"[^"]+"'
|
cut
-d
:
-f
2 |
tr
-d
'"'
)
if
[
"
$status
"
==
"valid"
]
;
then
_info
"Success"
_stopserver
$serverproc
serverproc
=
""
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
break
;
fi
if
[
"
$status
"
==
"invalid"
]
;
then
error
=
$(
echo
$response
| egrep
-o
'"error":{[^}]*}'
|
grep
-o
'"detail":"[^"]*"'
|
cut
-d
'"'
-f
4
)
_err
"
$d
:Verify error:
$error
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
;
fi
if
[
"
$status
"
==
"pending"
]
;
then
_info
"Pending"
else
_err
"
$d
:Verify error:
$response
"
_clearupwebbroot
"
$Le_Webroot
"
"
$removelevel
"
"
$token
"
_clearup
return
1
fi
done
done
_clearup
_info
"Verify finished, start to sign."
der
=
"
$(
openssl req
-in
$CSR_PATH
-outform
DER | _base64 | _b64
)
"
_send_signed_request
"
$API
/acme/new-cert"
"{
\"
resource
\"
:
\"
new-cert
\"
,
\"
csr
\"
:
\"
$der
\"
}"
"needbase64"
Le_LinkCert
=
"
$(
grep
-i
-o
'^Location.*$'
$CURL_HEADER
|
tr
-d
"
\r\n
"
|
cut
-d
" "
-f
2
)
"
_setopt
"
$DOMAIN_CONF
"
"Le_LinkCert"
"="
"
$Le_LinkCert
"
if
[
"
$Le_LinkCert
"
]
;
then
echo
-----BEGIN
CERTIFICATE-----
>
"
$CERT_PATH
"
curl
--silent
"
$Le_LinkCert
"
| openssl
base64
-e
>>
"
$CERT_PATH
"
echo
-----END
CERTIFICATE-----
>>
"
$CERT_PATH
"
_info
"Cert success."
cat
"
$CERT_PATH
"
_info
"Your cert is in
$CERT_PATH
"
fi
if
[
-z
"
$Le_LinkCert
"
]
;
then
response
=
"
$(
echo
$response
| openssl
base64
-d
-A
)
"
_err
"Sign failed:
$(
echo
"
$response
"
|
grep
-o
'"detail":"[^"]*"'
)
"
return
1
fi
_setopt
"
$DOMAIN_CONF
"
'Le_Vlist'
'='
"
\"\"
"
Le_LinkIssuer
=
$(
grep
-i
'^Link'
$CURL_HEADER
|
cut
-d
" "
-f
2|
cut
-d
';'
-f
1 |
tr
-d
'<>'
)
_setopt
"
$DOMAIN_CONF
"
"Le_LinkIssuer"
"="
"
$Le_LinkIssuer
"
if
[
"
$Le_LinkIssuer
"
]
;
then
echo
-----BEGIN
CERTIFICATE-----
>
"
$CA_CERT_PATH
"
curl
--silent
"
$Le_LinkIssuer
"
| openssl
base64
-e
>>
"
$CA_CERT_PATH
"
echo
-----END
CERTIFICATE-----
>>
"
$CA_CERT_PATH
"
_info
"The intermediate CA cert is in
$CA_CERT_PATH
"
fi
Le_CertCreateTime
=
$(
date
-u
"+%s"
)
_setopt
"
$DOMAIN_CONF
"
"Le_CertCreateTime"
"="
"
$Le_CertCreateTime
"
Le_CertCreateTimeStr
=
$(
date
-u
)
_setopt
"
$DOMAIN_CONF
"
"Le_CertCreateTimeStr"
"="
"
\"
$Le_CertCreateTimeStr
\"
"
if
[
!
"
$Le_RenewalDays
"
]
;
then
Le_RenewalDays
=
80
fi
_setopt
"
$DOMAIN_CONF
"
"Le_RenewalDays"
"="
"
$Le_RenewalDays
"
let
"Le_NextRenewTime=Le_CertCreateTime+Le_RenewalDays*24*60*60"
_setopt
"
$DOMAIN_CONF
"
"Le_NextRenewTime"
"="
"
$Le_NextRenewTime
"
Le_NextRenewTimeStr
=
$(
_time2str
$Le_NextRenewTime
)
_setopt
"
$DOMAIN_CONF
"
"Le_NextRenewTimeStr"
"="
"
\"
$Le_NextRenewTimeStr
\"
"
installcert
$Le_Domain
"
$Le_RealCertPath
"
"
$Le_RealKeyPath
"
"
$Le_RealCACertPath
"
"
$Le_ReloadCmd
"
}
renew
()
{
Le_Domain
=
"
$1
"
if
[
-z
"
$Le_Domain
"
]
;
then
_err
"Usage:
$0
domain.com"
return
1
fi
_initpath
$Le_Domain
if
[
!
-f
"
$DOMAIN_CONF
"
]
;
then
_info
"
$Le_Domain
is not a issued domain, skip."
return
0
;
fi
source
"
$DOMAIN_CONF
"
if
[
-z
"
$FORCE
"
]
&&
[
"
$Le_NextRenewTime
"
]
&&
[
"
$(
date
-u
"+%s"
)
"
-lt
"
$Le_NextRenewTime
"
]
;
then
_info
"Skip, Next renewal time is:
$Le_NextRenewTimeStr
"
return
2
fi
IS_RENEW
=
"1"
issue
"
$Le_Webroot
"
"
$Le_Domain
"
"
$Le_Alt
"
"
$Le_Keylength
"
"
$Le_RealCertPath
"
"
$Le_RealKeyPath
"
"
$Le_RealCACertPath
"
"
$Le_ReloadCmd
"
local
res
=
$?
IS_RENEW
=
""
return
$res
}
renewAll
()
{
_initpath
_info
"renewAll"
for
d
in
$(
ls
-F
$LE_WORKING_DIR
|
grep
[
^.].
*
[
.].
*
/
$
)
;
do
d
=
$(
echo
$d
|
cut
-d
'/'
-f
1
)
_info
"renew
$d
"
Le_LinkCert
=
""
Le_Domain
=
""
Le_Alt
=
""
Le_Webroot
=
""
Le_Keylength
=
""
Le_LinkIssuer
=
""
Le_CertCreateTime
=
""
Le_CertCreateTimeStr
=
""
Le_RenewalDays
=
""
Le_NextRenewTime
=
""
Le_NextRenewTimeStr
=
""
Le_RealCertPath
=
""
Le_RealKeyPath
=
""
Le_RealCACertPath
=
""
Le_ReloadCmd
=
""
DOMAIN_PATH
=
""
DOMAIN_CONF
=
""
DOMAIN_SSL_CONF
=
""
CSR_PATH
=
""
CERT_KEY_PATH
=
""
CERT_PATH
=
""
CA_CERT_PATH
=
""
ACCOUNT_KEY_PATH
=
""
wellknown_path
=
""
renew
"
$d
"
done
}
installcert
()
{
Le_Domain
=
"
$1
"
if
[
-z
"
$Le_Domain
"
]
;
then
_err
"Usage:
$0
domain.com [cert-file-path]|no [key-file-path]|no [ca-cert-file-path]|no [reloadCmd]|no"
return
1
fi
Le_RealCertPath
=
"
$2
"
Le_RealKeyPath
=
"
$3
"
Le_RealCACertPath
=
"
$4
"
Le_ReloadCmd
=
"
$5
"
_initpath
$Le_Domain
_setopt
"
$DOMAIN_CONF
"
"Le_RealCertPath"
"="
"
\"
$Le_RealCertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealCACertPath"
"="
"
\"
$Le_RealCACertPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_RealKeyPath"
"="
"
\"
$Le_RealKeyPath
\"
"
_setopt
"
$DOMAIN_CONF
"
"Le_ReloadCmd"
"="
"
\"
$Le_ReloadCmd
\"
"
if
[
"
$Le_RealCertPath
"
]
;
then
if
[
-f
"
$Le_RealCertPath
"
]
;
then
cp
-p
"
$Le_RealCertPath
"
"
$Le_RealCertPath
"
.bak
fi
cat
"
$CERT_PATH
"
>
"
$Le_RealCertPath
"
fi
if
[
"
$Le_RealCACertPath
"
]
;
then
if
[
-f
"
$Le_RealCACertPath
"
]
;
then
cp
-p
"
$Le_RealCACertPath
"
"
$Le_RealCACertPath
"
.bak
fi
if
[
"
$Le_RealCACertPath
"
==
"
$Le_RealCertPath
"
]
;
then
echo
""
>>
"
$Le_RealCACertPath
"
cat
"
$CA_CERT_PATH
"
>>
"
$Le_RealCACertPath
"
else
cat
"
$CA_CERT_PATH
"
>
"
$Le_RealCACertPath
"
fi
fi
if
[
"
$Le_RealKeyPath
"
]
;
then
if
[
-f
"
$Le_RealKeyPath
"
]
;
then
cp
-p
"
$Le_RealKeyPath
"
"
$Le_RealKeyPath
"
.bak
fi
cat
"
$CERT_KEY_PATH
"
>
"
$Le_RealKeyPath
"
fi
if
[
"
$Le_ReloadCmd
"
]
;
then
_info
"Run Le_ReloadCmd:
$Le_ReloadCmd
"
(
cd
"
$DOMAIN_PATH
"
&&
eval
"
$Le_ReloadCmd
"
)
fi
}
installcronjob
()
{
_initpath
_info
"Installing cron job"
if
!
crontab
-l
|
grep
'le.sh cron'
;
then
if
[
-f
"
$LE_WORKING_DIR
/le.sh"
]
;
then
lesh
=
"
\"
$LE_WORKING_DIR
\"
/le.sh"
else
_err
"Can not install cronjob, le.sh not found."
return
1
fi
crontab
-l
|
{
cat
;
echo
"0 0 * * * LE_WORKING_DIR=
\"
$LE_WORKING_DIR
\"
$lesh
cron > /dev/null"
;
}
| crontab -
fi
return
0
}
uninstallcronjob
()
{
_info
"Removing cron job"
cr
=
"
$(
crontab
-l
|
grep
'le.sh cron'
)
"
if
[
"
$cr
"
]
;
then
crontab
-l
|
sed
"/le.sh cron/d"
| crontab -
LE_WORKING_DIR
=
"
$(
echo
"
$cr
"
|
cut
-d
' '
-f
6 |
cut
-d
'='
-f
2 |
tr
-d
'"'
)
"
_info LE_WORKING_DIR
"
$LE_WORKING_DIR
"
fi
_initpath
}
# Detect profile file if not specified as environment variable
_detect_profile
()
{
if
[
-n
"
$PROFILE
"
-a
-f
"
$PROFILE
"
]
;
then
echo
"
$PROFILE
"
return
fi
local
DETECTED_PROFILE
DETECTED_PROFILE
=
''
local
SHELLTYPE
SHELLTYPE
=
"
$(
basename
"/
$SHELL
"
)
"
if
[
"
$SHELLTYPE
"
=
"bash"
]
;
then
if
[
-f
"
$HOME
/.bashrc"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bashrc"
elif
[
-f
"
$HOME
/.bash_profile"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bash_profile"
fi
elif
[
"
$SHELLTYPE
"
=
"zsh"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.zshrc"
fi
if
[
-z
"
$DETECTED_PROFILE
"
]
;
then
if
[
-f
"
$HOME
/.profile"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.profile"
elif
[
-f
"
$HOME
/.bashrc"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bashrc"
elif
[
-f
"
$HOME
/.bash_profile"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.bash_profile"
elif
[
-f
"
$HOME
/.zshrc"
]
;
then
DETECTED_PROFILE
=
"
$HOME
/.zshrc"
fi
fi
if
[
!
-z
"
$DETECTED_PROFILE
"
]
;
then
echo
"
$DETECTED_PROFILE
"
fi
}
_initconf
()
{
_initpath
if
[
!
-f
"
$ACCOUNT_CONF_PATH
"
]
;
then
echo
"#Account configurations:
#Here are the supported macros, uncomment them to make them take effect.
#ACCOUNT_EMAIL=aaa@aaa.com # the account email used to register account.
#STAGE=1 # Use the staging api
#FORCE=1 # Force to issue cert
#DEBUG=1 # Debug mode
#dns api
#######################
#Cloudflare:
#api key
#CF_Key="
sdfsdfsdfljlbjkljlkjsdfoiwje
"
#account email
#CF_Email="
xxxx@sss.com
"
#######################
#Dnspod.cn:
#api key id
#DP_Id="
1234
"
#api key
#DP_Key="
sADDsdasdgdsf
"
#######################
#Cloudxns.com:
#CX_Key="
1234
"
#
#CX_Secret="
sADDsdasdgdsf
"
"
>
$ACCOUNT_CONF_PATH
fi
}
install
()
{
_initpath
#check if there is sudo installed, AND if the current user is a sudoer.
if
command
-v
sudo
>
/dev/null
;
then
if
[
"
$(
sudo
-n
uptime
2>&1|grep
"load"
|wc
-l
)
"
!=
"0"
]
;
then
SUDO
=
sudo
fi
fi
if
command
-v
yum
>
/dev/null
;
then
YUM
=
"1"
INSTALL
=
"
$SUDO
yum install -y "
elif
command
-v
apt-get
>
/dev/null
;
then
INSTALL
=
"
$SUDO
apt-get install -y "
fi
if
!
command
-v
"curl"
>
/dev/null
;
then
_err
"Please install curl first."
_err
"
$INSTALL
curl"
return
1
fi
if
!
command
-v
"crontab"
>
/dev/null
;
then
_err
"Please install crontab first."
if
[
"
$YUM
"
]
;
then
_err
"
$INSTALL
crontabs"
else
_err
"
$INSTALL
crontab"
fi
return
1
fi
if
!
command
-v
"openssl"
>
/dev/null
;
then
_err
"Please install openssl first."
_err
"
$INSTALL
openssl"
return
1
fi
_info
"Installing to
$LE_WORKING_DIR
"
_info
"Installed to
$LE_WORKING_DIR
/le.sh"
cp
le.sh
$LE_WORKING_DIR
/
chmod
+x
$LE_WORKING_DIR
/le.sh
_profile
=
"
$(
_detect_profile
)
"
if
[
"
$_profile
"
]
;
then
_debug
"Found profile:
$_profile
"
echo
"LE_WORKING_DIR=
$LE_WORKING_DIR
alias le=
\"
$LE_WORKING_DIR
/le.sh
\"
alias le.sh=
\"
$LE_WORKING_DIR
/le.sh
\"
"
>
"
$LE_WORKING_DIR
/le.env"
_setopt
"
$_profile
"
"source
\"
$LE_WORKING_DIR
/le.env
\"
"
_info
"OK, Close and reopen your terminal to start using le"
else
_info
"No profile is found, you will need to go into
$LE_WORKING_DIR
to use le.sh"
fi
mkdir
-p
$LE_WORKING_DIR
/dnsapi
cp
dnsapi/
*
$LE_WORKING_DIR
/dnsapi/
#to keep compatible mv the .acc file to .key file
if
[
-f
"
$LE_WORKING_DIR
/account.acc"
]
;
then
mv
"
$LE_WORKING_DIR
/account.acc"
"
$LE_WORKING_DIR
/account.key"
fi
installcronjob
if
[
!
-f
"
$ACCOUNT_CONF_PATH
"
]
;
then
_initconf
fi
_info OK
}
uninstall
()
{
uninstallcronjob
_initpath
_profile
=
"
$(
_detect_profile
)
"
if
[
"
$_profile
"
]
;
then
sed
-i
/le.env/d
"
$_profile
"
fi
rm
-f
$LE_WORKING_DIR
/le.sh
_info
"The keys and certs are in
$LE_WORKING_DIR
, you can remove them by yourself."
}
cron
()
{
renewAll
}
version
()
{
_info
"
$PROJECT
"
_info
"v
$VER
"
}
showhelp
()
{
version
echo
"Usage: le.sh [command] ...[args]....
Avalible commands:
install:
Install le.sh to your system.
issue:
Issue a cert.
installcert:
Install the issued cert to apache/nginx or any other server.
renew:
Renew a cert.
renewAll:
Renew all the certs.
uninstall:
Uninstall le.sh, and uninstall the cron job.
version:
Show version info.
installcronjob:
Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
uninstallcronjob:
Uninstall the cron job. The 'uninstall' command can do this automatically.
createAccountKey:
Create an account private key, professional use.
createDomainKey:
Create an domain private key, professional use.
createCSR:
Create CSR , professional use.
"
}
if
[
-z
"
$1
"
]
;
then
showhelp
else
"
$@
"
fi
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment