summaryrefslogtreecommitdiffstats
path: root/lexd-spool.sh
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