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