-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathserver
executable file
·378 lines (348 loc) · 9.76 KB
/
server
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
#!/usr/bin/env bash
cd "${0%/*}"
set -o noglob
# you do NOT want to know why
export LC_ALL=C
ORM_DB=db.sqlite
VERSION=0.0.1
IRC_SERVER_HOST=127.0.0.1
PORT=${PORT:-6667}
declare -A SUBS
# self explanatory
function query() {
function _query() {
local QUERY
QUERY="$1"
shift
printf "PRAGMA foreign_keys = ON;\n"
printf ".output /dev/null\n"
printf "PRAGMA journal_mode = WAL;\n"
printf ".output\n"
printf ".parameter init\n"
INDEX=1
for arg in "$@"; do
: "${arg//\\/\\\\\\\\}"
: "${_//$'\t'/ }"
: "${_//\"/\\\"}"
: "${_//\'/\\\'}"
: "${_//$'\n'/\\\\n}"
if [[ "$_" =~ "'" ]]; then
printf ".parameter set ?%d \"%s\"\n" $INDEX "$_"
else
printf ".parameter set ?%d \"'%s'\"\n" $INDEX "$_"
fi
((INDEX++))
done
printf "%s\n" "$QUERY";
}
sqlite3 -separator $'\t' "$ORM_DB" 2>&1 < <(_query "$@")
}
split() {
# Usage: split "string" "delimiter"
IFS=$'\n' read -d "" -ra arr <<< "${1//$2/$'\n'}"
}
function listen() {
local TOPIC
TOPIC="$1"
if [[ -z "$TOPIC" ]]; then
debug "listen: ATTEMPTED TO LISTEN ON EMPTY TOPIC"
return
fi
local -a arr
while true; do
IFS= read -r line < "$TOPIC"
split "${line//$'\r'}" ' '
if [[ "${arr[0]}" =~ :(.+) ]]; then
PREFIX="${BASH_REMATCH[1]}"
arr=("${arr[@]:1}")
fi
COMMAND="${arr[0]}"
arr=("${arr[@]:1}")
local USER="${PREFIX%%@*}"
local USER="${USER%%!*}"
case "$COMMAND" in
PSST)
# internal stuff dont worry its all good
CMD="${arr[0]}"
arr=("${arr[@]:1}")
case "$CMD" in
NICK)
NICK="${arr[0]}"
;;
esac
continue
;;
PRIVMSG)
CHANNEL="${arr[0]}"
[[ "$USER" == "$NICK" ]] && continue
;;
JOIN)
CHANNEL="${arr[0]:1}"
if [[ "$USER" == "$NICK" ]]; then
SUBS["$CHANNEL"]=true
fi
;;
PART)
CHANNEL="${arr[0]:1}"
if [[ "$USER" == "$NICK" ]]; then
# debug "listener: $USER PARTED from $CHANNEL"
unset SUBS["$CHANNEL"]
fi
;;
NICK)
if [[ "$USER" == "$NICK" ]]; then
NICK="${arr[0]}"
fi
;;
QUIT)
;;
*)
continue
;;
esac
if [[ "$USER" != "$NICK" && -n "$CHANNEL" ]]; then
[[ -z "${SUBS[$CHANNEL]}" ]] && continue
fi
# forward the message
printf "%s\n" "$line"
done
}
function subscribe() {
mkdir -p pubsub/irc
local tmppipe=$(mktemp -up pubsub/irc)
mkfifo -m 600 "$tmppipe"
SUBSCRIPTION="$tmppipe"
debug "pipe: opened: $tmppipe"
}
function unsubscribe() {
[[ ! -z "$UNSUBBED" ]] && return
UNSUBBED=true
debug "pipe: closed: $SUBSCRIPTION"
[[ ! -z "$SUBSCRIPTION" ]] && rm -f "$SUBSCRIPTION"
printf "%s\r\n" ":$NICK!$USER@$IRC_SERVER_HOST QUIT :$QUIT_MESSAGE" | broadcast
query 'DELETE FROM conns WHERE id = ?;' "$CONN_ID" &> /dev/null
rm -rf "$HEARTBEAT"
}
function broadcast() {
[[ ! -d "pubsub/irc" ]] && return
TEE_ARGS=$(find pubsub/irc -type p)
[[ -z "$TEE_ARGS" ]] && return
printf "broadcast: " 1>&2
tee /dev/stderr $TEE_ARGS > /dev/null
}
reply() {
local RESPONSE
printf -v "RESPONSE" ":%s %s\r\n" "$IRC_SERVER_HOST" "$*"
RESPONSE="${RESPONSE:0:512}"
printf "%s" "$RESPONSE"
[[ -z "$SILENT" ]] && debug "outbound[$CONN_ID]: ${RESPONSE//$'\n'/}"
}
debug() {
printf "%s\n" "$@" 1>&2
}
heartbeat() {
while true; do
if [[ ! -f "$HEARTBEAT" ]]; then
exit 1
fi
if [[ "$(<$HEARTBEAT)" -lt $PING ]]; then
debug "heartbeat[$CONN_ID]: failed to pong; hanging up"
kill $SOUL
exit 1
fi
SILENT=true reply PING ":$PING"
((PING++))
sleep 60
done
}
if [[ -z "$IRC_CONN_HANDLER" ]]; then
rm -rf pubsub
mkdir pubsub
query 'CREATE TABLE IF NOT EXISTS channels
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name varchar(201) NOT NULL UNIQUE,
topic varchar(500)
);'
query 'INSERT INTO channels(name) VALUES("#test");'
query 'CREATE TABLE IF NOT EXISTS conns
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
nick varchar(201) UNIQUE
);'
query 'CREATE TABLE IF NOT EXISTS membership
(
channel INTEGER,
who INTEGER,
PRIMARY KEY (channel, who),
FOREIGN KEY(channel) REFERENCES channels(id) ON DELETE CASCADE,
FOREIGN KEY(who) REFERENCES conns(id) ON DELETE CASCADE
);'
query "DELETE FROM conns; DELETE FROM SQLITE_SEQUENCE WHERE name='conns';"
echo -n "main: Starting server on port "
export IRC_CONN_HANDLER=true
export IRC_SERVER_CREATED="$(date)"
tcpserver -v -1 -o -l 0 -H -D -R -c 1000 0 $PORT $0
else
REGISTERED=
welcome() {
[[ -n "$REGISTERED" ]] && return;
REGISTERED=1
reply 001 $NICK ":Welcome, $NICK!$USER@$IRC_SERVER_HOST"
reply 002 $NICK ":Your host is $IRC_SERVER_HOST, running version $VERSION"
reply 003 $NICK ":This server was created $IRC_SERVER_CREATED"
reply 004 $NICK "$IRC_SERVER_HOST bash-$VERSION"
reply 376 $NICK ":End of the /MOTD command."
}
PING=0
PONG=0
# echo "connection opened with $TCPREMOTEIP" 1>&2
subscribe
listen "$SUBSCRIPTION" &
mkdir -p pubsub/heartbeat
HEARTBEAT="$(mktemp -p pubsub/heartbeat)"
# debug "heartbeat: opened $HEARTBEAT"
echo 0 > "$HEARTBEAT"
CONN_ID="$(query 'INSERT INTO conns DEFAULT VALUES RETURNING id;')"
SOUL=$$
heartbeat &
# debug "conn_id: $CONN_ID"
trap unsubscribe EXIT
while IFS= read -r line; do
split "${line//$'\r'}" ' '
if [[ "${arr[0]}" =~ :(.+) ]]; then
PREFIX="${BASH_REMATCH[1]}"
arr=("${arr[@]:1}")
fi
COMMAND="${arr[0]}"
debug "inbound[$CONN_ID]: ${line//$'\r'}"
arr=("${arr[@]:1}")
case "${COMMAND^^}" in
# CAP)
# CAP="${arr[0]}"
# reply CAP "* NAK :"
# ;;
NICK)
NICK_REGEX='^[A-}][-0-9A-}]{0,8}$'
NEW_NICK="${arr[0]#:}"
if [[ -z "$NEW_NICK" ]]; then
reply 431 ":No nickname given"
elif [[ ! "$NEW_NICK" =~ $NICK_REGEX ]]; then
reply 432 $NEW_NICK ":Erroneous nickname"
else
RESULT="$(query 'UPDATE conns SET nick = ? WHERE id = ?;' "$NEW_NICK" "$CONN_ID")"
if [[ -n "$RESULT" ]]; then
# debug "result: $RESULT"
# debug "error: nickname $NEW_NICK already in use"
reply 433 '*' $NEW_NICK ":Nickname is already in use"
else
if [[ -n "$NICK" ]]; then
printf "%s\r\n" ":$NICK!$USER@$IRC_SERVER_HOST NICK $NEW_NICK" | broadcast
NICK="$NEW_NICK"
else
NICK="$NEW_NICK"
echo "PSST NICK $NICK" > "$SUBSCRIPTION"
welcome
fi
fi
fi
;;
USER)
USER="${arr[0]}"
MODE="${arr[1]}"
REALNAME="${arr[3]}"
if [[ -n "$NICK" ]]; then
welcome
fi
;;
PRIVMSG)
TARGET="${arr[0]}"
arr=("${arr[@]:1}")
MSG="${arr[*]}"
MSG="${MSG//$'\n'/}"
if [[ "${MSG:0:1}" != ":" ]]; then
MSG=":$MSG"
fi
printf "%s\r\n" ":$NICK PRIVMSG $TARGET $MSG" | broadcast
;;
TOPIC)
# TODO
CHANNEL="${arr[0]}"
arr=("${arr[@]:1}")
# TOPIC="$(query "SELECT topic FROM channels WHERE name = ?;" "$CHANNEL")"
# reply TOPIC "$CHANNEL" "$TOPIC"
;;
LIST)
while IFS=$'\t' read -r channel_id channel visible topic; do
reply 322 "$NICK $channel $visible :$topic"
done <<< "$(query "SELECT
channels.id,
channels.name,
COUNT(membership.who) AS visible,
channels.topic
FROM channels
LEFT JOIN membership
ON membership.channel = channels.id
GROUP BY channels.id, channels.name, channels.topic;")"
reply 323 ":End of LIST"
;;
JOIN)
if [[ "${arr[@]}" == "0" ]]; then
debug "joins: $NICK did join 0"
elif [[ -z "$REGISTERED" ]]; then
reply 451 '*' ':You have not registered'
elif [[ -n "$NICK" ]]; then
CHANNELS="${arr[0]}"
KEYS="${arr[1]}"
split "$CHANNELS" ','
CHANNELS=("${arr[@]}")
split "$KEYS" ','
KEYS=("${arr[@]}")
for CHANNEL in ${CHANNELS[@]}; do
IFS=$'\t' read -r CHANNEL_ID TOPIC <<< "$(query "SELECT id, topic FROM channels WHERE name = ?;" "$CHANNEL")"
if [[ -z "$CHANNEL_ID" ]]; then
continue
fi
query "INSERT INTO membership(channel, who) VALUES(?, ?);" "$CHANNEL_ID" "$CONN_ID"
SUBS["$CHANNEL"]=true
printf "%s\r\n" ":$NICK!$USER@$IRC_SERVER_HOST JOIN :$CHANNEL" | broadcast
EVERYONE="$(query "SELECT conns.nick FROM membership LEFT JOIN conns
ON conns.id = membership.who WHERE membership.channel = ?;" "$CHANNEL_ID")"
EVERYONE="${EVERYONE//$'\n'/ }"
reply 353 "$NICK" '=' "$CHANNEL" ":$EVERYONE"
reply 366 "$NICK" "$CHANNEL" ':'
reply 332 "$NICK" "$CHANNEL" ":$TOPIC"
done
fi
;;
QUIT)
exit 0
;;
PART)
CHANNELS="${arr[0]}"
KEYS="${arr[1]}"
split "$CHANNELS" ','
CHANNELS=("${arr[@]}")
split "$KEYS" ','
KEYS=("${arr[@]}")
query "DELETE FROM membership WHERE channel = ? AND who = ?;" "$CHANNEL_ID" "$CONN_ID"
for CHANNEL in ${CHANNELS[@]}; do
unset SUBS["$CHANNEL"]
printf "%s\r\n" ":$NICK!$USER@$IRC_SERVER_HOST PART :$CHANNEL" | broadcast
:
done
;;
PING)
SILENT=true reply PONG "$NICK" "${arr[0]}"
;;
PONG)
((PONG++))
echo "$PONG" > "$HEARTBEAT"
;;
*)
:
;;
esac
done
unsubscribe
fi