]> git.proxmox.com Git - mirror_acme.sh.git/blame - acme.sh
add `--certhome` for `--install` command to save all the certs.
[mirror_acme.sh.git] / acme.sh
CommitLineData
4c3b3608 1#!/usr/bin/env bash
635695ec 2VER=2.1.1
a7b7355d 3
6cc11ffb 4PROJECT_NAME="acme.sh"
a7b7355d 5
6cc11ffb 6PROJECT_ENTRY="acme.sh"
7
8PROJECT="https://github.com/Neilpang/$PROJECT_NAME"
4c3b3608 9
10DEFAULT_CA="https://acme-v01.api.letsencrypt.org"
11DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
12
a7b7355d 13DEFAULT_USER_AGENT="$PROJECT_ENTRY client: $PROJECT"
bbbdcb09 14
4c3b3608 15STAGE_CA="https://acme-staging.api.letsencrypt.org"
16
17VTYPE_HTTP="http-01"
18VTYPE_DNS="dns-01"
19
88fab7d6 20BEGIN_CSR="-----BEGIN CERTIFICATE REQUEST-----"
21END_CSR="-----END CERTIFICATE REQUEST-----"
22
23BEGIN_CERT="-----BEGIN CERTIFICATE-----"
24END_CERT="-----END CERTIFICATE-----"
25
a63b05a9 26if [[ -z "$AGREEMENT" ]] ; then
4c3b3608 27 AGREEMENT="$DEFAULT_AGREEMENT"
28fi
29
4c3b3608 30
31_info() {
a63b05a9 32 if [[ -z "$2" ]] ; then
33 echo "[$(date)] $1"
4c3b3608 34 else
a63b05a9 35 echo "[$(date)] $1"="'$2'"
4c3b3608 36 fi
37}
38
39_err() {
a63b05a9 40 _info "$@" >&2
4c3b3608 41 return 1
42}
43
c60883ef 44_debug() {
a63b05a9 45 if [[ -z "$DEBUG" ]] ; then
c60883ef 46 return
47 fi
a63b05a9 48 _err "$@"
c60883ef 49 return 0
50}
51
a63b05a9 52_debug2() {
53 if [[ "$DEBUG" -ge "2" ]] ; then
54 _debug "$@"
55 fi
56 return
57}
58
c60883ef 59_exists() {
60 cmd="$1"
a63b05a9 61 if [[ -z "$cmd" ]] ; then
c60883ef 62 _err "Usage: _exists cmd"
63 return 1
64 fi
65 command -v $cmd >/dev/null 2>&1
66 ret="$?"
a63b05a9 67 _debug2 "$cmd exists=$ret"
c60883ef 68 return $ret
69}
70
4c3b3608 71_h2b() {
72 hex=$(cat)
73 i=1
74 j=2
75 while [ '1' ] ; do
76 h=$(printf $hex | cut -c $i-$j)
a63b05a9 77 if [[ -z "$h" ]] ; then
4c3b3608 78 break;
79 fi
80 printf "\x$h"
81 let "i+=2"
82 let "j+=2"
83 done
84}
85
c60883ef 86#options file
87_sed_i() {
88 options="$1"
89 filename="$2"
a63b05a9 90 if [[ -z "$filename" ]] ; then
c60883ef 91 _err "Usage:_sed_i options filename"
92 return 1
93 fi
94
95 if sed -h 2>&1 | grep "\-i[SUFFIX]" ; then
96 _debug "Using sed -i"
97 sed -i ""
98 else
99 _debug "No -i support in sed"
100 text="$(cat $filename)"
101 echo "$text" | sed "$options" > "$filename"
102 fi
103}
104
88fab7d6 105#Usage: file startline endline
106_getfile() {
107 filename="$1"
108 startline="$2"
109 endline="$3"
a63b05a9 110 if [[ -z "$endline" ]] ; then
88fab7d6 111 _err "Usage: file startline endline"
112 return 1
113 fi
114
115 i="$(grep -n -- "$startline" $filename | cut -d : -f 1)"
a63b05a9 116 if [[ -z "$i" ]] ; then
88fab7d6 117 _err "Can not find start line: $startline"
118 return 1
119 fi
120 let "i+=1"
121 _debug i $i
122
123 j="$(grep -n -- "$endline" $filename | cut -d : -f 1)"
a63b05a9 124 if [[ -z "$j" ]] ; then
88fab7d6 125 _err "Can not find end line: $endline"
126 return 1
127 fi
128 let "j-=1"
129 _debug j $j
130
131 sed -n $i,${j}p "$filename"
132
133}
134
135#Usage: multiline
4c3b3608 136_base64() {
a63b05a9 137 if [[ "$1" ]] ; then
88fab7d6 138 openssl base64 -e
139 else
140 openssl base64 -e | tr -d '\r\n'
141 fi
142}
143
144#Usage: multiline
145_dbase64() {
a63b05a9 146 if [[ "$1" ]] ; then
88fab7d6 147 openssl base64 -d -A
148 else
149 openssl base64 -d
150 fi
151}
152
153#Usage: hashalg
154#Output Base64-encoded digest
155_digest() {
156 alg="$1"
a63b05a9 157 if [[ -z "$alg" ]] ; then
88fab7d6 158 _err "Usage: _digest hashalg"
159 return 1
160 fi
161
a63b05a9 162 if [[ "$alg" == "sha256" ]] ; then
88fab7d6 163 openssl dgst -sha256 -binary | _base64
164 else
165 _err "$alg is not supported yet"
166 return 1
167 fi
168
169}
170
171#Usage: keyfile hashalg
172#Output: Base64-encoded signature value
173_sign() {
174 keyfile="$1"
175 alg="$2"
a63b05a9 176 if [[ -z "$alg" ]] ; then
88fab7d6 177 _err "Usage: _sign keyfile hashalg"
178 return 1
179 fi
180
a63b05a9 181 if [[ "$alg" == "sha256" ]] ; then
88fab7d6 182 openssl dgst -sha256 -sign "$keyfile" | _base64
183 else
184 _err "$alg is not supported yet"
185 return 1
186 fi
187
4c3b3608 188}
189
34c27e09 190_ss() {
191 _port="$1"
edf08da6 192
193 if _exists "ss" ; then
194 _debug "Using: ss"
195 ss -ntpl | grep :$_port" "
196 return 0
197 fi
198
199 if _exists "netstat" ; then
251fc37c 200 _debug "Using: netstat"
ccb96535 201 if netstat -h 2>&1 | grep "\-p proto" >/dev/null ; then
202 #for windows version netstat tool
50a4ef9c 203 netstat -anb -p tcp | grep "LISTENING" | grep :$_port" "
ccb96535 204 else
14165e5a 205 if netstat -help 2>&1 | grep "\-p protocol" >/dev/null ; then
edf08da6 206 netstat -an -p tcp | grep LISTEN | grep :$_port" "
207 else
208 netstat -ntpl | grep :$_port" "
209 fi
ccb96535 210 fi
34c27e09 211 return 0
212 fi
edf08da6 213
34c27e09 214 return 1
215}
216
ac2d5123 217toPkcs() {
218 domain="$1"
219 pfxPassword="$2"
220 if [[ -z "$domain" ]] ; then
a7b7355d 221 echo "Usage: $PROJECT_ENTRY --toPkcs -d domain [--password pfx-password]"
ac2d5123 222 return 1
223 fi
224
225 _initpath "$domain"
226
227 if [[ "$pfxPassword" ]] ; then
228 openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword"
229 else
230 openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH"
231 fi
232
233 if [[ "$?" == "0" ]] ; then
234 _info "Success, Pfx is exported to: $CERT_PFX_PATH"
235 fi
236
237}
238
4c3b3608 239#domain [2048]
240createAccountKey() {
241 _info "Creating account key"
a63b05a9 242 if [[ -z "$1" ]] ; then
a7b7355d 243 echo Usage: $PROJECT_ENTRY --createAccountKey -d domain.com [--accountkeylength 2048]
4c3b3608 244 return
245 fi
246
247 account=$1
248 length=$2
b5eb4b90 249 _debug account "$account"
250 _debug length "$length"
4c3b3608 251 if [[ "$length" == "ec-"* ]] ; then
252 length=2048
253 fi
254
b5eb4b90 255 if [[ -z "$2" ]] || [[ "$2" == "no" ]] ; then
4c3b3608 256 _info "Use default length 2048"
257 length=2048
258 fi
259 _initpath
260
a63b05a9 261 if [[ -f "$ACCOUNT_KEY_PATH" ]] ; then
4c3b3608 262 _info "Account key exists, skip"
263 return
264 else
265 #generate account key
f89d991d 266 openssl genrsa $length 2>/dev/null > "$ACCOUNT_KEY_PATH"
4c3b3608 267 fi
268
269}
270
271#domain length
272createDomainKey() {
273 _info "Creating domain key"
a63b05a9 274 if [[ -z "$1" ]] ; then
a7b7355d 275 echo Usage: $PROJECT_ENTRY --createDomainKey -d domain.com [ --keylength 2048 ]
4c3b3608 276 return
277 fi
278
279 domain=$1
280 length=$2
281 isec=""
282 if [[ "$length" == "ec-"* ]] ; then
283 isec="1"
284 length=$(printf $length | cut -d '-' -f 2-100)
285 eccname="$length"
286 fi
287
a63b05a9 288 if [[ -z "$length" ]] ; then
289 if [[ "$isec" ]] ; then
4c3b3608 290 length=256
291 else
292 length=2048
293 fi
294 fi
295 _info "Use length $length"
296
a63b05a9 297 if [[ "$isec" ]] ; then
298 if [[ "$length" == "256" ]] ; then
4c3b3608 299 eccname="prime256v1"
300 fi
a63b05a9 301 if [[ "$length" == "384" ]] ; then
4c3b3608 302 eccname="secp384r1"
303 fi
a63b05a9 304 if [[ "$length" == "521" ]] ; then
4c3b3608 305 eccname="secp521r1"
306 fi
307 _info "Using ec name: $eccname"
308 fi
309
310 _initpath $domain
311
a63b05a9 312 if [[ ! -f "$CERT_KEY_PATH" ]] || ( [[ "$FORCE" ]] && ! [[ "$IS_RENEW" ]] ); then
4c3b3608 313 #generate account key
a63b05a9 314 if [[ "$isec" ]] ; then
4c3b3608 315 openssl ecparam -name $eccname -genkey 2>/dev/null > "$CERT_KEY_PATH"
316 else
317 openssl genrsa $length 2>/dev/null > "$CERT_KEY_PATH"
318 fi
319 else
a63b05a9 320 if [[ "$IS_RENEW" ]] ; then
4c3b3608 321 _info "Domain key exists, skip"
322 return 0
323 else
324 _err "Domain key exists, do you want to overwrite the key?"
41e3eafa 325 _err "Add '--force', and try again."
4c3b3608 326 return 1
327 fi
328 fi
329
330}
331
332# domain domainlist
333createCSR() {
334 _info "Creating csr"
a63b05a9 335 if [[ -z "$1" ]] ; then
a7b7355d 336 echo Usage: $PROJECT_ENTRY --createCSR -d domain1.com [-d domain2.com -d domain3.com ... ]
4c3b3608 337 return
338 fi
339 domain=$1
340 _initpath $domain
341
342 domainlist=$2
343
a63b05a9 344 if [[ -f "$CSR_PATH" ]] && [[ "$IS_RENEW" ]] && [[ -z "$FORCE" ]]; then
4c3b3608 345 _info "CSR exists, skip"
346 return
347 fi
348
a63b05a9 349 if [[ -z "$domainlist" ]] || [[ "$domainlist" == "no" ]]; then
4c3b3608 350 #single domain
351 _info "Single domain" $domain
1ad65f7d 352 printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n" > "$DOMAIN_SSL_CONF"
353 openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -config "$DOMAIN_SSL_CONF" -out "$CSR_PATH"
4c3b3608 354 else
355 alt="DNS:$(echo $domainlist | sed "s/,/,DNS:/g")"
356 #multi
357 _info "Multi domain" "$alt"
358 printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n[SAN]\nsubjectAltName=$alt" > "$DOMAIN_SSL_CONF"
359 openssl req -new -sha256 -key "$CERT_KEY_PATH" -subj "/CN=$domain" -reqexts SAN -config "$DOMAIN_SSL_CONF" -out "$CSR_PATH"
360 fi
361
362}
363
166096dc 364_urlencode() {
4c3b3608 365 __n=$(cat)
366 echo $__n | tr '/+' '_-' | tr -d '= '
367}
368
369_time2str() {
370 #BSD
371 if date -u -d@$1 2>/dev/null ; then
372 return
373 fi
374
375 #Linux
376 if date -u -r $1 2>/dev/null ; then
377 return
378 fi
379
380}
381
44df2967 382_stat() {
383 #Linux
384 if stat -c '%U:%G' "$1" 2>/dev/null ; then
385 return
386 fi
387
388 #BSD
389 if stat -f '%Su:%Sg' "$1" 2>/dev/null ; then
390 return
391 fi
392}
393
166096dc 394#keyfile
395_calcjwk() {
396 keyfile="$1"
a63b05a9 397 if [[ -z "$keyfile" ]] ; then
166096dc 398 _err "Usage: _calcjwk keyfile"
399 return 1
400 fi
401 EC_SIGN=""
402 if grep "BEGIN RSA PRIVATE KEY" "$keyfile" > /dev/null 2>&1 ; then
403 _debug "RSA key"
404 pub_exp=$(openssl rsa -in $keyfile -noout -text | grep "^publicExponent:"| cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1)
a63b05a9 405 if [[ "${#pub_exp}" == "5" ]] ; then
166096dc 406 pub_exp=0$pub_exp
407 fi
a63b05a9 408 _debug2 pub_exp "$pub_exp"
166096dc 409
410 e=$(echo $pub_exp | _h2b | _base64)
a63b05a9 411 _debug2 e "$e"
166096dc 412
413 modulus=$(openssl rsa -in $keyfile -modulus -noout | cut -d '=' -f 2 )
414 n=$(echo $modulus| _h2b | _base64 | _urlencode )
415 jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'
a63b05a9 416 _debug2 jwk "$jwk"
166096dc 417
418 HEADER='{"alg": "RS256", "jwk": '$jwk'}'
419 HEADERPLACE='{"nonce": "NONCE", "alg": "RS256", "jwk": '$jwk'}'
420 elif grep "BEGIN EC PRIVATE KEY" "$keyfile" > /dev/null 2>&1 ; then
421 _debug "EC key"
422 EC_SIGN="1"
423 crv="$(openssl ec -in $keyfile -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
a63b05a9 424 _debug2 crv $crv
166096dc 425
426 pubi="$(openssl ec -in $keyfile -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
a63b05a9 427 _debug2 pubi $pubi
166096dc 428 let "pubi=pubi+1"
429
430 pubj="$(openssl ec -in $keyfile -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)"
a63b05a9 431 _debug2 pubj $pubj
166096dc 432 let "pubj=pubj-1"
433
434 pubtext="$(openssl ec -in $keyfile -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")"
a63b05a9 435 _debug2 pubtext "$pubtext"
166096dc 436
437 xlen="$(printf "$pubtext" | tr -d ':' | wc -c)"
438 let "xlen=xlen/4"
a63b05a9 439 _debug2 xlen $xlen
166096dc 440
441 let "xend=xlen+1"
442 x="$(printf $pubtext | cut -d : -f 2-$xend)"
a63b05a9 443 _debug2 x $x
166096dc 444
445 x64="$(printf $x | tr -d : | _h2b | _base64 | _urlencode)"
a63b05a9 446 _debug2 x64 $x64
166096dc 447
448 let "xend+=1"
449 y="$(printf $pubtext | cut -d : -f $xend-10000)"
a63b05a9 450 _debug2 y $y
166096dc 451
452 y64="$(printf $y | tr -d : | _h2b | _base64 | _urlencode)"
a63b05a9 453 _debug2 y64 $y64
166096dc 454
455 jwk='{"kty": "EC", "crv": "'$crv'", "x": "'$x64'", "y": "'$y64'"}'
a63b05a9 456 _debug2 jwk "$jwk"
166096dc 457
458 HEADER='{"alg": "ES256", "jwk": '$jwk'}'
459 HEADERPLACE='{"nonce": "NONCE", "alg": "ES256", "jwk": '$jwk'}'
460
461 else
462 _err "Only RSA or EC key is supported."
463 return 1
464 fi
465
a63b05a9 466 _debug2 HEADER "$HEADER"
166096dc 467}
c60883ef 468# body url [needbase64]
469_post() {
470 body="$1"
471 url="$2"
472 needbase64="$3"
473
474 if _exists "curl" ; then
bbbdcb09 475 CURL="$CURL --dump-header $HTTP_HEADER "
a63b05a9 476 if [[ "$needbase64" ]] ; then
bbbdcb09 477 response="$($CURL -A "User-Agent: $USER_AGENT" -X POST --data "$body" $url | _base64)"
c60883ef 478 else
bbbdcb09 479 response="$($CURL -A "User-Agent: $USER_AGENT" -X POST --data "$body" $url)"
c60883ef 480 fi
481 else
a63b05a9 482 if [[ "$needbase64" ]] ; then
bbbdcb09 483 response="$($WGET -S -O - --user-agent="$USER_AGENT" --post-data="$body" $url 2>"$HTTP_HEADER" | _base64)"
c60883ef 484 else
bbbdcb09 485 response="$($WGET -S -O - --user-agent="$USER_AGENT" --post-data="$body" $url 2>"$HTTP_HEADER")"
c60883ef 486 fi
487 _sed_i "s/^ *//g" "$HTTP_HEADER"
488 fi
489 echo -n "$response"
490
491}
492
493# url getheader
494_get() {
495 url="$1"
496 onlyheader="$2"
497 _debug url $url
498 if _exists "curl" ; then
a63b05a9 499 if [[ "$onlyheader" ]] ; then
bbbdcb09 500 $CURL -I -A "User-Agent: $USER_AGENT" $url
c60883ef 501 else
bbbdcb09 502 $CURL -A "User-Agent: $USER_AGENT" $url
c60883ef 503 fi
504 else
bbbdcb09 505 _debug "WGET" "$WGET"
a63b05a9 506 if [[ "$onlyheader" ]] ; then
bbbdcb09 507 eval $WGET --user-agent=\"$USER_AGENT\" -S -O /dev/null $url 2>&1 | sed 's/^[ ]*//g'
c60883ef 508 else
bbbdcb09 509 eval $WGET --user-agent=\"$USER_AGENT\" -O - $url
c60883ef 510 fi
511 fi
512 ret=$?
513 return $ret
514}
166096dc 515
516# url payload needbase64 keyfile
4c3b3608 517_send_signed_request() {
518 url=$1
519 payload=$2
520 needbase64=$3
166096dc 521 keyfile=$4
a63b05a9 522 if [[ -z "$keyfile" ]] ; then
166096dc 523 keyfile="$ACCOUNT_KEY_PATH"
524 fi
4c3b3608 525 _debug url $url
526 _debug payload "$payload"
527
166096dc 528 if ! _calcjwk "$keyfile" ; then
529 return 1
530 fi
c60883ef 531
166096dc 532 payload64=$(echo -n $payload | _base64 | _urlencode)
a63b05a9 533 _debug2 payload64 $payload64
4c3b3608 534
535 nonceurl="$API/directory"
bbbdcb09 536 nonce="$(_get $nonceurl "onlyheader" | grep -o "Replay-Nonce:.*$" | head -1 | tr -d "\r\n" | cut -d ' ' -f 2)"
4c3b3608 537
538 _debug nonce "$nonce"
539
540 protected="$(printf "$HEADERPLACE" | sed "s/NONCE/$nonce/" )"
a63b05a9 541 _debug2 protected "$protected"
4c3b3608 542
166096dc 543 protected64="$(printf "$protected" | _base64 | _urlencode)"
a63b05a9 544 _debug2 protected64 "$protected64"
166096dc 545
88fab7d6 546 sig=$(echo -n "$protected64.$payload64" | _sign "$keyfile" "sha256" | _urlencode)
a63b05a9 547 _debug2 sig "$sig"
4c3b3608 548
549 body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
a63b05a9 550 _debug2 body "$body"
4c3b3608 551
bbbdcb09 552
c60883ef 553 response="$(_post "$body" $url "$needbase64" )"
4c3b3608 554
c60883ef 555 responseHeaders="$(cat $HTTP_HEADER)"
4c3b3608 556
a63b05a9 557 _debug2 responseHeaders "$responseHeaders"
558 _debug2 response "$response"
c60883ef 559 code="$(grep "^HTTP" $HTTP_HEADER | tail -1 | cut -d " " -f 2 | tr -d "\r\n" )"
4c3b3608 560 _debug code $code
561
562}
563
4c3b3608 564
565#setopt "file" "opt" "=" "value" [";"]
566_setopt() {
567 __conf="$1"
568 __opt="$2"
569 __sep="$3"
570 __val="$4"
571 __end="$5"
a63b05a9 572 if [[ -z "$__opt" ]] ; then
4c3b3608 573 echo usage: _setopt '"file" "opt" "=" "value" [";"]'
574 return
575 fi
a63b05a9 576 if [[ ! -f "$__conf" ]] ; then
4c3b3608 577 touch "$__conf"
578 fi
579
580 if grep -H -n "^$__opt$__sep" "$__conf" > /dev/null ; then
a63b05a9 581 _debug2 OK
4c3b3608 582 if [[ "$__val" == *"&"* ]] ; then
583 __val="$(echo $__val | sed 's/&/\\&/g')"
584 fi
585 text="$(cat $__conf)"
6dfaaa70 586 echo "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf"
4c3b3608 587
588 elif grep -H -n "^#$__opt$__sep" "$__conf" > /dev/null ; then
589 if [[ "$__val" == *"&"* ]] ; then
590 __val="$(echo $__val | sed 's/&/\\&/g')"
591 fi
592 text="$(cat $__conf)"
6dfaaa70 593 echo "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf"
4c3b3608 594
595 else
a63b05a9 596 _debug2 APP
4c3b3608 597 echo "$__opt$__sep$__val$__end" >> "$__conf"
598 fi
599 _debug "$(grep -H -n "^$__opt$__sep" $__conf)"
600}
601
602#_savedomainconf key value
603#save to domain.conf
604_savedomainconf() {
605 key="$1"
606 value="$2"
a63b05a9 607 if [[ "$DOMAIN_CONF" ]] ; then
4c3b3608 608 _setopt $DOMAIN_CONF "$key" "=" "$value"
609 else
610 _err "DOMAIN_CONF is empty, can not save $key=$value"
611 fi
612}
613
614#_saveaccountconf key value
615_saveaccountconf() {
616 key="$1"
617 value="$2"
a63b05a9 618 if [[ "$ACCOUNT_CONF_PATH" ]] ; then
281aa349 619 _setopt $ACCOUNT_CONF_PATH "$key" "=" "\"$value\""
4c3b3608 620 else
621 _err "ACCOUNT_CONF_PATH is empty, can not save $key=$value"
622 fi
623}
624
625_startserver() {
626 content="$1"
6fc1447f 627 _debug "startserver: $$"
1b2e940d 628 nchelp="$(nc -h 2>&1)"
850c1128 629
399306a1 630 if echo "$nchelp" | grep "\-q[ ,]" >/dev/null ; then
850c1128 631 _NC="nc -q 1 -l"
632 else
f76eb452 633 if echo "$nchelp" | grep "GNU netcat" >/dev/null && echo "$nchelp" | grep "\-c, \-\-close" >/dev/null ; then
634 _NC="nc -c -l"
6d60f288 635 elif echo "$nchelp" | grep "\-N" |grep "Shutdown the network socket after EOF on stdin" >/dev/null ; then
636 _NC="nc -N -l"
f76eb452 637 else
638 _NC="nc -l"
639 fi
4c3b3608 640 fi
1b2e940d 641
f76eb452 642 _debug "_NC" "$_NC"
4c3b3608 643# while true ; do
a63b05a9 644 if [[ "$DEBUG" ]] ; then
3aff11f6 645 if ! echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -p $Le_HTTPPort -vv ; then
646 echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC $Le_HTTPPort -vv ;
647 fi
4c3b3608 648 else
bac3b6b0 649 if ! echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -p $Le_HTTPPort > /dev/null 2>&1; then
650 echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC $Le_HTTPPort > /dev/null 2>&1
3aff11f6 651 fi
4c3b3608 652 fi
a63b05a9 653 if [[ "$?" != "0" ]] ; then
051c706d 654 _err "nc listen error."
6fc1447f 655 exit 1
051c706d 656 fi
4c3b3608 657# done
658}
659
6fc1447f 660_stopserver(){
4c3b3608 661 pid="$1"
6fc1447f 662 _debug "pid" "$pid"
663 if [[ -z "$pid" ]] ; then
664 return
665 fi
666
dca09ded 667 if [[ "$(ps | grep "$pid")" ]] ; then
668 _debug "Found proc process, kill it."
770dc4b2 669 kill -s 9 $pid > /dev/null
6fc1447f 670 fi
671
233e8a20 672 for ncid in $(echo $(ps | grep nc) | cut -d " " -f 1) ; do
dca09ded 673 _debug "kill $ncid"
770dc4b2 674 kill -s 9 $ncid > /dev/null
dca09ded 675 done
676
3d434e43 677 _get "http://localhost:$Le_HTTPPort" >/dev/null 2>&1
4c3b3608 678
679}
680
681_initpath() {
682
a63b05a9 683 if [[ -z "$LE_WORKING_DIR" ]] ; then
6cc11ffb 684 LE_WORKING_DIR=$HOME/.$PROJECT_NAME
4c3b3608 685 fi
686
d53289d7 687 _DEFAULT_ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf"
688
635695ec 689 if [[ -z "$ACCOUNT_CONF_PATH" ]] ; then
690 if [[ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]] ; then
691 source "$_DEFAULT_ACCOUNT_CONF_PATH"
692 fi
d53289d7 693 fi
694
a63b05a9 695 if [[ -z "$ACCOUNT_CONF_PATH" ]] ; then
d53289d7 696 ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH"
4c3b3608 697 fi
698
a63b05a9 699 if [[ -f "$ACCOUNT_CONF_PATH" ]] ; then
4c3b3608 700 source "$ACCOUNT_CONF_PATH"
701 fi
702
281aa349 703 if [[ "$IN_CRON" ]] ; then
704 if [[ ! "$_USER_PATH_EXPORTED" ]] ; then
705 _USER_PATH_EXPORTED=1
706 export PATH="$USER_PATH:$PATH"
707 fi
708 fi
709
a63b05a9 710 if [[ -z "$API" ]] ; then
711 if [[ -z "$STAGE" ]] ; then
4c3b3608 712 API="$DEFAULT_CA"
713 else
714 API="$STAGE_CA"
715 _info "Using stage api:$API"
716 fi
717 fi
718
a63b05a9 719 if [[ -z "$ACME_DIR" ]] ; then
4c3b3608 720 ACME_DIR="/home/.acme"
721 fi
722
a63b05a9 723 if [[ -z "$APACHE_CONF_BACKUP_DIR" ]] ; then
8a144f4d 724 APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR"
4c3b3608 725 fi
726
a63b05a9 727 if [[ -z "$USER_AGENT" ]] ; then
bbbdcb09 728 USER_AGENT="$DEFAULT_USER_AGENT"
729 fi
730
731 HTTP_HEADER="$LE_WORKING_DIR/http.header"
732
733 WGET="wget -q"
a63b05a9 734 if [[ "$DEBUG" -ge "2" ]] ; then
bbbdcb09 735 WGET="$WGET -d "
736 fi
737
738 dp="$LE_WORKING_DIR/curl.dump"
4a0f23e2 739 CURL="curl -L --silent"
a63b05a9 740 if [[ "$DEBUG" -ge "2" ]] ; then
4a0f23e2 741 CURL="$CURL -L --trace-ascii $dp "
bbbdcb09 742 fi
b2817897 743
744 _DEFAULT_ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key"
a63b05a9 745 if [[ -z "$ACCOUNT_KEY_PATH" ]] ; then
b2817897 746 ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH"
4c3b3608 747 fi
b2817897 748
749 domain="$1"
4c3b3608 750
a63b05a9 751 if [[ -z "$domain" ]] ; then
4c3b3608 752 return 0
753 fi
754
b2817897 755 _DEFAULT_CERT_HOME="$LE_WORKING_DIR"
756 if [[ -z "$CERT_HOME" ]] ; then
757 CERT_HOME="$_DEFAULT_CERT_HOME"
758 fi
759
760 domainhome="$CERT_HOME/$domain"
4c3b3608 761 mkdir -p "$domainhome"
762
a63b05a9 763 if [[ -z "$DOMAIN_PATH" ]] ; then
4c3b3608 764 DOMAIN_PATH="$domainhome"
765 fi
a63b05a9 766 if [[ -z "$DOMAIN_CONF" ]] ; then
1ad65f7d 767 DOMAIN_CONF="$domainhome/$domain.conf"
4c3b3608 768 fi
769
a63b05a9 770 if [[ -z "$DOMAIN_SSL_CONF" ]] ; then
1ad65f7d 771 DOMAIN_SSL_CONF="$domainhome/$domain.ssl.conf"
4c3b3608 772 fi
773
a63b05a9 774 if [[ -z "$CSR_PATH" ]] ; then
4c3b3608 775 CSR_PATH="$domainhome/$domain.csr"
776 fi
a63b05a9 777 if [[ -z "$CERT_KEY_PATH" ]] ; then
4c3b3608 778 CERT_KEY_PATH="$domainhome/$domain.key"
779 fi
a63b05a9 780 if [[ -z "$CERT_PATH" ]] ; then
4c3b3608 781 CERT_PATH="$domainhome/$domain.cer"
782 fi
a63b05a9 783 if [[ -z "$CA_CERT_PATH" ]] ; then
4c3b3608 784 CA_CERT_PATH="$domainhome/ca.cer"
785 fi
a63b05a9 786 if [[ -z "$CERT_FULLCHAIN_PATH" ]] ; then
850db6d4 787 CERT_FULLCHAIN_PATH="$domainhome/fullchain.cer"
caf1fc10 788 fi
a63b05a9 789 if [[ -z "$CERT_PFX_PATH" ]] ; then
ac2d5123 790 CERT_PFX_PATH="$domainhome/$domain.pfx"
791 fi
4c3b3608 792}
793
794
795_apachePath() {
4c3b3608 796 httpdconfname="$(apachectl -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"' )"
d62ee940 797 if [[ "$httpdconfname" == '/'* ]] ; then
798 httpdconf="$httpdconfname"
c456d954 799 httpdconfname="$(basename $httpdconfname)"
d62ee940 800 else
801 httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"' )"
802 httpdconf="$httpdroot/$httpdconfname"
803 fi
804
a63b05a9 805 if [[ ! -f $httpdconf ]] ; then
4c3b3608 806 _err "Apache Config file not found" $httpdconf
807 return 1
808 fi
809 return 0
810}
811
812_restoreApache() {
a63b05a9 813 if [[ -z "$usingApache" ]] ; then
4c3b3608 814 return 0
815 fi
816 _initpath
817 if ! _apachePath ; then
818 return 1
819 fi
820
a63b05a9 821 if [[ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]] ; then
4c3b3608 822 _debug "No config file to restore."
823 return 0
824 fi
825
826 cp -p "$APACHE_CONF_BACKUP_DIR/$httpdconfname" "$httpdconf"
5ef501c5 827 _debug "Restored: $httpdconf."
4c3b3608 828 if ! apachectl -t ; then
829 _err "Sorry, restore apache config error, please contact me."
830 return 1;
831 fi
5ef501c5 832 _debug "Restored successfully."
4c3b3608 833 rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname"
834 return 0
835}
836
837_setApache() {
838 _initpath
839 if ! _apachePath ; then
840 return 1
841 fi
842
843 #backup the conf
844 _debug "Backup apache config file" $httpdconf
845 cp -p $httpdconf $APACHE_CONF_BACKUP_DIR/
846 _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname"
847 _info "In case there is an error that can not be restored automatically, you may try restore it yourself."
848 _info "The backup file will be deleted on sucess, just forget it."
849
850 #add alias
b09d597c 851
852 apacheVer="$(apachectl -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2 )"
853 _debug "apacheVer" "$apacheVer"
854 apacheMajer="$(echo "$apacheVer" | cut -d . -f 1)"
855 apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
856
857 if [[ "$apacheVer" ]] && [[ "$apacheMajer" -ge "2" ]] && [[ "$apacheMinor" -ge "4" ]] ; then
858 echo "
4c3b3608 859Alias /.well-known/acme-challenge $ACME_DIR
860
861<Directory $ACME_DIR >
862Require all granted
b09d597c 863</Directory>
864 " >> $httpdconf
865 else
866 echo "
867Alias /.well-known/acme-challenge $ACME_DIR
868
869<Directory $ACME_DIR >
870Order allow,deny
871Allow from all
4c3b3608 872</Directory>
873 " >> $httpdconf
b09d597c 874 fi
875
4c3b3608 876
877 if ! apachectl -t ; then
878 _err "Sorry, apache config error, please contact me."
879 _restoreApache
880 return 1;
881 fi
882
a63b05a9 883 if [[ ! -d "$ACME_DIR" ]] ; then
4c3b3608 884 mkdir -p "$ACME_DIR"
885 chmod 755 "$ACME_DIR"
886 fi
887
888 if ! apachectl graceful ; then
889 _err "Sorry, apachectl graceful error, please contact me."
890 _restoreApache
891 return 1;
892 fi
893 usingApache="1"
894 return 0
895}
896
5ef501c5 897_clearup() {
4c3b3608 898 _stopserver $serverproc
899 serverproc=""
900 _restoreApache
901}
902
903# webroot removelevel tokenfile
904_clearupwebbroot() {
905 __webroot="$1"
a63b05a9 906 if [[ -z "$__webroot" ]] ; then
4c3b3608 907 _debug "no webroot specified, skip"
908 return 0
909 fi
910
a63b05a9 911 if [[ "$2" == '1' ]] ; then
4c3b3608 912 _debug "remove $__webroot/.well-known"
913 rm -rf "$__webroot/.well-known"
a63b05a9 914 elif [[ "$2" == '2' ]] ; then
4c3b3608 915 _debug "remove $__webroot/.well-known/acme-challenge"
916 rm -rf "$__webroot/.well-known/acme-challenge"
a63b05a9 917 elif [[ "$2" == '3' ]] ; then
4c3b3608 918 _debug "remove $__webroot/.well-known/acme-challenge/$3"
919 rm -rf "$__webroot/.well-known/acme-challenge/$3"
920 else
921 _info "Skip for removelevel:$2"
922 fi
923
924 return 0
925
926}
927
928issue() {
a63b05a9 929 if [[ -z "$2" ]] ; then
a7b7355d 930 echo "Usage: $PROJECT_ENTRY --issue -d a.com -w /path/to/webroot/a.com/ "
4c3b3608 931 return 1
932 fi
933 Le_Webroot="$1"
934 Le_Domain="$2"
935 Le_Alt="$3"
936 Le_Keylength="$4"
937 Le_RealCertPath="$5"
938 Le_RealKeyPath="$6"
939 Le_RealCACertPath="$7"
940 Le_ReloadCmd="$8"
a63b05a9 941 Le_RealFullChainPath="$9"
4c3b3608 942
943 _initpath $Le_Domain
944
a63b05a9 945 if [[ -f "$DOMAIN_CONF" ]] ; then
4c3b3608 946 Le_NextRenewTime=$(grep "^Le_NextRenewTime=" "$DOMAIN_CONF" | cut -d '=' -f 2)
a63b05a9 947 if [[ -z "$FORCE" ]] && [[ "$Le_NextRenewTime" ]] && [[ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ]] ; then
4c3b3608 948 _info "Skip, Next renewal time is: $(grep "^Le_NextRenewTimeStr" "$DOMAIN_CONF" | cut -d '=' -f 2)"
949 return 2
950 fi
951 fi
96a46cfc 952
953 _setopt "$DOMAIN_CONF" "Le_Domain" "=" "$Le_Domain"
954 _setopt "$DOMAIN_CONF" "Le_Alt" "=" "$Le_Alt"
955 _setopt "$DOMAIN_CONF" "Le_Webroot" "=" "$Le_Webroot"
956 _setopt "$DOMAIN_CONF" "Le_Keylength" "=" "$Le_Keylength"
957 _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\""
958 _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\""
959 _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\""
960 _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\""
a63b05a9 961 _setopt "$DOMAIN_CONF" "Le_RealFullChainPath" "=" "\"$Le_RealFullChainPath\""
4c3b3608 962
a63b05a9 963 if [[ "$Le_Alt" == "no" ]] ; then
4c3b3608 964 Le_Alt=""
965 fi
a63b05a9 966 if [[ "$Le_Keylength" == "no" ]] ; then
4c3b3608 967 Le_Keylength=""
968 fi
a63b05a9 969 if [[ "$Le_RealCertPath" == "no" ]] ; then
4c3b3608 970 Le_RealCertPath=""
971 fi
a63b05a9 972 if [[ "$Le_RealKeyPath" == "no" ]] ; then
4c3b3608 973 Le_RealKeyPath=""
974 fi
a63b05a9 975 if [[ "$Le_RealCACertPath" == "no" ]] ; then
4c3b3608 976 Le_RealCACertPath=""
977 fi
a63b05a9 978 if [[ "$Le_ReloadCmd" == "no" ]] ; then
4c3b3608 979 Le_ReloadCmd=""
980 fi
a63b05a9 981 if [[ "$Le_RealFullChainPath" == "no" ]] ; then
982 Le_RealFullChainPath=""
983 fi
96a46cfc 984
4c3b3608 985
a63b05a9 986 if [[ "$Le_Webroot" == *"no"* ]] ; then
4c3b3608 987 _info "Standalone mode."
5ef501c5 988 if ! _exists "nc" ; then
4c3b3608 989 _err "Please install netcat(nc) tools first."
990 return 1
991 fi
992
a63b05a9 993 if [[ -z "$Le_HTTPPort" ]] ; then
4c3b3608 994 Le_HTTPPort=80
995 fi
996 _setopt "$DOMAIN_CONF" "Le_HTTPPort" "=" "$Le_HTTPPort"
997
251fc37c 998 netprc="$(_ss "$Le_HTTPPort" | grep "$Le_HTTPPort")"
a63b05a9 999 if [[ "$netprc" ]] ; then
4c3b3608 1000 _err "$netprc"
1001 _err "tcp port $Le_HTTPPort is already used by $(echo "$netprc" | cut -d : -f 4)"
1002 _err "Please stop it first"
1003 return 1
1004 fi
1005 fi
1006
a63b05a9 1007 if [[ "$Le_Webroot" == *"apache"* ]] ; then
4c3b3608 1008 if ! _setApache ; then
1009 _err "set up apache error. Report error to me."
1010 return 1
1011 fi
1012 wellknown_path="$ACME_DIR"
1013 else
1014 usingApache=""
1015 fi
1016
41e3eafa 1017 if [[ ! -f "$ACCOUNT_KEY_PATH" ]] ; then
1018 if ! createAccountKey $Le_Domain $Le_Keylength ; then
1019 _err "Create account key error."
5ef501c5 1020 if [[ "$usingApache" ]] ; then
1021 _restoreApache
1022 fi
41e3eafa 1023 return 1
1024 fi
1025 fi
1026
166096dc 1027 if ! _calcjwk "$ACCOUNT_KEY_PATH" ; then
5ef501c5 1028 if [[ "$usingApache" ]] ; then
1029 _restoreApache
1030 fi
166096dc 1031 return 1
1032 fi
1033
1034 accountkey_json=$(echo -n "$jwk" | tr -d ' ' )
88fab7d6 1035 thumbprint=$(echo -n "$accountkey_json" | _digest "sha256" | _urlencode)
4c3b3608 1036
88fab7d6 1037 accountkeyhash="$(cat "$ACCOUNT_KEY_PATH" | _digest "sha256" )"
a63b05a9 1038 accountkeyhash="$(echo $accountkeyhash$API | _digest "sha256" )"
1039 if [[ "$accountkeyhash" != "$ACCOUNT_KEY_HASH" ]] ; then
166096dc 1040 _info "Registering account"
1041 regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}'
a63b05a9 1042 if [[ "$ACCOUNT_EMAIL" ]] ; then
166096dc 1043 regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}'
1044 fi
1045 _send_signed_request "$API/acme/new-reg" "$regjson"
1046
a63b05a9 1047 if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then
166096dc 1048 _info "Registered"
1049 echo $response > $LE_WORKING_DIR/account.json
a63b05a9 1050 elif [[ "$code" == '409' ]] ; then
166096dc 1051 _info "Already registered"
1052 else
1053 _err "Register account Error: $response"
1054 _clearup
1055 return 1
1056 fi
1057 ACCOUNT_KEY_HASH="$accountkeyhash"
1058 _saveaccountconf "ACCOUNT_KEY_HASH" "$ACCOUNT_KEY_HASH"
1059 else
1060 _info "Skip register account key"
1061 fi
1062
41e3eafa 1063 if [[ ! -f "$CERT_KEY_PATH" ]] ; then
1064 if ! createDomainKey $Le_Domain $Le_Keylength ; then
1065 _err "Create domain key error."
5ef501c5 1066 _clearup
41e3eafa 1067 return 1
1068 fi
4c3b3608 1069 fi
1070
1071 if ! createCSR $Le_Domain $Le_Alt ; then
1072 _err "Create CSR error."
5ef501c5 1073 _clearup
4c3b3608 1074 return 1
1075 fi
a63b05a9 1076
4c3b3608 1077 vlist="$Le_Vlist"
1078 # verify each domain
1079 _info "Verify each domain"
1080 sep='#'
a63b05a9 1081 if [[ -z "$vlist" ]] ; then
4c3b3608 1082 alldomains=$(echo "$Le_Domain,$Le_Alt" | tr ',' ' ' )
a63b05a9 1083 _index=1
1084 _currentRoot=""
4c3b3608 1085 for d in $alldomains
a63b05a9 1086 do
1087 _info "Getting webroot for domain" $d
1088 _w="$(echo $Le_Webroot | cut -d , -f $_index)"
1089 _debug _w "$_w"
1090 if [[ "$_w" ]] ; then
1091 _currentRoot="$_w"
1092 fi
1093 _debug "_currentRoot" "$_currentRoot"
1094 let "_index+=1"
1095
1096 vtype="$VTYPE_HTTP"
1097 if [[ "$_currentRoot" == "dns"* ]] ; then
1098 vtype="$VTYPE_DNS"
1099 fi
4c3b3608 1100 _info "Getting token for domain" $d
1101 _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}"
a63b05a9 1102 if [[ ! -z "$code" ]] && [[ ! "$code" == '201' ]] ; then
4c3b3608 1103 _err "new-authz error: $response"
1104 _clearup
1105 return 1
1106 fi
1107
348cddd7 1108 entry="$(printf $response | egrep -o '\{[^{]*"type":"'$vtype'"[^}]*')"
4c3b3608 1109 _debug entry "$entry"
1110
1111 token="$(printf "$entry" | egrep -o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
1112 _debug token $token
1113
1114 uri="$(printf "$entry" | egrep -o '"uri":"[^"]*'| cut -d : -f 2,3 | tr -d '"' )"
1115 _debug uri $uri
1116
1117 keyauthorization="$token.$thumbprint"
1118 _debug keyauthorization "$keyauthorization"
1119
a63b05a9 1120 dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
4c3b3608 1121 _debug dvlist "$dvlist"
1122
1123 vlist="$vlist$dvlist,"
1124
1125 done
1126
1127 #add entry
1128 dnsadded=""
1129 ventries=$(echo "$vlist" | tr ',' ' ' )
1130 for ventry in $ventries
1131 do
1132 d=$(echo $ventry | cut -d $sep -f 1)
1133 keyauthorization=$(echo $ventry | cut -d $sep -f 2)
a63b05a9 1134 vtype=$(echo $ventry | cut -d $sep -f 4)
1135 _currentRoot=$(echo $ventry | cut -d $sep -f 5)
1136 if [[ "$vtype" == "$VTYPE_DNS" ]] ; then
4c3b3608 1137 dnsadded='0'
1138 txtdomain="_acme-challenge.$d"
1139 _debug txtdomain "$txtdomain"
88fab7d6 1140 txt="$(echo -e -n $keyauthorization | _digest "sha256" | _urlencode)"
4c3b3608 1141 _debug txt "$txt"
1142 #dns
1143 #1. check use api
1144 d_api=""
a63b05a9 1145 if [[ -f "$LE_WORKING_DIR/$d/$_currentRoot" ]] ; then
1146 d_api="$LE_WORKING_DIR/$d/$_currentRoot"
1147 elif [[ -f "$LE_WORKING_DIR/$d/$_currentRoot.sh" ]] ; then
1148 d_api="$LE_WORKING_DIR/$d/$_currentRoot.sh"
1149 elif [[ -f "$LE_WORKING_DIR/$_currentRoot" ]] ; then
1150 d_api="$LE_WORKING_DIR/$_currentRoot"
1151 elif [[ -f "$LE_WORKING_DIR/$_currentRoot.sh" ]] ; then
1152 d_api="$LE_WORKING_DIR/$_currentRoot.sh"
1153 elif [[ -f "$LE_WORKING_DIR/dnsapi/$_currentRoot" ]] ; then
1154 d_api="$LE_WORKING_DIR/dnsapi/$_currentRoot"
1155 elif [[ -f "$LE_WORKING_DIR/dnsapi/$_currentRoot.sh" ]] ; then
1156 d_api="$LE_WORKING_DIR/dnsapi/$_currentRoot.sh"
4c3b3608 1157 fi
1158 _debug d_api "$d_api"
1159
a63b05a9 1160 if [[ "$d_api" ]] ; then
4c3b3608 1161 _info "Found domain api file: $d_api"
1162 else
1163 _err "Add the following TXT record:"
1164 _err "Domain: $txtdomain"
1165 _err "TXT value: $txt"
1166 _err "Please be aware that you prepend _acme-challenge. before your domain"
1167 _err "so the resulting subdomain will be: $txtdomain"
1168 continue
1169 fi
4c3b3608 1170
73b8b120 1171 (
1172 if ! source $d_api ; then
1173 _err "Load file $d_api error. Please check your api file and try again."
1174 return 1
1175 fi
1176
a63b05a9 1177 addcommand="$_currentRoot-add"
d53289d7 1178 if ! _exists $addcommand ; then
73b8b120 1179 _err "It seems that your api file is not correct, it must have a function named: $addcommand"
1180 return 1
1181 fi
1182
1183 if ! $addcommand $txtdomain $txt ; then
1184 _err "Error add txt for domain:$txtdomain"
1185 return 1
1186 fi
1187 )
4c3b3608 1188
73b8b120 1189 if [[ "$?" != "0" ]] ; then
5ef501c5 1190 _clearup
4c3b3608 1191 return 1
1192 fi
1193 dnsadded='1'
1194 fi
1195 done
1196
a63b05a9 1197 if [[ "$dnsadded" == '0' ]] ; then
4c3b3608 1198 _setopt "$DOMAIN_CONF" "Le_Vlist" "=" "\"$vlist\""
1199 _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
1200 _err "Please add the TXT records to the domains, and retry again."
5ef501c5 1201 _clearup
4c3b3608 1202 return 1
1203 fi
1204
1205 fi
1206
a63b05a9 1207 if [[ "$dnsadded" == '1' ]] ; then
4c3b3608 1208 _info "Sleep 60 seconds for the txt records to take effect"
1209 sleep 60
1210 fi
1211
1212 _debug "ok, let's start to verify"
a63b05a9 1213
4c3b3608 1214 ventries=$(echo "$vlist" | tr ',' ' ' )
1215 for ventry in $ventries
1216 do
1217 d=$(echo $ventry | cut -d $sep -f 1)
1218 keyauthorization=$(echo $ventry | cut -d $sep -f 2)
1219 uri=$(echo $ventry | cut -d $sep -f 3)
a63b05a9 1220 vtype=$(echo $ventry | cut -d $sep -f 4)
1221 _currentRoot=$(echo $ventry | cut -d $sep -f 5)
4c3b3608 1222 _info "Verifying:$d"
1223 _debug "d" "$d"
1224 _debug "keyauthorization" "$keyauthorization"
1225 _debug "uri" "$uri"
1226 removelevel=""
1227 token=""
a63b05a9 1228
1229 _debug "_currentRoot" "$_currentRoot"
1230
1231
1232 if [[ "$vtype" == "$VTYPE_HTTP" ]] ; then
1233 if [[ "$_currentRoot" == "no" ]] ; then
4c3b3608 1234 _info "Standalone mode server"
1235 _startserver "$keyauthorization" &
6fc1447f 1236 if [[ "$?" != "0" ]] ; then
5ef501c5 1237 _clearup
6fc1447f 1238 return 1
1239 fi
4c3b3608 1240 serverproc="$!"
1241 sleep 2
1242 _debug serverproc $serverproc
6fc1447f 1243
4c3b3608 1244 else
a63b05a9 1245 if [[ -z "$wellknown_path" ]] ; then
1246 wellknown_path="$_currentRoot/.well-known/acme-challenge"
4c3b3608 1247 fi
1248 _debug wellknown_path "$wellknown_path"
1249
a63b05a9 1250 if [[ ! -d "$_currentRoot/.well-known" ]] ; then
4c3b3608 1251 removelevel='1'
a63b05a9 1252 elif [[ ! -d "$_currentRoot/.well-known/acme-challenge" ]] ; then
4c3b3608 1253 removelevel='2'
1254 else
1255 removelevel='3'
1256 fi
1257
1258 token="$(echo -e -n "$keyauthorization" | cut -d '.' -f 1)"
1259 _debug "writing token:$token to $wellknown_path/$token"
1260
1261 mkdir -p "$wellknown_path"
1262 echo -n "$keyauthorization" > "$wellknown_path/$token"
df886ffa 1263 if [[ ! "$usingApache" ]] ; then
a63b05a9 1264 webroot_owner=$(_stat $_currentRoot)
df886ffa 1265 _debug "Changing owner/group of .well-known to $webroot_owner"
a63b05a9 1266 chown -R $webroot_owner "$_currentRoot/.well-known"
df886ffa 1267 fi
4c3b3608 1268
1269 fi
1270 fi
1271
1272 _send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}"
1273
a63b05a9 1274 if [[ ! -z "$code" ]] && [[ ! "$code" == '202' ]] ; then
c60883ef 1275 _err "$d:Challenge error: $response"
a63b05a9 1276 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1277 _clearup
1278 return 1
1279 fi
1280
6fc1447f 1281 waittimes=0
1282 if [[ -z "$MAX_RETRY_TIMES" ]] ; then
1283 MAX_RETRY_TIMES=30
1284 fi
1285
a63b05a9 1286 while [[ "1" ]] ; do
6fc1447f 1287 let "waittimes+=1"
1288 if [[ "$waittimes" -ge "$MAX_RETRY_TIMES" ]] ; then
1289 _err "$d:Timeout"
1290 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
1291 _clearup
1292 return 1
1293 fi
1294
4c3b3608 1295 _debug "sleep 5 secs to verify"
1296 sleep 5
1297 _debug "checking"
c60883ef 1298 response="$(_get $uri)"
a63b05a9 1299 if [[ "$?" != "0" ]] ; then
c60883ef 1300 _err "$d:Verify error:$response"
a63b05a9 1301 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1302 _clearup
1303 return 1
1304 fi
1305
7d91e35f 1306 status=$(echo $response | egrep -o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
a63b05a9 1307 if [[ "$status" == "valid" ]] ; then
4c3b3608 1308 _info "Success"
1309 _stopserver $serverproc
1310 serverproc=""
a63b05a9 1311 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1312 break;
1313 fi
1314
a63b05a9 1315 if [[ "$status" == "invalid" ]] ; then
4c3b3608 1316 error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4)
1317 _err "$d:Verify error:$error"
a63b05a9 1318 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1319 _clearup
1320 return 1;
1321 fi
1322
a63b05a9 1323 if [[ "$status" == "pending" ]] ; then
4c3b3608 1324 _info "Pending"
1325 else
1326 _err "$d:Verify error:$response"
a63b05a9 1327 _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
4c3b3608 1328 _clearup
1329 return 1
1330 fi
1331
1332 done
1333
1334 done
1335
1336 _clearup
1337 _info "Verify finished, start to sign."
fa8311dc 1338 der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _urlencode)"
4c3b3608 1339 _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64"
1340
1341
bbbdcb09 1342 Le_LinkCert="$(grep -i -o '^Location.*$' $HTTP_HEADER | head -1 | tr -d "\r\n" | cut -d " " -f 2)"
4c3b3608 1343 _setopt "$DOMAIN_CONF" "Le_LinkCert" "=" "$Le_LinkCert"
1344
a63b05a9 1345 if [[ "$Le_LinkCert" ]] ; then
88fab7d6 1346 echo "$BEGIN_CERT" > "$CERT_PATH"
c60883ef 1347 _get "$Le_LinkCert" | _base64 "multiline" >> "$CERT_PATH"
88fab7d6 1348 echo "$END_CERT" >> "$CERT_PATH"
4c3b3608 1349 _info "Cert success."
1350 cat "$CERT_PATH"
1351
1352 _info "Your cert is in $CERT_PATH"
caf1fc10 1353 cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
281aa349 1354
1355 if [[ ! "$USER_PATH" ]] || [[ ! "$IN_CRON" ]] ; then
1356 USER_PATH="$PATH"
1357 _saveaccountconf "USER_PATH" "$USER_PATH"
1358 fi
4c3b3608 1359 fi
1360
1361
a63b05a9 1362 if [[ -z "$Le_LinkCert" ]] ; then
88fab7d6 1363 response="$(echo $response | _dbase64 "multiline" )"
4c3b3608 1364 _err "Sign failed: $(echo "$response" | grep -o '"detail":"[^"]*"')"
1365 return 1
1366 fi
1367
1368 _setopt "$DOMAIN_CONF" 'Le_Vlist' '=' "\"\""
1369
bbbdcb09 1370 Le_LinkIssuer=$(grep -i '^Link' $HTTP_HEADER | head -1 | cut -d " " -f 2| cut -d ';' -f 1 | tr -d '<>' )
4c3b3608 1371 _setopt "$DOMAIN_CONF" "Le_LinkIssuer" "=" "$Le_LinkIssuer"
1372
a63b05a9 1373 if [[ "$Le_LinkIssuer" ]] ; then
88fab7d6 1374 echo "$BEGIN_CERT" > "$CA_CERT_PATH"
c60883ef 1375 _get "$Le_LinkIssuer" | _base64 "multiline" >> "$CA_CERT_PATH"
88fab7d6 1376 echo "$END_CERT" >> "$CA_CERT_PATH"
4c3b3608 1377 _info "The intermediate CA cert is in $CA_CERT_PATH"
caf1fc10 1378 cat "$CA_CERT_PATH" >> "$CERT_FULLCHAIN_PATH"
1379 _info "And the full chain certs is there: $CERT_FULLCHAIN_PATH"
4c3b3608 1380 fi
1381
1382 Le_CertCreateTime=$(date -u "+%s")
1383 _setopt "$DOMAIN_CONF" "Le_CertCreateTime" "=" "$Le_CertCreateTime"
1384
1385 Le_CertCreateTimeStr=$(date -u )
1386 _setopt "$DOMAIN_CONF" "Le_CertCreateTimeStr" "=" "\"$Le_CertCreateTimeStr\""
1387
06625071 1388 if [[ -z "$Le_RenewalDays" ]] || [[ "$Le_RenewalDays" -lt "0" ]] || [[ "$Le_RenewalDays" -gt "80" ]] ; then
4c3b3608 1389 Le_RenewalDays=80
1390 fi
1391
1392 _setopt "$DOMAIN_CONF" "Le_RenewalDays" "=" "$Le_RenewalDays"
1393
1394 let "Le_NextRenewTime=Le_CertCreateTime+Le_RenewalDays*24*60*60"
1395 _setopt "$DOMAIN_CONF" "Le_NextRenewTime" "=" "$Le_NextRenewTime"
1396
1397 Le_NextRenewTimeStr=$( _time2str $Le_NextRenewTime )
1398 _setopt "$DOMAIN_CONF" "Le_NextRenewTimeStr" "=" "\"$Le_NextRenewTimeStr\""
1399
1400
a63b05a9 1401 installcert $Le_Domain "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath"
4c3b3608 1402
1403}
1404
1405renew() {
1406 Le_Domain="$1"
a63b05a9 1407 if [[ -z "$Le_Domain" ]] ; then
a7b7355d 1408 _err "Usage: $PROJECT_ENTRY --renew -d domain.com"
4c3b3608 1409 return 1
1410 fi
1411
1412 _initpath $Le_Domain
1413
a63b05a9 1414 if [[ ! -f "$DOMAIN_CONF" ]] ; then
4c3b3608 1415 _info "$Le_Domain is not a issued domain, skip."
1416 return 0;
1417 fi
1418
1419 source "$DOMAIN_CONF"
a63b05a9 1420 if [[ -z "$FORCE" ]] && [[ "$Le_NextRenewTime" ]] && [[ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ]] ; then
4c3b3608 1421 _info "Skip, Next renewal time is: $Le_NextRenewTimeStr"
1422 return 2
1423 fi
1424
1425 IS_RENEW="1"
a63b05a9 1426 issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath"
4c3b3608 1427 local res=$?
1428 IS_RENEW=""
1429
1430 return $res
1431}
1432
1433renewAll() {
1434 _initpath
1435 _info "renewAll"
1436
b2817897 1437 for d in $(ls -F ${CERT_HOME}/ | grep [^.].*[.].*/$ ) ; do
4c3b3608 1438 d=$(echo $d | cut -d '/' -f 1)
1439 _info "renew $d"
1440
1441 Le_LinkCert=""
1442 Le_Domain=""
ed373617 1443 Le_Alt="no"
4c3b3608 1444 Le_Webroot=""
1445 Le_Keylength=""
1446 Le_LinkIssuer=""
1447
1448 Le_CertCreateTime=""
1449 Le_CertCreateTimeStr=""
1450 Le_RenewalDays=""
1451 Le_NextRenewTime=""
1452 Le_NextRenewTimeStr=""
1453
1454 Le_RealCertPath=""
1455 Le_RealKeyPath=""
1456
1457 Le_RealCACertPath=""
1458
1459 Le_ReloadCmd=""
a63b05a9 1460 Le_RealFullChainPath=""
4c3b3608 1461
1462 DOMAIN_PATH=""
1463 DOMAIN_CONF=""
1464 DOMAIN_SSL_CONF=""
1465 CSR_PATH=""
1466 CERT_KEY_PATH=""
1467 CERT_PATH=""
1468 CA_CERT_PATH=""
ac2d5123 1469 CERT_PFX_PATH=""
caf1fc10 1470 CERT_FULLCHAIN_PATH=""
4c3b3608 1471 ACCOUNT_KEY_PATH=""
1472
1473 wellknown_path=""
1474
1475 renew "$d"
1476 done
1477
1478}
1479
1480installcert() {
1481 Le_Domain="$1"
a63b05a9 1482 if [[ -z "$Le_Domain" ]] ; then
a7b7355d 1483 echo "Usage: $PROJECT_ENTRY --installcert -d domain.com [--certpath cert-file-path] [--keypath key-file-path] [--capath ca-cert-file-path] [ --reloadCmd reloadCmd] [--fullchainpath fullchain-path]"
4c3b3608 1484 return 1
1485 fi
1486
1487 Le_RealCertPath="$2"
1488 Le_RealKeyPath="$3"
1489 Le_RealCACertPath="$4"
1490 Le_ReloadCmd="$5"
a63b05a9 1491 Le_RealFullChainPath="$6"
4c3b3608 1492
1493 _initpath $Le_Domain
1494
1495 _setopt "$DOMAIN_CONF" "Le_RealCertPath" "=" "\"$Le_RealCertPath\""
1496 _setopt "$DOMAIN_CONF" "Le_RealCACertPath" "=" "\"$Le_RealCACertPath\""
1497 _setopt "$DOMAIN_CONF" "Le_RealKeyPath" "=" "\"$Le_RealKeyPath\""
1498 _setopt "$DOMAIN_CONF" "Le_ReloadCmd" "=" "\"$Le_ReloadCmd\""
a63b05a9 1499 _setopt "$DOMAIN_CONF" "Le_RealFullChainPath" "=" "\"$Le_RealFullChainPath\""
4c3b3608 1500
a63b05a9 1501 if [[ "$Le_RealCertPath" ]] ; then
1502 if [[ -f "$Le_RealCertPath" ]] ; then
4c3b3608 1503 cp -p "$Le_RealCertPath" "$Le_RealCertPath".bak
1504 fi
1505 cat "$CERT_PATH" > "$Le_RealCertPath"
1506 fi
1507
a63b05a9 1508 if [[ "$Le_RealCACertPath" ]] ; then
1509 if [[ "$Le_RealCACertPath" == "$Le_RealCertPath" ]] ; then
4c3b3608 1510 echo "" >> "$Le_RealCACertPath"
1511 cat "$CA_CERT_PATH" >> "$Le_RealCACertPath"
1512 else
a63b05a9 1513 if [[ -f "$Le_RealCACertPath" ]] ; then
78552b18
B
1514 cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak
1515 fi
4c3b3608 1516 cat "$CA_CERT_PATH" > "$Le_RealCACertPath"
1517 fi
1518 fi
1519
1520
a63b05a9 1521 if [[ "$Le_RealKeyPath" ]] ; then
1522 if [[ -f "$Le_RealKeyPath" ]] ; then
4c3b3608 1523 cp -p "$Le_RealKeyPath" "$Le_RealKeyPath".bak
1524 fi
1525 cat "$CERT_KEY_PATH" > "$Le_RealKeyPath"
1526 fi
a63b05a9 1527
1528 if [[ "$Le_RealFullChainPath" ]] ; then
1529 if [[ -f "$Le_RealFullChainPath" ]] ; then
1530 cp -p "$Le_RealFullChainPath" "$Le_RealFullChainPath".bak
1531 fi
1532 cat "$CERT_FULLCHAIN_PATH" > "$Le_RealFullChainPath"
1533 fi
4c3b3608 1534
a63b05a9 1535 if [[ "$Le_ReloadCmd" ]] ; then
4c3b3608 1536 _info "Run Le_ReloadCmd: $Le_ReloadCmd"
1537 (cd "$DOMAIN_PATH" && eval "$Le_ReloadCmd")
1538 fi
1539
1540}
1541
1542installcronjob() {
1543 _initpath
77546ea5 1544 if ! _exists "crontab" ; then
1545 _err "crontab doesn't exist, so, we can not install cron jobs."
1546 _err "All your certs will not be renewed automatically."
a7b7355d 1547 _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' everyday."
77546ea5 1548 return 1
1549 fi
1550
4c3b3608 1551 _info "Installing cron job"
a7b7355d 1552 if ! crontab -l | grep "$PROJECT_ENTRY --cron" ; then
1553 if [[ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]] ; then
1554 lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY"
4c3b3608 1555 else
a7b7355d 1556 _err "Can not install cronjob, $PROJECT_ENTRY not found."
4c3b3608 1557 return 1
1558 fi
a7b7355d 1559 crontab -l | { cat; echo "0 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null"; } | crontab -
4c3b3608 1560 fi
a63b05a9 1561 if [[ "$?" != "0" ]] ; then
4c3b3608 1562 _err "Install cron job failed. You need to manually renew your certs."
1563 _err "Or you can add cronjob by yourself:"
a7b7355d 1564 _err "$lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null"
4c3b3608 1565 return 1
1566 fi
1567}
1568
1569uninstallcronjob() {
37db5b81 1570 if ! _exists "crontab" ; then
1571 return
1572 fi
4c3b3608 1573 _info "Removing cron job"
a7b7355d 1574 cr="$(crontab -l | grep "$PROJECT_ENTRY --cron")"
a63b05a9 1575 if [[ "$cr" ]] ; then
a7b7355d 1576 crontab -l | sed "/$PROJECT_ENTRY --cron/d" | crontab -
1577 LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')"
4c3b3608 1578 _info LE_WORKING_DIR "$LE_WORKING_DIR"
1579 fi
1580 _initpath
a7b7355d 1581
4c3b3608 1582}
1583
6cb415f5 1584revoke() {
1585 Le_Domain="$1"
a63b05a9 1586 if [[ -z "$Le_Domain" ]] ; then
a7b7355d 1587 echo "Usage: $PROJECT_ENTRY --revoke -d domain.com"
6cb415f5 1588 return 1
1589 fi
1590
1591 _initpath $Le_Domain
a63b05a9 1592 if [[ ! -f "$DOMAIN_CONF" ]] ; then
6cb415f5 1593 _err "$Le_Domain is not a issued domain, skip."
1594 return 1;
1595 fi
1596
a63b05a9 1597 if [[ ! -f "$CERT_PATH" ]] ; then
6cb415f5 1598 _err "Cert for $Le_Domain $CERT_PATH is not found, skip."
1599 return 1
1600 fi
1601
1602 cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}"| tr -d "\r\n" | _urlencode)"
1603
a63b05a9 1604 if [[ -z "$cert" ]] ; then
6cb415f5 1605 _err "Cert for $Le_Domain is empty found, skip."
1606 return 1
1607 fi
1608
1609 data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
1610 uri="$API/acme/revoke-cert"
1611
1612 _info "Try domain key first."
1613 if _send_signed_request $uri "$data" "" "$CERT_KEY_PATH"; then
a63b05a9 1614 if [[ -z "$response" ]] ; then
6cb415f5 1615 _info "Revoke success."
1616 rm -f $CERT_PATH
1617 return 0
1618 else
1619 _err "Revoke error by domain key."
1620 _err "$resource"
1621 fi
1622 fi
1623
1624 _info "Then try account key."
1625
1626 if _send_signed_request $uri "$data" "" "$ACCOUNT_KEY_PATH" ; then
a63b05a9 1627 if [[ -z "$response" ]] ; then
6cb415f5 1628 _info "Revoke success."
1629 rm -f $CERT_PATH
1630 return 0
1631 else
1632 _err "Revoke error."
1633 _debug "$resource"
1634 fi
1635 fi
1636 return 1
1637}
4c3b3608 1638
1639# Detect profile file if not specified as environment variable
1640_detect_profile() {
a63b05a9 1641 if [ -n "$PROFILE" -a -f "$PROFILE" ] ; then
4c3b3608 1642 echo "$PROFILE"
1643 return
1644 fi
1645
1646 local DETECTED_PROFILE
1647 DETECTED_PROFILE=''
1648 local SHELLTYPE
1649 SHELLTYPE="$(basename "/$SHELL")"
1650
a63b05a9 1651 if [[ "$SHELLTYPE" = "bash" ]] ; then
1652 if [[ -f "$HOME/.bashrc" ]] ; then
4c3b3608 1653 DETECTED_PROFILE="$HOME/.bashrc"
a63b05a9 1654 elif [[ -f "$HOME/.bash_profile" ]] ; then
4c3b3608 1655 DETECTED_PROFILE="$HOME/.bash_profile"
1656 fi
a63b05a9 1657 elif [[ "$SHELLTYPE" = "zsh" ]] ; then
4c3b3608 1658 DETECTED_PROFILE="$HOME/.zshrc"
1659 fi
1660
a63b05a9 1661 if [[ -z "$DETECTED_PROFILE" ]] ; then
1662 if [[ -f "$HOME/.profile" ]] ; then
4c3b3608 1663 DETECTED_PROFILE="$HOME/.profile"
a63b05a9 1664 elif [[ -f "$HOME/.bashrc" ]] ; then
4c3b3608 1665 DETECTED_PROFILE="$HOME/.bashrc"
a63b05a9 1666 elif [[ -f "$HOME/.bash_profile" ]] ; then
4c3b3608 1667 DETECTED_PROFILE="$HOME/.bash_profile"
a63b05a9 1668 elif [[ -f "$HOME/.zshrc" ]] ; then
4c3b3608 1669 DETECTED_PROFILE="$HOME/.zshrc"
1670 fi
1671 fi
1672
a63b05a9 1673 if [[ ! -z "$DETECTED_PROFILE" ]] ; then
4c3b3608 1674 echo "$DETECTED_PROFILE"
1675 fi
1676}
1677
1678_initconf() {
1679 _initpath
a63b05a9 1680 if [[ ! -f "$ACCOUNT_CONF_PATH" ]] ; then
d53289d7 1681 echo "#ACCOUNT_CONF_PATH=xxxx
1682
1683#Account configurations:
4c3b3608 1684#Here are the supported macros, uncomment them to make them take effect.
d53289d7 1685
4c3b3608 1686#ACCOUNT_EMAIL=aaa@aaa.com # the account email used to register account.
5fd3f21b 1687#ACCOUNT_KEY_PATH=\"/path/to/account.key\"
b2817897 1688#CERT_HOME=\"/path/to/cert/home\"
4c3b3608 1689
1690#STAGE=1 # Use the staging api
1691#FORCE=1 # Force to issue cert
1692#DEBUG=1 # Debug mode
1693
166096dc 1694#ACCOUNT_KEY_HASH=account key hash
1695
635695ec 1696USER_AGENT=\"$USER_AGENT\"
281aa349 1697
1698#USER_PATH=""
1699
4c3b3608 1700#dns api
1701#######################
1702#Cloudflare:
1703#api key
3d49985a 1704#CF_Key=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"
4c3b3608 1705#account email
3d49985a 1706#CF_Email=\"xxxx@sss.com\"
4c3b3608 1707
1708#######################
1709#Dnspod.cn:
1710#api key id
3d49985a 1711#DP_Id=\"1234\"
4c3b3608 1712#api key
3d49985a 1713#DP_Key=\"sADDsdasdgdsf\"
4c3b3608 1714
1715#######################
1716#Cloudxns.com:
3d49985a 1717#CX_Key=\"1234\"
4c3b3608 1718#
3d49985a 1719#CX_Secret=\"sADDsdasdgdsf\"
4c3b3608 1720
1721 " > $ACCOUNT_CONF_PATH
1722 fi
1723}
1724
c60883ef 1725_precheck() {
1726 if ! _exists "curl" && ! _exists "wget"; then
1727 _err "Please install curl or wget first, we need to access http resources."
4c3b3608 1728 return 1
1729 fi
1730
c60883ef 1731 if ! _exists "crontab" ; then
77546ea5 1732 _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'."
c60883ef 1733 _err "We need to set cron job to renew the certs automatically."
77546ea5 1734 _err "Otherwise, your certs will not be able to be renewed automatically."
a63b05a9 1735 if [[ -z "$FORCE" ]] ; then
a7b7355d 1736 _err "Please add '--force' and try install again to go without crontab."
1737 _err "./$PROJECT_ENTRY --install --force"
77546ea5 1738 return 1
1739 fi
4c3b3608 1740 fi
1741
c60883ef 1742 if ! _exists "openssl" ; then
1743 _err "Please install openssl first."
1744 _err "We need openssl to generate keys."
4c3b3608 1745 return 1
1746 fi
1747
c60883ef 1748 if ! _exists "nc" ; then
1749 _err "It is recommended to install nc first, try to install 'nc' or 'netcat'."
1750 _err "We use nc for standalone server if you use standalone mode."
1751 _err "If you don't use standalone mode, just ignore this warning."
1752 fi
1753
1754 return 0
1755}
1756
1757install() {
635695ec 1758
c60883ef 1759 if ! _initpath ; then
1760 _err "Install failed."
4c3b3608 1761 return 1
1762 fi
635695ec 1763
c60883ef 1764 if ! _precheck ; then
1765 _err "Pre-check failed, can not install."
4c3b3608 1766 return 1
1767 fi
c60883ef 1768
6cc11ffb 1769 #convert from le
1770 if [[ -d "$HOME/.le" ]] ; then
1771 for envfile in "le.env" "le.sh.env"
1772 do
1773 if [[ -f "$HOME/.le/$envfile" ]] ; then
1774 if grep "le.sh" "$HOME/.le/$envfile" >/dev/null ; then
1775 _upgrading="1"
1776 _info "You are upgrading from le.sh"
1777 _info "Renaming \"$HOME/.le\" to $LE_WORKING_DIR"
1778 mv "$HOME/.le" "$LE_WORKING_DIR"
1779 mv "$LE_WORKING_DIR/$envfile" "$LE_WORKING_DIR/$PROJECT_ENTRY.env"
1780 break;
1781 fi
1782 fi
1783 done
1784 fi
1785
4c3b3608 1786 _info "Installing to $LE_WORKING_DIR"
635695ec 1787
4a0f23e2 1788 if ! mkdir -p "$LE_WORKING_DIR" ; then
1789 _err "Can not craete working dir: $LE_WORKING_DIR"
1790 return 1
1791 fi
1792
a7b7355d 1793 cp $PROJECT_ENTRY "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY"
4c3b3608 1794
a63b05a9 1795 if [[ "$?" != "0" ]] ; then
a7b7355d 1796 _err "Install failed, can not copy $PROJECT_ENTRY"
4c3b3608 1797 return 1
1798 fi
1799
a7b7355d 1800 _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
4c3b3608 1801
1802 _profile="$(_detect_profile)"
a63b05a9 1803 if [[ "$_profile" ]] ; then
4c3b3608 1804 _debug "Found profile: $_profile"
1805
6cc11ffb 1806 _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env"
1807 if [[ "$_upgrading" == "1" ]] ; then
1808 echo "$(cat $_envfile)" | sed "s|^LE_WORKING_DIR.*$||" > "$_envfile"
1809 echo "$(cat $_envfile)" | sed "s|^alias le.*$||" > "$_envfile"
1810 echo "$(cat $_envfile)" | sed "s|^alias le.sh.*$||" > "$_envfile"
1811 fi
1812
1813 _setopt "$_envfile" "LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\""
1814 _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY\""
1815
b86869a0 1816 echo "" >> "$_profile"
a7b7355d 1817 _setopt "$_profile" "source \"$LE_WORKING_DIR/$PROJECT_NAME.env\""
1818 _info "OK, Close and reopen your terminal to start using $PROJECT_NAME"
4c3b3608 1819 else
a7b7355d 1820 _info "No profile is found, you will need to go into $LE_WORKING_DIR to use $PROJECT_NAME"
4c3b3608 1821 fi
1822
1823 mkdir -p $LE_WORKING_DIR/dnsapi
1824 cp dnsapi/* $LE_WORKING_DIR/dnsapi/
1825
1826 #to keep compatible mv the .acc file to .key file
a63b05a9 1827 if [[ -f "$LE_WORKING_DIR/account.acc" ]] ; then
4c3b3608 1828 mv "$LE_WORKING_DIR/account.acc" "$LE_WORKING_DIR/account.key"
1829 fi
d53289d7 1830
a63b05a9 1831 if [[ ! -f "$ACCOUNT_CONF_PATH" ]] ; then
4c3b3608 1832 _initconf
1833 fi
6cc11ffb 1834
1835 if [[ "$_DEFAULT_ACCOUNT_CONF_PATH" != "$ACCOUNT_CONF_PATH" ]] ; then
635695ec 1836 _setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
6cc11ffb 1837 fi
1838
b2817897 1839 if [[ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]] ; then
1840 _saveaccountconf "CERT_HOME" "$CERT_HOME"
1841 fi
1842
1843 if [[ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]] ; then
1844 _saveaccountconf "ACCOUNT_KEY_PATH" "$ACCOUNT_KEY_PATH"
1845 fi
1846
d53289d7 1847 installcronjob
1848
4c3b3608 1849 _info OK
1850}
1851
1852uninstall() {
1853 uninstallcronjob
1854 _initpath
1855
1856 _profile="$(_detect_profile)"
a63b05a9 1857 if [[ "$_profile" ]] ; then
7203a1c1 1858 text="$(cat $_profile)"
a7b7355d 1859 echo "$text" | sed "s|^source.*$PROJECT_NAME.env.*$||" > "$_profile"
4c3b3608 1860 fi
1861
a7b7355d 1862 rm -f $LE_WORKING_DIR/$PROJECT_ENTRY
4c3b3608 1863 _info "The keys and certs are in $LE_WORKING_DIR, you can remove them by yourself."
1864
1865}
1866
1867cron() {
281aa349 1868 IN_CRON=1
4c3b3608 1869 renewAll
281aa349 1870 IN_CRON=""
4c3b3608 1871}
1872
1873version() {
a63b05a9 1874 echo "$PROJECT"
1875 echo "v$VER"
4c3b3608 1876}
1877
1878showhelp() {
1879 version
a7b7355d 1880 echo "Usage: $PROJECT_ENTRY command ...[parameters]....
a63b05a9 1881Commands:
1882 --help, -h Show this help message.
1883 --version, -v Show version info.
a7b7355d 1884 --install Install $PROJECT_NAME to your system.
1885 --uninstall Uninstall $PROJECT_NAME, and uninstall the cron job.
a63b05a9 1886 --issue Issue a cert.
1887 --installcert Install the issued cert to apache/nginx or any other server.
1888 --renew, -r Renew a cert.
1889 --renewAll Renew all the certs
1890 --revoke Revoke a cert.
1891 --installcronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
1892 --uninstallcronjob Uninstall the cron job. The 'uninstall' command can do this automatically.
1893 --cron Run cron job to renew all the certs.
1894 --toPkcs Export the certificate and key to a pfx file.
1895 --createAccountKey, -cak Create an account private key, professional use.
1896 --createDomainKey, -cdk Create an domain private key, professional use.
1897 --createCSR, -ccsr Create CSR , professional use.
1898
1899Parameters:
1900 --domain, -d domain.tld Specifies a domain, used to issue, renew or revoke etc.
1901 --force, -f Used to force to install or force to renew a cert immediately.
1902 --staging, --test Use staging server, just for test.
1903 --debug Output debug info.
1904
1905 --webroot, -w /path/to/webroot Specifies the web root folder for web root mode.
1906 --standalone Use standalone mode.
1907 --apache Use apache mode.
1908 --dns [dns-cf|dns-dp|dns-cx|/path/to/api/file] Use dns mode or dns api.
1909
1910 --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384.
1911 --accountkeylength, -ak [2048] Specifies the account key length.
1912
1913 These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert:
1914
1915 --certpath /path/to/real/cert/file After issue/renew, the cert will be copied to this path.
1916 --keypath /path/to/real/key/file After issue/renew, the key will be copied to this path.
1917 --capath /path/to/real/ca/file After issue/renew, the intermediate cert will be copied to this path.
1918 --fullchainpath /path/to/fullchain/file After issue/renew, the fullchain cert will be copied to this path.
1919
1920 --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
1921
1922 --accountconf Specifies a customized account config file.
635695ec 1923 --home Specifies the home dir for $PROJECT_NAME .
b2817897 1924 --certhome Specifies the home dir to save all the certs.
635695ec 1925 --useragent Specifies the user agent string. it will be saved for future use too.
b5eb4b90 1926 --accountemail Specifies the account email for registering, Only valid for the '--install' command.
06625071 1927 --accountkey Specifies the account key path, Only valid for the '--install' command.
1928 --days Specifies the days to renew the cert when using '--issue' command. The max value is 80 days.
a63b05a9 1929
4c3b3608 1930 "
1931}
1932
4a0f23e2 1933_installOnline() {
1934 _info "Installing from online archive."
a63b05a9 1935 if [[ ! "$BRANCH" ]] ; then
4a0f23e2 1936 BRANCH="master"
1937 fi
1938 _initpath
1939 target="$PROJECT/archive/$BRANCH.tar.gz"
1940 _info "Downloading $target"
1941 localname="$BRANCH.tar.gz"
1942 if ! _get "$target" > $localname ; then
1943 _debug "Download error."
1944 return 1
1945 fi
1946 _info "Extracting $localname"
1947 tar xzf $localname
6cc11ffb 1948 cd "$PROJECT_NAME-$BRANCH"
a7b7355d 1949 chmod +x $PROJECT_ENTRY
1950 if ./$PROJECT_ENTRY install ; then
4a0f23e2 1951 _info "Install success!"
1952 fi
1953
1954 cd ..
6cc11ffb 1955 rm -rf "$PROJECT_NAME-$BRANCH"
4a0f23e2 1956 rm -f "$localname"
1957}
1958
a63b05a9 1959
1960_process() {
1961 _CMD=""
1962 _domain=""
1963 _altdomains="no"
1964 _webroot=""
1965 _keylength="no"
1966 _accountkeylength="no"
1967 _certpath="no"
1968 _keypath="no"
1969 _capath="no"
1970 _fullchainpath="no"
1971 _reloadcmd="no"
1972 _password=""
635695ec 1973 _accountconf=""
1974 _useragent=""
b5eb4b90 1975 _accountemail=""
1976 _accountkey=""
b2817897 1977 _certhome=""
a63b05a9 1978 while (( ${#} )); do
1979 case "${1}" in
1980
1981 --help|-h)
1982 showhelp
1983 return
1984 ;;
1985 --version|-v)
1986 version
1987 return
1988 ;;
1989 --install)
1990 _CMD="install"
1991 ;;
1992 --uninstall)
1993 _CMD="uninstall"
1994 ;;
1995 --issue)
1996 _CMD="issue"
1997 ;;
1998 --installcert|-i)
1999 _CMD="installcert"
2000 ;;
2001 --renew|-r)
2002 _CMD="renew"
2003 ;;
2004 --renewAll|-renewall)
2005 _CMD="renewAll"
2006 ;;
2007 --revoke)
2008 _CMD="revoke"
2009 ;;
2010 --installcronjob)
2011 _CMD="installcronjob"
2012 ;;
2013 --uninstallcronjob)
2014 _CMD="uninstallcronjob"
2015 ;;
2016 --cron)
2017 _CMD="cron"
2018 ;;
2019 --toPkcs)
2020 _CMD="toPkcs"
2021 ;;
2022 --createAccountKey|--createaccountkey|-cak)
2023 _CMD="createAccountKey"
2024 ;;
2025 --createDomainKey|--createdomainkey|-cdk)
2026 _CMD="createDomainKey"
2027 ;;
2028 --createCSR|--createcsr|-ccr)
2029 _CMD="createCSR"
2030 ;;
2031
2032
2033 --domain|-d)
2034 _dvalue="$2"
2035
2036 if [[ -z "$_dvalue" ]] || [[ "$_dvalue" == "-"* ]] ; then
2037 _err "'$_dvalue' is not a valid domain for parameter '$1'"
2038 return 1
2039 fi
2040
2041 if [[ -z "$_domain" ]] ; then
2042 _domain="$_dvalue"
2043 else
2044 if [[ "$_altdomains" == "no" ]] ; then
2045 _altdomains="$_dvalue"
2046 else
2047 _altdomains="$_altdomains,$_dvalue"
2048 fi
2049 fi
2050 shift
2051 ;;
2052
2053 --force|-f)
2054 FORCE="1"
2055 ;;
2056 --staging|--test)
2057 STAGE="1"
2058 ;;
2059 --debug)
6fc1447f 2060 if [[ "$2" == "-"* ]] || [[ -z "$2" ]]; then
a63b05a9 2061 DEBUG="1"
2062 else
2063 DEBUG="$2"
2064 shift
6fc1447f 2065 fi
a63b05a9 2066 ;;
a63b05a9 2067 --webroot|-w)
2068 wvalue="$2"
2069 if [[ -z "$_webroot" ]] ; then
2070 _webroot="$wvalue"
2071 else
2072 _webroot="$_webroot,$wvalue"
2073 fi
2074 shift
2075 ;;
2076 --standalone)
2077 wvalue="no"
2078 if [[ -z "$_webroot" ]] ; then
2079 _webroot="$wvalue"
2080 else
2081 _webroot="$_webroot,$wvalue"
2082 fi
2083 ;;
2084 --apache)
2085 wvalue="apache"
2086 if [[ -z "$_webroot" ]] ; then
2087 _webroot="$wvalue"
2088 else
2089 _webroot="$_webroot,$wvalue"
2090 fi
2091 ;;
2092 --dns)
2093 wvalue="dns"
2094 if [[ "$2" != "-"* ]] ; then
2095 wvalue="$2"
2096 shift
2097 fi
2098 if [[ -z "$_webroot" ]] ; then
2099 _webroot="$wvalue"
2100 else
2101 _webroot="$_webroot,$wvalue"
2102 fi
2103 ;;
2104 --keylength|-k)
2105 _keylength="$2"
2106 accountkeylength="$2"
2107 shift
2108 ;;
2109 --accountkeylength|-ak)
2110 accountkeylength="$2"
2111 shift
2112 ;;
2113
2114 --certpath)
2115 _certpath="$2"
2116 shift
2117 ;;
2118 --keypath)
2119 _keypath="$2"
2120 shift
2121 ;;
2122 --capath)
2123 _capath="$2"
2124 shift
2125 ;;
2126 --fullchainpath)
2127 _fullchainpath="$2"
2128 shift
2129 ;;
635695ec 2130 --reloadcmd|--reloadCmd)
a63b05a9 2131 _reloadcmd="$2"
2132 shift
2133 ;;
2134 --password)
2135 _password="$2"
2136 shift
2137 ;;
2138 --accountconf)
635695ec 2139 _accountconf="$2"
2140 ACCOUNT_CONF_PATH="$_accountconf"
a7b7355d 2141 shift
a63b05a9 2142 ;;
a7b7355d 2143 --home)
a63b05a9 2144 LE_WORKING_DIR="$2"
a7b7355d 2145 shift
a63b05a9 2146 ;;
b2817897 2147 --certhome)
2148 _certhome="$2"
2149 CERT_HOME="$_certhome"
2150 shift
2151 ;;
635695ec 2152 --useragent)
2153 _useragent="$2"
2154 USER_AGENT="$_useragent"
2155 shift
2156 ;;
b5eb4b90 2157 --accountemail )
2158 _accountemail="$2"
2159 ACCOUNT_EMAIL="$_accountemail"
2160 shift
2161 ;;
2162 --accountkey )
2163 _accountkey="$2"
2164 ACCOUNT_KEY_PATH="$_accountkey"
2165 shift
2166 ;;
06625071 2167 --days )
2168 _days="$2"
2169 Le_RenewalDays="$_days"
2170 shift
2171 ;;
a63b05a9 2172 *)
2173 _err "Unknown parameter : $1"
2174 return 1
2175 ;;
2176 esac
2177
2178 shift 1
2179 done
2180
2181
2182 case "${_CMD}" in
2183 install) install ;;
2184 uninstall) uninstall ;;
2185 issue)
70a55875 2186 issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath"
a63b05a9 2187 ;;
2188 installcert)
3ed4102a 2189 installcert "$_domain" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath"
a63b05a9 2190 ;;
2191 renew)
2192 renew "$_domain"
2193 ;;
2194 renewAll)
2195 renewAll
2196 ;;
2197 revoke)
2198 revoke "$_domain"
2199 ;;
2200 installcronjob) installcronjob ;;
2201 uninstallcronjob) uninstallcronjob ;;
2202 cron) cron ;;
2203 toPkcs)
2204 toPkcs "$_domain" "$_password"
2205 ;;
2206 createAccountKey)
2207 createAccountKey "$_domain" "$_accountkeylength"
2208 ;;
2209 createDomainKey)
2210 createDomainKey "$_domain" "$_keylength"
2211 ;;
2212 createCSR)
2213 createCSR "$_domain" "$_altdomains"
2214 ;;
2215
2216 *)
2217 _err "Invalid command: $_CMD"
2218 showhelp;
2219 return 1
2220 ;;
2221 esac
2222
635695ec 2223 if [[ "$_useragent" ]] ; then
2224 _saveaccountconf "USER_AGENT" "$_useragent"
2225 fi
b5eb4b90 2226 if [[ "$_accountemail" ]] ; then
2227 _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail"
2228 fi
b2817897 2229
635695ec 2230
a63b05a9 2231}
2232
2233
2234if [[ "$INSTALLONLINE" ]] ; then
d1f97fc8 2235 INSTALLONLINE=""
4a0f23e2 2236 _installOnline $BRANCH
2237 exit
2238fi
4c3b3608 2239
a63b05a9 2240if [[ -z "$1" ]] ; then
4c3b3608 2241 showhelp
2242else
a63b05a9 2243 if [[ "$1" == "-"* ]] ; then
2244 _process "$@"
2245 else
2246 "$@"
2247 fi
4c3b3608 2248fi
a63b05a9 2249
2250