Merge pull request #8361 from jandelgado/add_udptunnel_package
[feed/packages.git] / net / acme / files / run.sh
1 #!/bin/sh
2 # Wrapper for acme.sh to work on openwrt.
3 #
4 # This program is free software; you can redistribute it and/or modify it under
5 # the terms of the GNU General Public License as published by the Free Software
6 # Foundation; either version 3 of the License, or (at your option) any later
7 # version.
8 #
9 # Author: Toke Høiland-Jørgensen <toke@toke.dk>
10
11 CHECK_CRON=$1
12 ACME=/usr/lib/acme/acme.sh
13 export CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
14 export NO_TIMESTAMP=1
15
16 UHTTPD_LISTEN_HTTP=
17 STATE_DIR='/etc/acme'
18 ACCOUNT_EMAIL=
19 DEBUG=0
20 NGINX_WEBSERVER=0
21 UPDATE_NGINX=0
22 UPDATE_UHTTPD=0
23
24 . /lib/functions.sh
25
26 check_cron()
27 {
28 [ -f "/etc/crontabs/root" ] && grep -q '/etc/init.d/acme' /etc/crontabs/root && return
29 echo "0 0 * * * /etc/init.d/acme start" >> /etc/crontabs/root
30 /etc/init.d/cron start
31 }
32
33 log()
34 {
35 logger -t acme -s -p daemon.info "$@"
36 }
37
38 err()
39 {
40 logger -t acme -s -p daemon.err "$@"
41 }
42
43 debug()
44 {
45 [ "$DEBUG" -eq "1" ] && logger -t acme -s -p daemon.debug "$@"
46 }
47
48 get_listeners() {
49 local proto rq sq listen remote state program
50 netstat -nptl 2>/dev/null | while read proto rq sq listen remote state program; do
51 case "$proto#$listen#$program" in
52 tcp#*:80#[0-9]*/*) echo -n "${program%% *} " ;;
53 esac
54 done
55 }
56
57 pre_checks()
58 {
59 main_domain="$1"
60
61 log "Running pre checks for $main_domain."
62
63 listeners="$(get_listeners)"
64
65 debug "port80 listens: $listeners"
66
67 for listener in $(get_listeners); do
68 pid="${listener%/*}"
69 cmd="${listener#*/}"
70
71 case "$cmd" in
72 uhttpd)
73 debug "Found uhttpd listening on port 80; trying to disable."
74
75 UHTTPD_LISTEN_HTTP=$(uci get uhttpd.main.listen_http)
76
77 if [ -z "$UHTTPD_LISTEN_HTTP" ]; then
78 err "$main_domain: Unable to find uhttpd listen config."
79 err "Manually disable uhttpd or set webroot to continue."
80 return 1
81 fi
82
83 uci set uhttpd.main.listen_http=''
84 uci commit uhttpd || return 1
85 if ! /etc/init.d/uhttpd reload ; then
86 uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP"
87 uci commit uhttpd
88 return 1
89 fi
90 ;;
91 nginx*)
92 debug "Found nginx listening on port 80; trying to disable."
93 NGINX_WEBSERVER=1
94 local tries=0
95 while grep -sq "$cmd" "/proc/$pid/cmdline" && kill -0 "$pid"; do
96 /etc/init.d/nginx stop
97 if [ $tries -gt 10 ]; then
98 debug "Can't stop nginx. Terminating script."
99 return 1
100 fi
101 debug "Waiting for nginx to stop..."
102 tries=$((tries + 1))
103 sleep 1
104 done
105 ;;
106 "")
107 debug "Nothing listening on port 80."
108 ;;
109 *)
110 err "$main_domain: Cannot run in standalone mode; another daemon is listening on port 80."
111 err "Disable other daemon or set webroot to continue."
112 return 1
113 ;;
114 esac
115 done
116
117 iptables -I input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" || return 1
118 ip6tables -I input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" || return 1
119 debug "v4 input_rule: $(iptables -nvL input_rule)"
120 debug "v6 input_rule: $(ip6tables -nvL input_rule)"
121 return 0
122 }
123
124 post_checks()
125 {
126 log "Running post checks (cleanup)."
127 # The comment ensures we only touch our own rules. If no rules exist, that
128 # is fine, so hide any errors
129 iptables -D input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" 2>/dev/null
130 ip6tables -D input_rule -p tcp --dport 80 -j ACCEPT -m comment --comment "ACME" 2>/dev/null
131
132 if [ -e /etc/init.d/uhttpd ] && ( [ -n "$UHTTPD_LISTEN_HTTP" ] || [ "$UPDATE_UHTTPD" -eq 1 ] ); then
133 if [ -n "$UHTTPD_LISTEN_HTTP" ]; then
134 uci set uhttpd.main.listen_http="$UHTTPD_LISTEN_HTTP"
135 UHTTPD_LISTEN_HTTP=
136 fi
137 uci commit uhttpd
138 /etc/init.d/uhttpd reload
139 fi
140
141 if [ -e /etc/init.d/nginx ] && ( [ "$NGINX_WEBSERVER" -eq 1 ] || [ "$UPDATE_NGINX" -eq 1 ] ); then
142 NGINX_WEBSERVER=0
143 /etc/init.d/nginx restart
144 fi
145 }
146
147 err_out()
148 {
149 post_checks
150 exit 1
151 }
152
153 int_out()
154 {
155 post_checks
156 trap - INT
157 kill -INT $$
158 }
159
160 is_staging()
161 {
162 local main_domain="$1"
163
164 grep -q "acme-staging" "$STATE_DIR/$main_domain/${main_domain}.conf"
165 return $?
166 }
167
168 issue_cert()
169 {
170 local section="$1"
171 local acme_args=
172 local enabled
173 local use_staging
174 local update_uhttpd
175 local update_nginx
176 local keylength
177 local domains
178 local main_domain
179 local moved_staging=0
180 local failed_dir
181 local webroot
182 local dns
183 local ret
184
185 config_get_bool enabled "$section" enabled 0
186 config_get_bool use_staging "$section" use_staging
187 config_get_bool update_uhttpd "$section" update_uhttpd
188 config_get_bool update_nginx "$section" update_nginx
189 config_get domains "$section" domains
190 config_get keylength "$section" keylength
191 config_get webroot "$section" webroot
192 config_get dns "$section" dns
193
194 UPDATE_NGINX=$update_nginx
195 UPDATE_UHTTPD=$update_uhttpd
196
197 [ "$enabled" -eq "1" ] || return
198
199 [ "$DEBUG" -eq "1" ] && acme_args="$acme_args --debug"
200
201 set -- $domains
202 main_domain=$1
203
204 [ -n "$webroot" ] || [ -n "$dns" ] || pre_checks "$main_domain" || return 1
205
206 log "Running ACME for $main_domain"
207
208 handle_credentials() {
209 local credential="$1"
210 eval export $credential
211 }
212 config_list_foreach "$section" credentials handle_credentials
213
214 if [ -e "$STATE_DIR/$main_domain" ]; then
215 if [ "$use_staging" -eq "0" ] && is_staging "$main_domain"; then
216 log "Found previous cert issued using staging server. Moving it out of the way."
217 mv "$STATE_DIR/$main_domain" "$STATE_DIR/$main_domain.staging"
218 moved_staging=1
219 else
220 log "Found previous cert config. Issuing renew."
221 $ACME --home "$STATE_DIR" --renew -d "$main_domain" $acme_args && ret=0 || ret=1
222 post_checks
223 return $ret
224 fi
225 fi
226
227
228 acme_args="$acme_args $(for d in $domains; do echo -n "-d $d "; done)"
229 acme_args="$acme_args --keylength $keylength"
230 [ -n "$ACCOUNT_EMAIL" ] && acme_args="$acme_args --accountemail $ACCOUNT_EMAIL"
231 [ "$use_staging" -eq "1" ] && acme_args="$acme_args --staging"
232
233 if [ -n "$dns" ]; then
234 log "Using dns mode"
235 acme_args="$acme_args --dns $dns"
236 elif [ -z "$webroot" ]; then
237 log "Using standalone mode"
238 acme_args="$acme_args --standalone --listen-v6"
239 else
240 if [ ! -d "$webroot" ]; then
241 err "$main_domain: Webroot dir '$webroot' does not exist!"
242 post_checks
243 return 1
244 fi
245 log "Using webroot dir: $webroot"
246 acme_args="$acme_args --webroot $webroot"
247 fi
248
249 if ! $ACME --home "$STATE_DIR" --issue $acme_args; then
250 failed_dir="$STATE_DIR/${main_domain}.failed-$(date +%s)"
251 err "Issuing cert for $main_domain failed. Moving state to $failed_dir"
252 [ -d "$STATE_DIR/$main_domain" ] && mv "$STATE_DIR/$main_domain" "$failed_dir"
253 if [ "$moved_staging" -eq "1" ]; then
254 err "Restoring staging certificate"
255 mv "$STATE_DIR/${main_domain}.staging" "$STATE_DIR/${main_domain}"
256 fi
257 post_checks
258 return 1
259 fi
260
261 if [ -e /etc/init.d/uhttpd ] && [ "$update_uhttpd" -eq "1" ]; then
262 uci set uhttpd.main.key="$STATE_DIR/${main_domain}/${main_domain}.key"
263 uci set uhttpd.main.cert="$STATE_DIR/${main_domain}/fullchain.cer"
264 # commit and reload is in post_checks
265 fi
266
267 if [ -e /etc/init.d/nginx ] && [ "$update_nginx" -eq "1" ]; then
268 sed -i "s#ssl_certificate\ .*#ssl_certificate $STATE_DIR/${main_domain}/fullchain.cer;#g" /etc/nginx/nginx.conf
269 sed -i "s#ssl_certificate_key\ .*#ssl_certificate_key $STATE_DIR/${main_domain}/${main_domain}.key;#g" /etc/nginx/nginx.conf
270 # commit and reload is in post_checks
271 fi
272
273 post_checks
274 }
275
276 load_vars()
277 {
278 local section="$1"
279
280 STATE_DIR=$(config_get "$section" state_dir)
281 ACCOUNT_EMAIL=$(config_get "$section" account_email)
282 DEBUG=$(config_get "$section" debug)
283 }
284
285 check_cron
286 [ -n "$CHECK_CRON" ] && exit 0
287 [ -e "/var/run/acme_boot" ] && rm -f "/var/run/acme_boot" && exit 0
288
289 config_load acme
290 config_foreach load_vars acme
291
292 if [ -z "$STATE_DIR" ] || [ -z "$ACCOUNT_EMAIL" ]; then
293 err "state_dir and account_email must be set"
294 exit 1
295 fi
296
297 [ -d "$STATE_DIR" ] || mkdir -p "$STATE_DIR"
298
299 trap err_out HUP TERM
300 trap int_out INT
301
302 config_foreach issue_cert cert
303
304 exit 0