Skip to content

Commit 70e85c2

Browse files
author
Kristan Kenney
committed
Add backend scripts for controlling mail SSL functionality
1 parent e6b235b commit 70e85c2

11 files changed

+752
-19
lines changed

bin/v-add-letsencrypt-mail-domain

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
#!/bin/bash
2+
# info: check letsencrypt domain
3+
# options: USER DOMAIN [ALIASES] [RESTART] [NOTIFY]
4+
#
5+
# The function check and validates domain with Let's Encrypt
6+
7+
8+
#----------------------------------------------------------#
9+
# Variable&Function #
10+
#----------------------------------------------------------#
11+
12+
# Argument definition
13+
user=$1
14+
domain=$2
15+
aliases=$3
16+
restart=$4
17+
notify=$5
18+
19+
# LE API
20+
LE_API='https://acme-v02.api.letsencrypt.org'
21+
22+
# Includes
23+
source $HESTIA/func/main.sh
24+
source $HESTIA/func/domain.sh
25+
source $HESTIA/conf/hestia.conf
26+
27+
# encode base64
28+
encode_base64() {
29+
cat |base64 |tr '+/' '-_' |tr -d '\r\n='
30+
}
31+
32+
# Let's Encrypt v2 curl function
33+
query_le_v2() {
34+
35+
protected='{"nonce": "'$3'",'
36+
protected=''$protected' "url": "'$1'",'
37+
protected=''$protected' "alg": "RS256", "kid": "'$KID'"}'
38+
content="Content-Type: application/jose+json"
39+
40+
payload_=$(echo -n "$2" |encode_base64)
41+
protected_=$(echo -n "$protected" |encode_base64)
42+
signature_=$(printf "%s" "$protected_.$payload_" |\
43+
openssl dgst -sha256 -binary -sign $USER_DATA/ssl/user.key |\
44+
encode_base64)
45+
46+
post_data='{"protected":"'"$protected_"'",'
47+
post_data=$post_data'"payload":"'"$payload_"'",'
48+
post_data=$post_data'"signature":"'"$signature_"'"}'
49+
50+
curl -s -i -d "$post_data" "$1" -H "$content"
51+
}
52+
53+
54+
55+
#----------------------------------------------------------#
56+
# Verifications #
57+
#----------------------------------------------------------#
58+
59+
check_args '2' "$#" 'USER DOMAIN [ALIASES] [RESTART] [NOTIFY]'
60+
is_format_valid 'user' 'domain'
61+
is_system_enabled "$MAIL_SYSTEM" 'MAIL_SYSTEM'
62+
is_object_valid 'user' 'USER' "$user"
63+
is_object_unsuspended 'user' 'USER' "$user"
64+
is_object_valid 'mail' 'DOMAIN' "$domain"
65+
is_object_unsuspended 'mail' 'DOMAIN' "$domain"
66+
is_object_value_empty 'mail' 'DOMAIN' "$domain" '$SSL'
67+
68+
69+
#----------------------------------------------------------#
70+
# Action #
71+
#----------------------------------------------------------#
72+
# Parsing domain data
73+
get_domain_values 'web'
74+
75+
# Registering LetsEncrypt user account
76+
$BIN/v-add-letsencrypt-user $user
77+
if [ "$?" -ne 0 ]; then
78+
touch $HESTIA/data/queue/letsencrypt.pipe
79+
sed -i "/ $domain /d" $HESTIA/data/queue/letsencrypt.pipe
80+
send_notice "LETSENCRYPT" "Account registration failed"
81+
check_result $E_CONNECT "LE account registration" > /dev/null
82+
fi
83+
84+
# Parsing LetsEncrypt account data
85+
source $USER_DATA/ssl/le.conf
86+
87+
# Checking wildcard alias
88+
if [ "$aliases" = "*.$domain" ]; then
89+
wildcard='yes'
90+
proto="dns-01"
91+
if [ ! -e "$HESTIA/data/users/$user/dns/$domain.conf" ]; then
92+
check_result $E_NOTEXIST "DNS domain $domain doesn't exist"
93+
fi
94+
else
95+
proto="http-01"
96+
fi
97+
98+
# Requesting nonce / STEP 1
99+
answer=$(curl -s -I "$LE_API/directory")
100+
nonce=$(echo "$answer" |grep Nonce |cut -f2 -d \ |tr -d '\r\n')
101+
status=$(echo "$answer"|grep HTTP/1.1 |tail -n1 |cut -f 2 -d ' ')
102+
if [[ "$status" -ne 200 ]]; then
103+
check_result $E_CONNECT "Let's Encrypt nonce request status $status"
104+
fi
105+
106+
# Placing new order / STEP 2
107+
url="$LE_API/acme/new-order"
108+
payload='{"identifiers":['
109+
for identifier in $(echo $domain,$aliases |tr ',' '\n' |sort -u); do
110+
payload=$payload'{"type":"dns","value":"'$identifier'"},'
111+
done
112+
payload=$(echo "$payload"|sed "s/,$//")
113+
payload=$payload']}'
114+
answer=$(query_le_v2 "$url" "$payload" "$nonce")
115+
nonce=$(echo "$answer" |grep Nonce |cut -f2 -d \ |tr -d '\r\n')
116+
authz=$(echo "$answer" |grep "acme/authz" |cut -f2 -d '"')
117+
finalize=$(echo "$answer" |grep 'finalize":' |cut -f4 -d '"')
118+
status=$(echo "$answer" |grep HTTP/1.1 |tail -n1 |cut -f2 -d ' ')
119+
if [[ "$status" -ne 201 ]]; then
120+
check_result $E_CONNECT "Let's Encrypt new auth status $status"
121+
fi
122+
123+
# Requesting authorization token / STEP 3
124+
for auth in $authz; do
125+
payload=''
126+
answer=$(query_le_v2 "$auth" "$payload" "$nonce")
127+
url=$(echo "$answer" |grep -A3 $proto |grep url |cut -f 4 -d \")
128+
token=$(echo "$answer" |grep -A3 $proto |grep token |cut -f 4 -d \")
129+
nonce=$(echo "$answer" |grep Nonce |cut -f2 -d \ |tr -d '\r\n')
130+
status=$(echo "$answer"|grep HTTP/1.1 |tail -n1 |cut -f 2 -d ' ')
131+
if [[ "$status" -ne 200 ]]; then
132+
check_result $E_CONNECT "Let's Encrypt acme/authz bad status $status"
133+
fi
134+
135+
# Accepting challenge / STEP 4
136+
if [ "$wildcard" = 'yes' ]; then
137+
record=$(printf "%s" "$token.$THUMB" |\
138+
openssl dgst -sha256 -binary |encode_base64)
139+
old_records=$($BIN/v-list-dns-records $user $domain plain|grep 'TXT')
140+
old_records=$(echo "$old_records" |grep _acme-challenge |cut -f 1)
141+
for old_record in $old_records; do
142+
$BIN/v-delete-dns-record $user $domain $old_record
143+
done
144+
$BIN/v-add-dns-record $user $domain "_acme-challenge" "TXT" $record
145+
check_result $? "DNS _acme-challenge record wasn't created"
146+
else
147+
if [ "$WEB_SYSTEM" = 'nginx' ] || [ ! -z "$PROXY_SYSTEM" ]; then
148+
conf="$HOMEDIR/$user/conf/web/$domain/nginx.conf_letsencrypt"
149+
sconf="$HOMEDIR/$user/conf/web/$domain/nginx.ssl.conf_letsencrypt"
150+
if [ ! -e "$conf" ]; then
151+
echo 'location ~ "^/\.well-known/acme-challenge/(.*)$" {' \
152+
> $conf
153+
echo ' default_type text/plain;' >> $conf
154+
echo ' return 200 "$1.'$THUMB'";' >> $conf
155+
echo '}' >> $conf
156+
fi
157+
if [ ! -e "$sconf" ]; then
158+
ln -s "$conf" "$sconf"
159+
fi
160+
$BIN/v-restart-proxy
161+
check_result $? "Proxy restart failed" > /dev/null
162+
163+
else
164+
well_known="$HOMEDIR/$user/web/$rdomain/public_html/.well-known"
165+
acme_challenge="$well_known/acme-challenge"
166+
mkdir -p $acme_challenge
167+
echo "$token.$THUMB" > $acme_challenge/$token
168+
chown -R $user:$user $well_known
169+
fi
170+
$BIN/v-restart-web
171+
check_result $? "Web restart failed" > /dev/null
172+
fi
173+
174+
# Requesting ACME validation / STEP 5
175+
validation_check=$(echo "$answer" |grep '"valid"')
176+
if [[ ! -z "$validation_check" ]]; then
177+
validation='valid'
178+
else
179+
validation='pending'
180+
fi
181+
182+
# Doing pol check on status
183+
i=1
184+
while [ "$validation" = 'pending' ]; do
185+
payload='{}'
186+
answer=$(query_le_v2 "$url" "$payload" "$nonce")
187+
validation=$(echo "$answer"|grep -A1 $proto |tail -n1|cut -f4 -d \")
188+
nonce=$(echo "$answer" |grep Nonce |cut -f2 -d \ |tr -d '\r\n')
189+
status=$(echo "$answer"|grep HTTP/1.1 |tail -n1 |cut -f 2 -d ' ')
190+
if [[ "$status" -ne 200 ]]; then
191+
check_result $E_CONNECT "Let's Encrypt validation status $status"
192+
fi
193+
194+
i=$((i + 1))
195+
if [ "$i" -gt 10 ]; then
196+
check_result $E_CONNECT "Let's Encrypt domain validation timeout"
197+
fi
198+
sleep 1
199+
done
200+
if [ "$validation" = 'invalid' ]; then
201+
check_result $E_CONNECT "Let's Encrypt domain verification failed"
202+
fi
203+
done
204+
205+
# Generating new ssl certificate
206+
ssl_dir=$($BIN/v-generate-ssl-cert "$domain" "info@$domain" "US" "California"\
207+
"San Francisco" "Hestia" "IT" "$aliases" |tail -n1 |awk '{print $2}')
208+
209+
# Sending CSR to finalize order / STEP 6
210+
csr=$(openssl req -in $ssl_dir/$domain.csr -outform DER |encode_base64)
211+
payload='{"csr":"'$csr'"}'
212+
answer=$(query_le_v2 "$finalize" "$payload" "$nonce")
213+
nonce=$(echo "$answer" |grep Nonce |cut -f2 -d \ |tr -d '\r\n')
214+
status=$(echo "$answer"|grep HTTP/1.1 |tail -n1 |cut -f 2 -d ' ')
215+
certificate=$(echo "$answer"|grep 'certificate":' |cut -f4 -d '"')
216+
if [[ "$status" -ne 200 ]]; then
217+
check_result $E_CONNECT "Let's Encrypt finalize bad status $status"
218+
fi
219+
220+
# Downloading signed certificate / STEP 7
221+
curl -s "$certificate" -o $ssl_dir/$domain.pem
222+
223+
# Splitting up downloaded pem
224+
crt_end=$(grep -n END $ssl_dir/$domain.pem |head -n1 |cut -f1 -d:)
225+
head -n $crt_end $ssl_dir/$domain.pem > $ssl_dir/$domain.crt
226+
227+
pem_lines=$(wc -l $ssl_dir/$domain.pem |cut -f 1 -d ' ')
228+
ca_end=$(grep -n "BEGIN" $ssl_dir/$domain.pem |tail -n1 |cut -f 1 -d :)
229+
ca_end=$(( pem_lines - crt_end + 1 ))
230+
tail -n $ca_end $ssl_dir/$domain.pem > $ssl_dir/$domain.ca
231+
232+
# Temporary fix for double "END CERTIFICATE"
233+
if [[ $(head -n 1 $ssl_dir/$domain.ca) = "-----END CERTIFICATE-----" ]]; then
234+
sed -i '1,2d' $ssl_dir/$domain.ca
235+
fi
236+
237+
# Adding SSL
238+
$BIN/v-delete-mail-domain-ssl $user $domain >/dev/null 2>&1
239+
$BIN/v-add-mail-domain-ssl $user $domain $ssl_dir
240+
241+
if [ "$?" -ne '0' ]; then
242+
touch $HESTIA/data/queue/letsencrypt.pipe
243+
sed -i "/ $domain /d" $HESTIA/data/queue/letsencrypt.pipe
244+
send_notice 'LETSENCRYPT' "$domain certificate installation failed"
245+
check_result $? "SSL install" > /dev/null
246+
fi
247+
248+
# Adding LE autorenew cronjob
249+
if [ -z "$(grep v-update-lets $HESTIA/data/users/admin/cron.conf)" ]; then
250+
min=$(generate_password '012345' '2')
251+
hour=$(generate_password '1234567' '1')
252+
cmd="sudo $BIN/v-update-letsencrypt-ssl"
253+
$BIN/v-add-cron-job admin "$min" "$hour" '*' '*' '*' "$cmd" > /dev/null
254+
fi
255+
256+
# Updating letsencrypt key
257+
if [ -z "$LETSENCRYPT" ]; then
258+
add_object_key "mail" 'DOMAIN' "$domain" 'LETSENCRYPT' 'SUSPENDED'
259+
fi
260+
261+
update_object_value 'mail' 'DOMAIN' "$domain" 'LETSENCRYPT' 'yes'
262+
263+
#----------------------------------------------------------#
264+
# Hestia #
265+
#----------------------------------------------------------#
266+
267+
# Deleting task from queue
268+
touch $HESTIA/data/queue/letsencrypt.pipe
269+
sed -i "/ $domain /d" $HESTIA/data/queue/letsencrypt.pipe
270+
271+
# Notifying user
272+
send_notice 'LETSENCRYPT' "$domain SSL has been installed successfully"
273+
274+
# Logging
275+
log_event "$OK" "$ARGUMENTS"
276+
277+
exit

bin/v-add-mail-domain

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ dkim_size=${6-1024}
2121
source $HESTIA/func/main.sh
2222
source $HESTIA/func/domain.sh
2323
source $HESTIA/conf/hestia.conf
24+
source $HESTIA/func/ip.sh
2425

2526
# Define mail user
2627
if [ "$MAIL_SYSTEM" = 'exim4' ]; then
@@ -32,7 +33,7 @@ fi
3233
# Additional argument formatting
3334
format_domain
3435
format_domain_idn
35-
36+
get_user_ip
3637

3738
#----------------------------------------------------------#
3839
# Verifications #
@@ -53,13 +54,11 @@ is_dir_symlink $HOMEDIR/$user/mail
5354
#----------------------------------------------------------#
5455

5556
# Generating timestamp
56-
time_n_date=$(date +'%T %F')
57-
time=$(echo "$time_n_date" |cut -f 1 -d \ )
58-
date=$(echo "$time_n_date" |cut -f 2 -d \ )
57+
new_timestamp
5958

6059
# Adding domain to mail.conf
6160
s="DOMAIN='$domain' ANTIVIRUS='$antivirus' ANTISPAM='$antispam' DKIM='$dkim'"
62-
s="$s CATCHALL='' ACCOUNTS='0' U_DISK='0' SUSPENDED='no' TIME='$time'"
61+
s="$s SSL='no' LETSENCRYPT='no' CATCHALL='' ACCOUNTS='0' WEBMAIL='yes' U_DISK='0' SUSPENDED='no' TIME='$time'"
6362
s="$s DATE='$date'"
6463
echo $s >> $USER_DATA/mail.conf
6564
touch $USER_DATA/mail/$domain.conf
@@ -128,15 +127,15 @@ if [ ! -z "$DNS_SYSTEM" ] && [ "$dkim" = 'yes' ]; then
128127
fi
129128
fi
130129

131-
132130
#----------------------------------------------------------#
133131
# Hestia #
134132
#----------------------------------------------------------#
135133

136134
# Increasing domain value
137135
increase_user_value "$user" '$U_MAIL_DOMAINS'
136+
138137
if [ "$dkim" = 'yes' ]; then
139-
increase_user_value "$user" '$U_MAIL_DKMI'
138+
increase_user_value "$user" '$U_MAIL_DKIM'
140139
fi
141140

142141
# Logging

0 commit comments

Comments
 (0)