blob: 9b9b72330a1b67f87b6e2b6265bfda9a964c6551 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
#!/bin/ksh
# OpenBSD ksh daemon script to process remote spool files over SSH
# Fall back to the running user's home if $HOME is unset
USER_HOME="${HOME:-$(eval echo "~${USER}")}"
SSH_CONFIG="${USER_HOME}/.ssh/config"
SSH_HOST="lex-spool"
TMP_DIR="/var/www/var/lex/tmp"
DICT_DIR="/var/www/var/lex/dict"
SPOOL_DIR="/var/www/var/lex/spool"
BAD_DIR="/var/www/var/lex/bad"
SOCK_PATH="/var/run/lexd/sock"
SLEEP_INTERVAL=30
log_err() {
logger -t lexd-spool -p daemon.err "$1"
}
log_info() {
logger -t lexd-spool -p daemon.info "$1"
}
# Move an unprocessable or corrupt file to the remote bad directory
quarantine_file() {
log_err "Moving unprocessable file to ${SPOOL_FILE}"
ssh -F "${SSH_CONFIG}" "${SSH_HOST}" \
"mkdir -p '${BAD_DIR}' && mv -f '${SPOOL_DIR}/${SPOOL_FILE}' '${BAD_DIR}/'" 2>/dev/null
}
while true; do
# Fetch one file cleanly from spool directory
SPOOL_FILE=$(ssh -F "${SSH_CONFIG}" -n "${SSH_HOST}" \
"ls -1 '${SPOOL_DIR}' 2>/dev/null | head -n 1")
if [ $? -ne 0 ]; then
log_err "Failed connection attempt to ${SSH_HOST}"
sleep "${SLEEP_INTERVAL}"
continue
fi
# If spool is empty, wait and retry
if [ -z "${SPOOL_FILE}" ]; then
sleep "${SLEEP_INTERVAL}"
continue
fi
(
set -e
# Fetch remote spool file content
raw_prompt=$(ssh -F "${SSH_CONFIG}" -n "${SSH_HOST}" \
"cat '${SPOOL_DIR}/${SPOOL_FILE}'")
if [ -z "${raw_prompt}" ]; then
log_err "Empty spool file: ${SPOOL_FILE}"
quarantine_file
exit 1
fi
# Log spool file + prompt snippet, capped at 45 characters total
prefix="${SPOOL_FILE}: "
prefix_len=${#prefix}
max_prompt_len=$(( 45 - prefix_len ))
if [ ${max_prompt_len} -gt 0 ]; then
trunc_prompt=$(print -r -- "${raw_prompt}" | cut -c1-"${max_prompt_len}")
log_entry="${prefix}${trunc_prompt}"
else
log_entry=$(print -r -- "${prefix}" | cut -c1-45)
fi
log_info "Processing: ${log_entry}"
# Pass prompt to socket and insert missing empty lines before uppercase headers
response=$(print -r -- "${raw_prompt}" | nc -w 300 -U "${SOCK_PATH}" | awk '
/^[A-Z ]+:[[:space:]]*$/ {
if (NR > 1 && last != "") {
print ""
}
}
{
print $0
last = $0
}
')
if [ -z "${response}" ]; then
log_err "Empty socket response for: ${SPOOL_FILE}"
quarantine_file
exit 1
fi
# Strip leading '<' character if present
response="${response#<}"
# Extract first word (lowercased)
target_word=$(print -r -- "${response}" \
| awk '{print tolower($1); exit}')
if [ -z "${target_word}" ]; then
log_err "No target filename for: ${SPOOL_FILE}"
quarantine_file
exit 1
fi
# Validate target word
case "${target_word}" in
*[!a-z-]*)
log_err "Invalid word '${target_word}': ${SPOOL_FILE}"
quarantine_file
exit 1
;;
[!a-z]*|*[!a-z])
log_err "Bad bounds '${target_word}': ${SPOOL_FILE}"
quarantine_file
exit 1
;;
esac
target_file="${DICT_DIR}/${target_word}"
tmp_file="${TMP_DIR}/${target_word}"
remote_cmd="cat > '${tmp_file}' &&"
remote_cmd="${remote_cmd} mv -f '${tmp_file}'"
remote_cmd="${remote_cmd} '${target_file}' &&"
remote_cmd="${remote_cmd} rm -f"
remote_cmd="${remote_cmd} '${SPOOL_DIR}/${SPOOL_FILE}'"
ssh_err=$(print -r -- "${response}" \
| fold -s -w 72 \
| ssh -F "${SSH_CONFIG}" "${SSH_HOST}" "${remote_cmd}" 2>&1 >/dev/null)
ssh_res=$?
if [ ${ssh_res} -ne 0 ]; then
log_err "SSH error on ${SPOOL_FILE} (code ${ssh_res}): ${ssh_err}"
quarantine_file
exit 1
fi
log_info "Finished processing: ${target_word}"
)
if [ $? -ne 0 ]; then
log_err "Processing failed for file: ${SPOOL_FILE}"
fi
sleep "${SLEEP_INTERVAL}"
done
|