@@ -52,6 +52,12 @@ serve_request() {
5252 fi
5353
5454 serve_mail_root=" ${MAILDIRS: A} "
55+ case " $serve_mail_root " in
56+ * :* )
57+ error " Invalid MAILDIRS path '$serve_mail_root ' (':' not supported by dovecot mail_location)"
58+ return 1
59+ ;;
60+ esac
5561 serve_runtime=" ${MAILDIRS} /.imap"
5662 serve_runtime=" ${serve_runtime: A} "
5763 serve_conf=" ${serve_runtime} /dovecot.conf"
@@ -65,6 +71,31 @@ serve_request() {
6571 return 0
6672}
6773
74+ # Dovecot passwd-file fields must not contain separators/newlines.
75+ serve_validate_passwd_field () {
76+ fn serve_validate_passwd_field
77+ local _field_name=" $1 "
78+ local _field_value=" $2 "
79+
80+ [[ " $_field_value " == * :* ]] && {
81+ error " Invalid ${_field_name} : ':' is not allowed"
82+ return 1
83+ }
84+ [[ " $_field_value " == * $' \n ' * || " $_field_value " == * $' \r ' * ]] && {
85+ error " Invalid ${_field_name} : newlines are not allowed"
86+ return 1
87+ }
88+ return 0
89+ }
90+
91+ # Escape paths for dovecot.conf values.
92+ serve_escape_dovecot_path () {
93+ local _path=" $1 "
94+ _path=" ${_path// \\ / \\\\ } "
95+ _path=" ${_path// / \\\\ } "
96+ print -- " $_path "
97+ }
98+
6899# Keep canonical maildirs present so IMAP exposure is predictable.
69100serve_ensure_maildirs () {
70101 fn serve_ensure_maildirs
@@ -121,23 +152,31 @@ serve_generate_password() {
121152
122153# Render dovecot.conf from request/domain state.
123154serve_render_dovecot_conf () {
155+ local _mail_root _runtime _passwd_file _log_file _info_log_file
156+ _mail_root=" $( serve_escape_dovecot_path " $serve_mail_root " ) "
157+ _runtime=" $( serve_escape_dovecot_path " $serve_runtime " ) "
158+ _passwd_file=" $( serve_escape_dovecot_path " $serve_passwd_file " ) "
159+ _log_file=" $( serve_escape_dovecot_path " $serve_log_file " ) "
160+ _info_log_file=" $( serve_escape_dovecot_path " $serve_info_log_file " ) "
161+
124162 cat << EOF
125163protocols = imap
126164listen = ${serve_host}
127165ssl = no
166+ disable_plaintext_auth = no
128167auth_mechanisms = plain
129168mail_debug = no
130169
131- mail_location = maildir:${serve_mail_root } :LAYOUT=fs:INDEX=${serve_runtime } /index:CONTROL=${serve_runtime } /control
170+ mail_location = maildir:${_mail_root } :LAYOUT=fs:INDEX=${_runtime } /index:CONTROL=${_runtime } /control
132171
133- base_dir = ${serve_runtime } /run
134- state_dir = ${serve_runtime } /state
135- log_path = ${serve_log_file }
136- info_log_path = ${serve_info_log_file }
172+ base_dir = ${_runtime } /run
173+ state_dir = ${_runtime } /state
174+ log_path = ${_log_file }
175+ info_log_path = ${_info_log_file }
137176
138177passdb {
139178 driver = passwd-file
140- args = ${serve_passwd_file }
179+ args = ${_passwd_file }
141180}
142181
143182userdb {
164203serve_write_runtime_files () {
165204 fn serve_write_runtime_files
166205
206+ serve_validate_passwd_field " JARO_SERVE_USER" " $serve_user " || return 1
207+ serve_validate_passwd_field " JARO_SERVE_PASSWORD" " $serve_password " || return 1
208+
167209 cat << EOF > "$serve_passwd_file "
168210${serve_user} :{PLAIN}${serve_password} :${serve_uid} :${serve_gid} ::${serve_mail_root} ::
169211EOF
@@ -187,26 +229,42 @@ serve_validate_config() {
187229 return 0
188230}
189231
190- # Return 0 when selected port is already listening, 1 when free, 2 when unknown.
232+ # Return 0 when a listener conflicts with requested bind.
233+ serve_port_listener_conflicts () {
234+ fn serve_port_listener_conflicts
235+ local _listener=" $1 "
236+
237+ case " $_listener " in
238+ " 127.0.0.1:${serve_port} " |" localhost:${serve_port} " |" 0.0.0.0:${serve_port} " |" *:${serve_port} " |" [::]:${serve_port} " |" :::${serve_port} " )
239+ return 0
240+ ;;
241+ esac
242+ return 1
243+ }
244+
245+ # Return 0 when selected port is already listening on conflicting address, 1 when free, 2 when unknown.
191246serve_port_in_use () {
192247 fn serve_port_in_use
248+ local _listener
193249
194250 if command -v lsof > /dev/null 2>&1 ; then
195- lsof -nP -iTCP: ${serve_port} -sTCP:LISTEN 2> /dev/null \
196- | awk ' {print $9} ' \
197- | grep -Eq " (^|:) ${serve_port} $ " && return 0
251+ while IFS= read -r _listener ; do
252+ serve_port_listener_conflicts " $_listener " && return 0
253+ done < <( lsof -nP -iTCP: ${serve_port} -sTCP:LISTEN 2> /dev/null | awk ' {print $9} ' )
198254 return 1
199255 fi
200256
201257 if command -v ss > /dev/null 2>&1 ; then
202- ss -ltn 2> /dev/null | awk ' {print $4}' \
203- | grep -Eq " (^|:)${serve_port} $" && return 0
258+ while IFS= read -r _listener; do
259+ serve_port_listener_conflicts " $_listener " && return 0
260+ done < <( ss -ltn 2> /dev/null | awk ' {print $4}' )
204261 return 1
205262 fi
206263
207264 if command -v netstat > /dev/null 2>&1 ; then
208- netstat -ltn 2> /dev/null | awk ' {print $4}' \
209- | grep -Eq " (^|:)${serve_port} $" && return 0
265+ while IFS= read -r _listener; do
266+ serve_port_listener_conflicts " $_listener " && return 0
267+ done < <( netstat -ltn 2> /dev/null | awk ' {print $4}' )
210268 return 1
211269 fi
212270
0 commit comments