AmendHub

Download

jcs

/

subtext

/

binkp.c

 

(View History)

jcs   binkp: Validate filename, more safety checks Latest amendment: 630 on 2026-09-18

1 /*
2 * FTN Binkp
3 * https://www.ritlabs.com/binkp/
4 *
5 * Copyright (c) 2023 joshua stein <jcs@jcs.org>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include "binkp.h"
23 #include "board.h"
24 #include "fidopkt.h"
25 #include "logger.h"
26 #include "mail.h"
27 #include "subtext.h"
28 #include "zip.h"
29
30 /* #define BINKP_DEBUG */
31
32 #define BINKP_CONNECTION_ALIVE(bpc) \
33 (bpc->tcp_status_pb.connectionState == ConnectionStateEstablished || \
34 bpc->tcp_status_pb.connectionState == ConnectionStateCloseWait)
35
36 bool binkp_ready = false;
37 time_t binkp_next_poll = 0;
38 bool binkp_last_poll_error = false;
39
40 static struct fidopkt_address binkp_our_address = { 0 };
41 static struct binkp_connection *binkpc = NULL;
42 static Str255 binkp_inbox_dir, binkp_done_dir, binkp_outbox_dir,
43 binkp_bad_dir;
44 static short binkp_fidopkt_skipped, binkp_fidopkt_imported;
45 static bool binkp_temp_fail = false;
46 struct uthread *binkp_thread = NULL;
47
48 void binkp_mkdir(Str255 dir_str, char *suffix);
49 bool binkp_connect(void);
50 void binkp_free(void);
51 size_t binkp_send_frame(short command, char *data, size_t data_size);
52 bool binkp_read_frame(void);
53 bool binkp_login(void);
54 void binkp_ingest(void);
55 bool binkp_zip_decider(char *filename, size_t size);
56 void binkp_fidopkt_processor(char *filename, unsigned char *data,
57 size_t size);
58 ssize_t binkp_buffer_file(Str255 path, char **data);
59 bool binkp_valid_filename(char *filename);
60 void binkp_toss_inbox(void);
61 void binkp_deliver_outbox(void);
62
63 void
64 binkp_init(void)
65 {
66 Str255 base_dir;
67
68 binkp_ready = false;
69
70 binkp_mkdir(base_dir, NULL);
71 binkp_mkdir(binkp_inbox_dir, "inbox");
72 binkp_mkdir(binkp_done_dir, "processed");
73 binkp_mkdir(binkp_outbox_dir, "outbox");
74 binkp_mkdir(binkp_bad_dir, "bad");
75
76 if (db->config.binkp_hostname[0] == '\0')
77 return;
78
79 if (!fidopkt_parse_address(db->config.ftn_node_addr,
80 &binkp_our_address)) {
81 logger_printf("[binkp] Failed parsing local FTN node address "
82 "\"%s\", fix in settings", db->config.ftn_node_addr);
83 return;
84 }
85
86 binkp_ready = true;
87 }
88
89 bool
90 binkp_reinit(void)
91 {
92 binkp_init();
93 return true;
94 }
95
96 void
97 binkp_mkdir(Str255 dir_str, char *suffix)
98 {
99 short error;
100 long id;
101
102 if (getpath(db->bile->vrefnum, db->bile->filename, dir_str, false) != 0)
103 panic("getpath failed on %s", PtoCstr(db->bile->filename));
104 PtoCstr(dir_str);
105 strlcat((char *)dir_str, ":binkp", sizeof(Str255));
106 if (suffix != NULL) {
107 strlcat((char *)dir_str, ":", sizeof(Str255));
108 strlcat((char *)dir_str, suffix, sizeof(Str255));
109 }
110 CtoPstr(dir_str);
111
112 if (!FIsDir(dir_str)) {
113 error = DirCreate(db->bile->vrefnum, 0, dir_str, &id);
114 if (error)
115 panic("Failed creating %s: %d", PtoCstr(dir_str), error);
116 }
117 }
118
119 void
120 binkp_atexit(void)
121 {
122 if (binkpc != NULL)
123 binkp_free();
124 }
125
126 void
127 binkp_poll(void)
128 {
129 unsigned long started = Time, elapsed;
130
131 binkp_last_poll_error = false;
132
133 binkp_connect();
134 if (!binkpc) {
135 binkp_last_poll_error = true;
136 goto done;
137 }
138
139 if (!binkp_login()) {
140 binkp_last_poll_error = true;
141 goto done;
142 }
143
144 while (binkpc != NULL && !binkpc->done_receiving && !binkpc->error) {
145 binkp_read_frame();
146 uthread_yield();
147 }
148
149 if (binkpc)
150 binkp_deliver_outbox();
151
152 if (binkpc)
153 _TCPClose(&binkpc->tcp_send_iopb, binkpc->tcp_stream, nil, nil,
154 false);
155
156 elapsed = Time - started;
157 logger_printf("[binkp] Finished polling in %ld sec%s", elapsed,
158 elapsed == 1 ? "" : "s");
159
160 done:
161 if (binkpc != NULL) {
162 if (binkpc->error)
163 binkp_last_poll_error = true;
164
165 binkp_free();
166 }
167
168 binkp_next_poll = Time + db->config.binkp_interval_seconds;
169
170 binkp_toss_inbox();
171 }
172
173 bool
174 binkp_connect(void)
175 {
176 char ip_s[16];
177 char *hostname;
178 ip_addr local_ip, host_ip;
179 tcp_port local_port;
180 short error;
181
182 if (_TCPInit() != noErr)
183 panic("Failed initializing MacTCP");
184
185 if (binkpc != NULL)
186 binkp_free();
187
188 binkpc = xmalloczero(sizeof(struct binkp_connection));
189 if (binkpc == NULL) {
190 logger_printf("[binkp] Failed allocating connection of size %ld",
191 sizeof(struct binkp_connection));
192 goto error;
193 }
194 binkpc->buf_size = 1024;
195 binkpc->buf = xmalloc(binkpc->buf_size);
196 if (binkpc->buf == NULL) {
197 logger_printf("[binkp] Failed allocating connection buffer of "
198 "size %ld", binkpc->buf_size);
199 goto error;
200 }
201 binkpc->tcp_buf_size = (4 * 1500) + binkpc->buf_size;
202 binkpc->tcp_buf = xmalloc(binkpc->tcp_buf_size);
203 if (binkpc->tcp_buf == NULL) {
204 logger_printf("[binkp] Failed allocating TCP buffer of "
205 "size %ld", binkpc->tcp_buf_size);
206 goto error;
207 }
208
209 hostname = db->config.binkp_hostname;
210 error = DNSResolveName(hostname, &host_ip, uthread_yield);
211 if (error) {
212 logger_printf("[binkp] Failed resolving binkp host %s: %d",
213 db->config.binkp_hostname, error);
214 goto error;
215 }
216
217 error = _TCPCreate(&binkpc->tcp_send_iopb, &binkpc->tcp_stream,
218 (Ptr)binkpc->tcp_buf, binkpc->tcp_buf_size, nil, nil, nil, false);
219 if (error) {
220 logger_printf("[binkp] Failed creating TCP stream: %d", error);
221 goto error;
222 }
223
224 long2ip(host_ip, (char *)&ip_s);
225
226 logger_printf("[binkp] Connecting to %s (%s) port %d",
227 db->config.binkp_hostname, ip_s, db->config.binkp_port);
228
229 error = _TCPActiveOpen(&binkpc->tcp_send_iopb, binkpc->tcp_stream,
230 host_ip, db->config.binkp_port, &local_ip, &local_port, nil, nil,
231 true);
232 while (!error && binkpc->tcp_send_iopb.ioResult > 0)
233 uthread_yield();
234 if (error || binkpc->tcp_send_iopb.ioResult != 0) {
235 logger_printf("[binkp] Failed connecting to %s (%s) port %d: %d",
236 db->config.binkp_hostname, ip_s, db->config.binkp_port,
237 binkpc->tcp_send_iopb.ioResult);
238 goto error;
239 }
240
241 binkpc->last_frame_at = Time;
242 return true;
243
244 error:
245 binkp_free();
246 return false;
247 }
248
249 void
250 binkp_free(void)
251 {
252 if (binkpc == NULL)
253 return;
254
255 if (binkpc->tcp_stream)
256 _TCPRelease(&binkpc->tcp_send_iopb, binkpc->tcp_stream, nil, nil,
257 false);
258
259 if (binkpc->tcp_buf != NULL)
260 xfree(&binkpc->tcp_buf);
261 if (binkpc->buf != NULL)
262 xfree(&binkpc->buf);
263
264 xfree(&binkpc);
265 }
266
267 size_t
268 binkp_send_frame(short command, char *data, size_t data_size)
269 {
270 u_int16_t flen;
271 short error;
272
273 if (binkpc == NULL)
274 return 0;
275
276 flen = data_size + 2;
277 if (command != BINKP_DATA)
278 flen++;
279 if (flen > binkpc->buf_size) {
280 logger_printf("[binkp] Frame too large (%u), can't send", flen);
281 return 0;
282 }
283
284 while (binkpc->tcp_send_iopb.ioResult > 0) {
285 /* previous _TCPSend has not completed yet */
286 if (!BINKP_CONNECTION_ALIVE(binkpc)) {
287 binkpc->error = true;
288 logger_printf("[binkp] Connection lost (%d) while sending frame",
289 binkpc->tcp_status_pb.connectionState);
290 return false;
291 }
292 uthread_yield();
293 }
294
295 if (command == BINKP_DATA) {
296 binkpc->buf[0] = (data_size >> 8) & 0xff;
297 binkpc->buf[1] = data_size & 0xff;
298 flen = data_size + 2;
299 memcpy(binkpc->buf + 2, data, data_size);
300 } else {
301 binkpc->buf[0] = (((data_size + 1) | 0x8000) >> 8) & 0xff;
302 binkpc->buf[1] = (data_size + 1) & 0xff;
303 binkpc->buf[2] = command;
304 flen = data_size + 3;
305 memcpy(binkpc->buf + 3, data, data_size);
306 /* not sent, just for debugging */
307 binkpc->buf[3 + data_size] = '\0';
308 }
309
310 memset(&binkpc->tcp_wds, 0, sizeof(binkpc->tcp_wds));
311 binkpc->tcp_wds[0].ptr = (Ptr)binkpc->buf;
312 binkpc->tcp_wds[0].length = flen;
313
314 #ifdef BINKP_DEBUG
315 switch (command) {
316 case BINKP_COMMAND_M_PWD:
317 logger_printf("[binkp] Sending password command of size %ld",
318 data_size);
319 break;
320 case BINKP_COMMAND_M_FILE:
321 logger_printf("[binkp] Sending new file: %s", binkpc->buf + 3);
322 break;
323 case BINKP_COMMAND_M_EOB:
324 logger_printf("[binkp] Sending EOB");
325 break;
326 case BINKP_COMMAND_M_GOT:
327 logger_printf("[binkp] Sending GOT for %s",
328 binkpc->cur_incoming_file.filename);
329 break;
330 case BINKP_DATA:
331 logger_printf("[binkp] Sending data of size %ld", data_size);
332 break;
333 default:
334 logger_printf("[binkp] Sending command %d of size %ld: %s",
335 command, data_size, binkpc->buf + 3);
336 }
337 #endif
338
339 error = _TCPSend(&binkpc->tcp_send_iopb, binkpc->tcp_stream,
340 binkpc->tcp_wds, nil, nil, false);
341 if (error) {
342 if (BINKP_CONNECTION_ALIVE(binkpc)) {
343 logger_printf("[binkp] TCPSend of %d failed (%d), aborting",
344 binkpc->tcp_wds[0].length, error);
345 } else {
346 logger_printf("[binkp] Connection lost (%d) while sending frame",
347 binkpc->tcp_status_pb.connectionState);
348 }
349 binkpc->error = true;
350 return 0;
351 }
352
353 binkpc->last_frame_at = Time;
354 uthread_yield();
355
356 return binkpc->tcp_wds[0].length;
357 }
358
359 bool
360 binkp_read_frame(void)
361 {
362 char tmp[128];
363 size_t len, off, frame_data_read;
364 unsigned short rlen, slen;
365 short error;
366 Ptr read_dest;
367
368 if (binkpc == NULL)
369 return false;
370
371 error = _TCPStatus(&binkpc->tcp_read_iopb, binkpc->tcp_stream,
372 &binkpc->tcp_status_pb, nil, nil, false);
373 if (error) {
374 binkpc->error = true;
375 return false;
376 }
377
378 if (!BINKP_CONNECTION_ALIVE(binkpc)) {
379 logger_printf("[binkp] Connection lost (%d) while reading frame",
380 binkpc->tcp_status_pb.connectionState);
381 binkpc->error = true;
382 return false;
383 }
384
385 if (binkpc->tcp_status_pb.amtUnreadData < 2) {
386 if (Time - binkpc->last_frame_at > 60) {
387 logger_printf("[binkp] No data received in %ld seconds, "
388 "disconnecting", Time - binkpc->last_frame_at);
389 binkpc->error = true;
390 }
391 return false;
392 }
393
394 rlen = 2;
395 error = _TCPRcv(&binkpc->tcp_read_iopb, binkpc->tcp_stream,
396 (Ptr)binkpc->buf, &rlen, nil, nil, false);
397 if (error) {
398 binkpc->error = true;
399 return false;
400 }
401
402 #ifdef BINKP_DEBUG
403 logger_printf("[binkp] Received frame header 0x%x 0x%x",
404 (unsigned char)binkpc->buf[0], (unsigned char)binkpc->buf[1]);
405 #endif
406
407 binkpc->cur_frame.type = (unsigned char)binkpc->buf[0] >> 7;
408 binkpc->cur_frame.data_size =
409 (unsigned short)((unsigned char)binkpc->buf[0] & 127) << 8;
410 binkpc->cur_frame.data_size |= (unsigned char)binkpc->buf[1];
411 if (binkpc->cur_frame.data_size == 0) {
412 logger_printf("[binkp] Received bogus frame, no data_size");
413 return false;
414 }
415
416 binkpc->last_frame_at = Time;
417
418 frame_data_read = 0;
419 while (frame_data_read < binkpc->cur_frame.data_size) {
420 error = _TCPStatus(&binkpc->tcp_read_iopb, binkpc->tcp_stream,
421 &binkpc->tcp_status_pb, nil, nil, false);
422 if (error)
423 goto failed_read;
424
425 if (!BINKP_CONNECTION_ALIVE(binkpc)) {
426 logger_printf("[binkp] Connection lost (%d) while reading frame",
427 binkpc->tcp_status_pb.connectionState);
428 goto failed_read;
429 }
430
431 if (binkpc->tcp_status_pb.amtUnreadData == 0) {
432 uthread_yield();
433 continue;
434 }
435
436 if (binkpc->cur_frame.type == BINKP_TYPE_COMMAND) {
437 if (frame_data_read >= binkpc->buf_size) {
438 /*
439 * Frame is too big but we can't overwrite buf since we
440 * need the start of the frame, so just read into a junk
441 * buffer and discard it
442 */
443 read_dest = tmp;
444 rlen = MIN(binkpc->cur_frame.data_size - frame_data_read,
445 sizeof(tmp));
446 } else {
447 read_dest = binkpc->buf + frame_data_read;
448 rlen = MIN(binkpc->buf_size - frame_data_read,
449 binkpc->cur_frame.data_size - frame_data_read);
450 }
451 } else {
452 read_dest = binkpc->buf;
453 rlen = MIN(binkpc->cur_frame.data_size - frame_data_read,
454 binkpc->buf_size);
455 }
456
457 rlen = MIN(rlen, binkpc->tcp_status_pb.amtUnreadData);
458
459 error = _TCPRcv(&binkpc->tcp_read_iopb, binkpc->tcp_stream,
460 read_dest, &rlen, nil, nil, false);
461 if (error)
462 goto failed_read;
463
464 binkpc->last_frame_at = Time;
465 frame_data_read += rlen;
466
467 if (binkpc->cur_frame.type == BINKP_TYPE_DATA) {
468 binkpc->cur_incoming_file.data_read += rlen;
469 #ifdef BINKP_DEBUG
470 logger_printf("[binkp] Read %d TCP chunk (%ld / %d)",
471 rlen, frame_data_read, binkpc->cur_frame.data_size);
472 #endif
473
474 if (binkpc->cur_incoming_file.frefnum == 0) {
475 logger_printf("[binkp] Received data frame before "
476 "M_FILE");
477 goto failed_read;
478 }
479
480 if (binkpc->cur_incoming_file.data_read >
481 binkpc->cur_incoming_file.size) {
482 logger_printf("[binkp] Received %ld bytes for %s but "
483 "size is %ld", binkpc->cur_incoming_file.data_read,
484 binkpc->cur_incoming_file.filename,
485 binkpc->cur_incoming_file.size);
486 goto failed_read;
487 }
488
489 len = rlen;
490 error = FSWrite(binkpc->cur_incoming_file.frefnum, &len,
491 binkpc->buf);
492 if (error) {
493 logger_printf("[binkp] Error writing %u to %s: %d",
494 rlen, PtoCstr(binkpc->cur_incoming_file.pfilename),
495 error);
496 CtoPstr(binkpc->cur_incoming_file.pfilename);
497 binkpc->done_receiving = binkpc->done_sending = true;
498 goto failed_read;
499 }
500 }
501 }
502
503 if (frame_data_read > binkpc->cur_frame.data_size)
504 panic("binkp data overread");
505
506 if (binkpc->cur_frame.type == BINKP_TYPE_COMMAND) {
507 binkpc->cur_frame.command_id = binkpc->buf[0];
508 if (frame_data_read < binkpc->buf_size)
509 binkpc->buf[frame_data_read] = '\0';
510 else
511 binkpc->buf[binkpc->buf_size - 1] = '\0';
512
513 #ifdef BINKP_DEBUG
514 logger_printf("[binkp] Read command 0x%x frame [%ld]: %s",
515 binkpc->cur_frame.command_id,
516 frame_data_read, binkpc->buf + (frame_data_read > 0 ? 1 : 0));
517 #endif
518
519 switch (binkpc->cur_frame.command_id) {
520 case BINKP_COMMAND_M_NUL:
521 if (strncmp(binkpc->buf + 1, "SYS ", 4) == 0)
522 logger_printf("[binkp] Connected to %s",
523 binkpc->buf + 1 + 4);
524 else if (strncmp(binkpc->buf + 1, "TIME ", 5) == 0) {
525 /*
526 * TODO: try to parse timezone and pass to fidopkt, so
527 * any packets without a TZUTC line can use the hub's
528 * timezone.
529 */
530 }
531 break;
532 case BINKP_COMMAND_M_FILE:
533 if (binkpc->cur_incoming_file.filename[0]) {
534 logger_printf("[binkp] Received M_FILE but not done "
535 "with file %s!", binkpc->cur_incoming_file.filename);
536 binkpc->cur_incoming_file.filename[0] = '\0';
537 if (binkpc->cur_incoming_file.frefnum > 0) {
538 FSClose(binkpc->cur_incoming_file.frefnum);
539 binkpc->cur_incoming_file.frefnum = 0;
540 }
541 }
542
543 if (sscanf(binkpc->buf + 1, "%64s %lu %lu %lu%n",
544 binkpc->cur_incoming_file.filename,
545 &binkpc->cur_incoming_file.size,
546 &binkpc->cur_incoming_file.mtime, &off, &slen) == 4) {
547 if (!binkp_valid_filename(
548 binkpc->cur_incoming_file.filename)) {
549 logger_printf("[binkp] Refusing M_FILE with bad "
550 "filename \"%s\"",
551 binkpc->cur_incoming_file.filename);
552 binkp_send_frame(BINKP_COMMAND_M_SKIP,
553 binkpc->buf + 1, slen);
554 binkpc->cur_incoming_file.filename[0] = '\0';
555 /* not an error */
556 return true;
557 }
558
559 logger_printf("[binkp] Receiving file \"%s\" size %ld",
560 binkpc->cur_incoming_file.filename,
561 binkpc->cur_incoming_file.size);
562 if (off != 0)
563 logger_printf("[binkp] Non-zero file fetch offset not "
564 "supported");
565 binkpc->cur_incoming_file.data_read = 0;
566
567 PtoCstr(binkp_inbox_dir);
568 snprintf((char *)binkpc->cur_incoming_file.pfilename,
569 sizeof(binkpc->cur_incoming_file.pfilename),
570 "%s:%s", binkp_inbox_dir,
571 binkpc->cur_incoming_file.filename);
572 CtoPstr(binkp_inbox_dir);
573 CtoPstr(binkpc->cur_incoming_file.pfilename);
574
575 error = Create(binkpc->cur_incoming_file.pfilename, 0,
576 SUBTEXT_CREATOR, 'BINK');
577 if (error == dupFNErr) {
578 FSDelete(binkpc->cur_incoming_file.pfilename, 0);
579 error = Create(binkpc->cur_incoming_file.pfilename, 0,
580 SUBTEXT_CREATOR, 'BINK');
581 }
582 if (error) {
583 warn("Failed creating %s: %d",
584 PtoCstr(binkpc->cur_incoming_file.pfilename), error);
585 CtoPstr(binkpc->cur_incoming_file.pfilename);
586 goto failed_read;
587 }
588
589 error = FSOpen(binkpc->cur_incoming_file.pfilename, 0,
590 &binkpc->cur_incoming_file.frefnum);
591 if (error) {
592 warn("Failed opening %s: %d",
593 PtoCstr(binkpc->cur_incoming_file.pfilename), error);
594 CtoPstr(binkpc->cur_incoming_file.pfilename);
595 goto failed_read;
596 }
597
598 len = binkpc->cur_incoming_file.size;
599 error = Allocate(binkpc->cur_incoming_file.frefnum, &len);
600 if (error) {
601 warn("Failed setting %s to size %ld: %d",
602 PtoCstr(binkpc->cur_incoming_file.pfilename),
603 binkpc->cur_incoming_file.size, error);
604 CtoPstr(binkpc->cur_incoming_file.pfilename);
605 }
606 } else {
607 logger_printf("[binkp] Failed parsing M_FILE: %s",
608 binkpc->buf + 1);
609 }
610 break;
611 case BINKP_COMMAND_M_EOB:
612 binkpc->done_receiving = true;
613 break;
614 case BINKP_COMMAND_M_ERR:
615 logger_printf("[binkp] Error from remote: %s",
616 binkpc->buf + 1);
617 binkpc->done_receiving = binkpc->done_sending = true;
618 break;
619 case BINKP_COMMAND_M_BSY:
620 logger_printf("[binkp] Remote is busy: %s", binkpc->buf + 1);
621 binkpc->done_receiving = binkpc->done_sending = true;
622 break;
623 }
624 } else {
625 #ifdef BINKP_DEBUG
626 logger_printf("[binkp] Read %d data frame of file (%ld / %ld)",
627 binkpc->cur_frame.data_size,
628 binkpc->cur_incoming_file.data_read,
629 binkpc->cur_incoming_file.size);
630 #endif
631 if (binkpc->cur_incoming_file.data_read ==
632 binkpc->cur_incoming_file.size) {
633 #ifdef BINKP_DEBUG
634 logger_printf("[binkp] Done reading file %s (%ld)",
635 binkpc->cur_incoming_file.filename,
636 binkpc->cur_incoming_file.size);
637 #endif
638 FSClose(binkpc->cur_incoming_file.frefnum);
639 binkpc->cur_incoming_file.frefnum = 0;
640
641 len = snprintf(tmp, sizeof(tmp), "%s %lu %lu",
642 binkpc->cur_incoming_file.filename,
643 binkpc->cur_incoming_file.size,
644 binkpc->cur_incoming_file.mtime);
645 if (len >= sizeof(tmp)) {
646 logger_printf("[binkp] M_GOT snprintf overflow");
647 goto failed_read;
648 }
649 if (!binkp_send_frame(BINKP_COMMAND_M_GOT, tmp, len))
650 logger_printf("[binkp] Failed sending M_GOT %s",
651 binkpc->cur_incoming_file.filename);
652
653 binkpc->cur_incoming_file.filename[0] = '\0';
654 }
655 }
656
657 return true;
658
659 failed_read:
660 if (binkpc->cur_incoming_file.frefnum > 0) {
661 FSClose(binkpc->cur_incoming_file.frefnum);
662 binkpc->cur_incoming_file.frefnum = 0;
663 }
664 binkpc->done_receiving = binkpc->done_sending = true;
665 binkpc->error = true;
666 return false;
667 }
668
669 bool
670 binkp_login(void)
671 {
672 char command[50];
673 size_t len;
674
675 /* discard early frames */
676 while (binkpc != NULL && binkp_read_frame())
677 ;
678
679 if (!binkpc || binkpc->error)
680 return false;
681
682 len = snprintf(command, sizeof(command), "SYS %s", db->config.name);
683 if (len >= sizeof(command)) {
684 logger_printf("[binkp] SYS snprintf overflow");
685 return false;
686 }
687 if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len))
688 return false;
689
690 len = snprintf(command, sizeof(command), "LOC %s", db->config.location);
691 if (len >= sizeof(command)) {
692 logger_printf("[binkp] LOC snprintf overflow");
693 return false;
694 }
695 if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len))
696 return false;
697
698 len = snprintf(command, sizeof(command), "NDL 14400,TCP,BINKP");
699 if (len >= sizeof(command)) {
700 logger_printf("[binkp] NDL snprintf overflow");
701 return false;
702 }
703 if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len))
704 return false;
705
706 len = snprintf(command, sizeof(command), "VER Subtext/%s binkp/1.0",
707 get_version(false));
708 if (len >= sizeof(command)) {
709 logger_printf("[binkp] VER snprintf overflow");
710 return false;
711 }
712 if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len))
713 return false;
714
715 if (!binkp_send_frame(BINKP_COMMAND_M_ADR,
716 db->config.ftn_node_addr, strlen(db->config.ftn_node_addr)))
717 return false;
718
719 if (!binkp_send_frame(BINKP_COMMAND_M_PWD, db->config.binkp_password,
720 strlen(db->config.binkp_password)))
721 return false;
722
723 while (binkpc && !binkpc->error) {
724 if (!binkp_read_frame()) {
725 uthread_yield();
726
727 if (Time - binkpc->last_frame_at > 30) {
728 logger_printf("[binkp] No frame received in %ld seconds, "
729 "aborting", (Time - binkpc->last_frame_at));
730 binkpc->error = true;
731 break;
732 }
733 continue;
734 }
735
736 if (binkpc->cur_frame.type != BINKP_TYPE_COMMAND)
737 continue;
738
739 if (binkpc->cur_frame.command_id == BINKP_COMMAND_M_ERR) {
740 logger_printf("[binkp] Login as %s failed, disconnecting",
741 db->config.ftn_node_addr);
742 binkp_free();
743 return false;
744 }
745
746 if (binkpc->cur_frame.command_id == BINKP_COMMAND_M_OK) {
747 logger_printf("[binkp] Logged in successfully as %s",
748 db->config.ftn_node_addr);
749 return true;
750 }
751 }
752
753 return false;
754 }
755
756 void
757 binkp_toss_inbox(void)
758 {
759 CInfoPBRec cipbr = { 0 };
760 HFileInfo *fpb = (HFileInfo *)&cipbr;
761 DirInfo *dpb = (DirInfo *)&cipbr;
762 CMovePBRec cmpbr = { 0 };
763 Str255 file_name_c, path, done_path;
764 short dir_id, error, zret;
765 unsigned long started = Time, elapsed;
766 ssize_t data_size;
767 char *data, *errmail;
768 bool bad;
769
770 binkp_fidopkt_skipped = 0;
771 binkp_fidopkt_imported = 0;
772 binkp_temp_fail = false;
773
774 fpb->ioVRefNum = 0;
775 fpb->ioNamePtr = (StringPtr)&binkp_inbox_dir;
776 error = PBGetCatInfo(&cipbr, false);
777 if (error) {
778 logger_printf("[binkp] PBGetCatInfo on binkp dir failed: %d",
779 error);
780 return;
781 }
782 dir_id = dpb->ioDrDirID;
783
784 fpb->ioNamePtr = (StringPtr)&file_name_c;
785 for (;;) {
786 file_name_c[0] = 0;
787 fpb->ioDirID = dir_id;
788 /*
789 * Keep requesting the first item in the directory since we're
790 * deleting or moving each file as we process it
791 */
792 fpb->ioFDirIndex = 1;
793
794 error = PBGetCatInfo(&cipbr, false);
795 if (error)
796 break;
797
798 PtoCstr(file_name_c);
799
800 PtoCstr(binkp_inbox_dir);
801 snprintf((char *)path, sizeof(path), "%s:%s",
802 binkp_inbox_dir, file_name_c);
803 logger_printf("[binkp] Tossing file %s", (char *)path);
804 CtoPstr(path);
805 CtoPstr(binkp_inbox_dir);
806
807 binkp_fidopkt_skipped = 0;
808 binkp_fidopkt_imported = 0;
809 bad = false;
810
811 if (zip_is_zip_file(path)) {
812 zret = zip_read_file(path, binkp_zip_decider,
813 binkp_fidopkt_processor);
814 if (zret == ZIP_NO_MEMORY)
815 binkp_temp_fail = true;
816 else if (zret != ZIP_OK) {
817 bad = true;
818 logger_printf("[binkp] Failed processing ZIP file %s: %d",
819 file_name_c, zret);
820 }
821 } else if (strstr((char *)file_name_c, ".pkt")) {
822 data_size = binkp_buffer_file(path, &data);
823 if (data_size == 0)
824 binkp_temp_fail = true;
825 else if (data_size == -1) {
826 logger_printf("[binkp] Permanent failure, skipping file");
827 bad = true;
828 } else {
829 binkp_fidopkt_processor((char *)file_name_c,
830 (unsigned char *)data, data_size);
831 xfree(&data);
832 }
833 }
834
835 if (binkp_temp_fail) {
836 logger_printf("[binkp] Temporary failure during tossing, "
837 "aborting");
838 binkp_temp_fail = false;
839 return;
840 }
841
842 logger_printf("[binkp] %s: tossed %d, skipped %d",
843 (char *)file_name_c, binkp_fidopkt_imported,
844 binkp_fidopkt_skipped);
845
846 if (db->config.binkp_delete_done && !bad) {
847 if ((error = FSDelete(path, 0)) != 0) {
848 logger_printf("[binkp] Failed deleting inbox file %s: %d",
849 PtoCstr(path), error);
850 break;
851 }
852 } else {
853 if (bad) {
854 PtoCstr(binkp_bad_dir);
855 snprintf((char *)done_path, sizeof(done_path),
856 "%s:%s", binkp_bad_dir, file_name_c);
857
858 errmail = xmalloc(512);
859 if (errmail != NULL) {
860 snprintf(errmail, 512,
861 "A permanent error occurred while tossing file "
862 "\"%s\" and it\r\n"
863 "has been moved to the \"bad\" folder for further "
864 "inspection:\r\n\r\n%s",
865 file_name_c, done_path);
866
867 mail_to_sysop("Failed tossing file", errmail);
868 xfree(&errmail);
869 }
870
871 CtoPstr(done_path);
872 CtoPstr(binkp_bad_dir);
873 } else {
874 PtoCstr(binkp_done_dir);
875 snprintf((char *)done_path, sizeof(done_path),
876 "%s:%s", binkp_done_dir, file_name_c);
877 CtoPstr(done_path);
878 CtoPstr(binkp_done_dir);
879 }
880
881 try_rename:
882 cmpbr.ioNamePtr = path;
883 if (bad)
884 cmpbr.ioNewName = binkp_bad_dir;
885 else
886 cmpbr.ioNewName = binkp_done_dir;
887 cmpbr.ioDirID = 0;
888 cmpbr.ioNewDirID = 0;
889 cmpbr.ioVRefNum = 0;
890 error = PBCatMove(&cmpbr, false);
891 if (error == dupFNErr) {
892 if ((error = FSDelete(done_path, 0)) != 0)
893 panic("[binkp] Failed deleting %s: %d",
894 PtoCstr(done_path), error);
895 goto try_rename;
896 } else if (error) {
897 panic("[binkp] Failed moving %s to %s: %d",
898 PtoCstr(path), PtoCstr(done_path), error);
899 }
900 }
901 }
902
903 if (binkp_fidopkt_skipped == 0 && binkp_fidopkt_imported == 0)
904 return;
905
906 elapsed = Time - started;
907 logger_printf("[binkp] Done tossing in %ld sec%s", elapsed,
908 elapsed == 1 ? "" : "s");
909 }
910
911 bool
912 binkp_zip_decider(char *filename, size_t size)
913 {
914 size_t flen = strlen(filename);
915
916 if (flen >= 4 && strcmp(filename + flen - 4, ".pkt") == 0)
917 return true;
918
919 return false;
920 }
921
922 void
923 binkp_fidopkt_processor(char *filename, unsigned char *data, size_t size)
924 {
925 struct fidopkt_message *msg = NULL;
926 struct fidopkt_header *header = NULL;
927 struct board *board = NULL;
928 short n, ret;
929
930 uthread_yield();
931
932 while (size != 0 && (msg = fidopkt_parse_message(filename, header,
933 (char **)&data, &size)) != NULL) {
934 if (msg->time)
935 /* convert to local time zone */
936 msg->time +=
937 (((long)db->config.timezone_utcoff * 60 * 60) / 100);
938 else
939 msg->time = Time;
940
941 if (msg->area[0]) {
942 board = NULL;
943
944 for (n = 0; n < db->nboards; n++) {
945 if (db->boards[n].ftn_area[0] &&
946 strcasecmp(msg->area, db->boards[n].ftn_area) == 0) {
947 board = &db->boards[n];
948 break;
949 }
950 }
951
952 if (!board) {
953 #ifdef BINKP_DEBUG
954 logger_printf("[fidopkt] No local board for %s, skipping",
955 msg->area);
956 #endif
957 binkp_fidopkt_skipped++;
958 goto next_message;
959 }
960
961 ret = board_toss_ftn_message(board, msg, false);
962 } else
963 ret = mail_toss_ftn_message(msg);
964
965 if (ret == -1) {
966 binkp_temp_fail = true;
967 fidopkt_message_free(&msg);
968 break;
969 }
970 if (ret == 0)
971 binkp_fidopkt_skipped++;
972 else
973 binkp_fidopkt_imported++;
974
975 next_message:
976 if (header == NULL && size > 0) {
977 header = xmalloc(sizeof(msg->header));
978 if (header == NULL) {
979 fidopkt_message_free(&msg);
980 binkp_temp_fail = true;
981 break;
982 }
983 memcpy(header, &msg->header, sizeof(struct fidopkt_header));
984 }
985 fidopkt_message_free(&msg);
986 }
987
988 if (header)
989 xfree(&header);
990 }
991
992 ssize_t
993 binkp_buffer_file(Str255 path, char **data)
994 {
995 struct stat sb;
996 long count;
997 short error, frefnum;
998
999 if (FStat(path, &sb) != 0)
1000 return -1;
1001
1002 if (sb.st_size == 0)
1003 return -1;
1004
1005 *data = xmalloc(sb.st_size);
1006 if (*data == NULL)
1007 return 0;
1008
1009 error = FSOpen(path, 0, &frefnum);
1010 if (error) {
1011 xfree(data);
1012 return -1;
1013 }
1014
1015 count = sb.st_size;
1016 error = FSRead(frefnum, &count, *data);
1017 FSClose(frefnum);
1018 if (error && error != eofErr) {
1019 xfree(data);
1020 return -1;
1021 }
1022
1023 return count;
1024 }
1025
1026 bool
1027 binkp_valid_filename(char *filename)
1028 {
1029 size_t len, n;
1030 unsigned char c;
1031
1032 len = strlen(filename);
1033 if (!len)
1034 return false;
1035
1036 for (n = 0; n < len; n++) {
1037 c = filename[n];
1038
1039 if (n == 0 && c == '.')
1040 return false;
1041
1042 if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
1043 (c >= 'a' && c <= 'z') || c == '_' || c == '-' || c == '+' ||
1044 (c == '.')))
1045 return false;
1046 }
1047
1048 return true;
1049 }
1050
1051 void
1052 binkp_deliver_outbox(void)
1053 {
1054 Str255 file_name_c, path, done_path;
1055 char command[50];
1056 CInfoPBRec cipbr = { 0 };
1057 HFileInfo *fpb = (HFileInfo *)&cipbr;
1058 DirInfo *dpb = (DirInfo *)&cipbr;
1059 CMovePBRec cmpbr = { 0 };
1060 struct stat sb;
1061 size_t len, fsize_left;
1062 short dir_id, error, frefnum = 0;
1063 char *data = NULL;
1064
1065 fpb->ioVRefNum = 0;
1066 fpb->ioNamePtr = (StringPtr)&binkp_outbox_dir;
1067 error = PBGetCatInfo(&cipbr, false);
1068 if (error) {
1069 logger_printf("[binkp] PBGetCatInfo on binkp outbox failed: %d",
1070 error);
1071 return;
1072 }
1073 dir_id = dpb->ioDrDirID;
1074
1075 fpb->ioNamePtr = (StringPtr)&file_name_c;
1076 for (;;) {
1077 file_name_c[0] = 0;
1078 fpb->ioDirID = dir_id;
1079 /*
1080 * Keep requesting the first item in the directory since we're
1081 * deleting or moving each file as we process it.
1082 */
1083 fpb->ioFDirIndex = 1;
1084
1085 error = PBGetCatInfo(&cipbr, false);
1086 if (error)
1087 break;
1088
1089 if (data == NULL) {
1090 data = xmalloc(binkpc->buf_size);
1091 if (data == NULL) {
1092 logger_printf("[binkp] Failed allocating outbound buffer "
1093 "%lu", binkpc->buf_size);
1094 return;
1095 }
1096 }
1097
1098 PtoCstr(file_name_c);
1099
1100 PtoCstr(binkp_outbox_dir);
1101 snprintf((char *)path, sizeof(path), "%s:%s",
1102 binkp_outbox_dir, file_name_c);
1103 CtoPstr(path);
1104 CtoPstr(binkp_outbox_dir);
1105
1106 FStat(path, &sb);
1107
1108 logger_printf("[binkp] Sending file %s size %lu", file_name_c,
1109 sb.st_size);
1110
1111 len = snprintf(command, sizeof(command), "%s %lu %lu 0",
1112 file_name_c, sb.st_size, MAC_TO_UNIX_TIME(sb.st_ctime));
1113 if (len >= sizeof(command)) {
1114 logger_printf("[binkp] M_FILE snprintf overflow");
1115 goto done;
1116 }
1117 if (!binkp_send_frame(BINKP_COMMAND_M_FILE, command, len))
1118 goto done;
1119
1120 error = FSOpen(path, 0, &frefnum);
1121 if (error) {
1122 warn("binkp: failed opening %s: %d", PtoCstr(path), error);
1123 goto done;
1124 }
1125
1126 for (fsize_left = sb.st_size; fsize_left != 0; ) {
1127 /* leave 2 bytes for size */
1128 len = MIN(fsize_left, binkpc->buf_size - 2);
1129
1130 error = FSRead(frefnum, &len, data);
1131 if (error && error != eofErr) {
1132 warn("binkp: error reading from %s: %d", PtoCstr(path),
1133 error);
1134 goto done;
1135 }
1136
1137 if (!binkp_send_frame(BINKP_DATA, data, len))
1138 goto done;
1139
1140 fsize_left -= len;
1141 }
1142
1143 FSClose(frefnum);
1144 frefnum = 0;
1145
1146 /* wait for frame */
1147 while (binkpc != NULL && !binkp_read_frame() && !binkpc->error) {
1148 if (Time - binkpc->last_frame_at > 30) {
1149 logger_printf("[binkp] No frame received in %ld seconds, "
1150 "aborting", (Time - binkpc->last_frame_at));
1151 binkpc->error = true;
1152 goto done;
1153 }
1154 uthread_yield();
1155 continue;
1156 }
1157
1158 if (!binkpc || binkpc->error) {
1159 logger_printf("[binkp] Connection lost waiting for M_GOT");
1160 goto done;
1161 }
1162
1163 if (binkpc->cur_frame.type == BINKP_TYPE_COMMAND &&
1164 binkpc->cur_frame.command_id == BINKP_COMMAND_M_SKIP) {
1165 logger_printf("[binkp] Received SKIP response "
1166 "to sending file %s, will retry during next connection",
1167 file_name_c);
1168 goto send_eob;
1169 }
1170
1171 if (binkpc->cur_frame.type != BINKP_TYPE_COMMAND ||
1172 binkpc->cur_frame.command_id != BINKP_COMMAND_M_GOT) {
1173 logger_printf("[binkp] Received unexpected response "
1174 "(0x%x 0x%x) to sending file, aborting",
1175 binkpc->cur_frame.type, binkpc->cur_frame.command_id);
1176 goto done;
1177 }
1178
1179 logger_printf("[binkp] Successfully sent file %s", file_name_c);
1180
1181 if (db->config.binkp_delete_done) {
1182 if ((error = FSDelete(path, 0)))
1183 /* this must be fatal so we don't keep trying this file */
1184 panic("[binkp] Failed deleting outbox file %s: %d",
1185 PtoCstr(path), error);
1186 } else {
1187 PtoCstr(binkp_done_dir);
1188 snprintf((char *)done_path, sizeof(done_path),
1189 "%s:%s", binkp_done_dir, file_name_c);
1190 CtoPstr(done_path);
1191 CtoPstr(binkp_done_dir);
1192
1193 try_rename:
1194 cmpbr.ioNamePtr = path;
1195 cmpbr.ioNewName = binkp_done_dir;
1196 cmpbr.ioDirID = 0;
1197 cmpbr.ioNewDirID = 0;
1198 cmpbr.ioVRefNum = 0;
1199 error = PBCatMove(&cmpbr, false);
1200 if (error == dupFNErr) {
1201 error = FSDelete(done_path, 0);
1202 if (error)
1203 panic("[binkp] Failed deleting %s: %d",
1204 PtoCstr(done_path), error);
1205 goto try_rename;
1206 } else if (error) {
1207 panic("[binkp] Failed moving %s to %s: %d",
1208 PtoCstr(path), PtoCstr(done_path), error);
1209 }
1210 }
1211 }
1212
1213 send_eob:
1214 if (!binkp_send_frame(BINKP_COMMAND_M_EOB, "", 0))
1215 logger_printf("[binkp] Failed sending M_EOB");
1216
1217 if (binkpc)
1218 binkpc->done_sending = true;
1219
1220 done:
1221 if (data)
1222 xfree(&data);
1223 if (frefnum)
1224 FSClose(frefnum);
1225 }
1226
1227 bool
1228 binkp_packets_in_outbox(void)
1229 {
1230 CInfoPBRec cipbr = { 0 };
1231 HFileInfo *fpb = (HFileInfo *)&cipbr;
1232 DirInfo *dpb = (DirInfo *)&cipbr;
1233 Str255 file_name_c;
1234 short dir_id, error;
1235
1236 fpb->ioVRefNum = 0;
1237 fpb->ioNamePtr = (StringPtr)&binkp_outbox_dir;
1238 error = PBGetCatInfo(&cipbr, false);
1239 if (error) {
1240 logger_printf("[binkp] PBGetCatInfo on binkp outbox failed: %d",
1241 error);
1242 return false;
1243 }
1244 dir_id = dpb->ioDrDirID;
1245
1246 fpb->ioNamePtr = (StringPtr)&file_name_c;
1247 file_name_c[0] = 0;
1248 fpb->ioDirID = dir_id;
1249 fpb->ioFDirIndex = 1;
1250
1251 error = PBGetCatInfo(&cipbr, false);
1252 if (error)
1253 return false;
1254
1255 return true;
1256 }
1257
1258 bool
1259 binkp_scan_message(struct fidopkt_message *msg)
1260 {
1261 Str255 path;
1262 char filename[32];
1263 char *buf = NULL;
1264 short error, frefnum = 0;
1265 size_t size, asize;
1266 bool ret = false;
1267
1268 size = fidopkt_encode_message(msg, &buf,
1269 db->config.ftn_hub_pkt_password, db->config.timezone_utcoff);
1270 if (size == 0) {
1271 logger_printf("[binkp] Failed encoding fidopkt during scan");
1272 return false;
1273 }
1274
1275 if (binkp_outbox_dir[0] == 0) {
1276 warn("binkp: outbox dir is empty, can't store scanned packet");
1277 goto done;
1278 }
1279
1280 snprintf(filename, sizeof(filename), "%c%07lx.pkt",
1281 msg->area[0] ? 'b' : 'm', msg->msgid.id & 0x0fffffff);
1282
1283 PtoCstr(binkp_outbox_dir);
1284 snprintf((char *)path, sizeof(path), "%s:%s", binkp_outbox_dir,
1285 filename);
1286 CtoPstr(binkp_outbox_dir);
1287 CtoPstr(path);
1288
1289 error = Create(path, 0, SUBTEXT_CREATOR, FTN_SPOOL_PACKET_TYPE);
1290 if (error == dupFNErr) {
1291 warn("binkp: collision saving packet to %s", PtoCstr(path));
1292 goto done;
1293 }
1294 if (error) {
1295 warn("binkp: failed creating %s: %d", PtoCstr(path), error);
1296 goto done;
1297 }
1298
1299 error = FSOpen(path, 0, &frefnum);
1300 if (error) {
1301 warn("binkp: failed opening newly created %s: %d", PtoCstr(path),
1302 error);
1303 goto done;
1304 }
1305
1306 asize = size;
1307 error = Allocate(frefnum, &asize);
1308 if (error) {
1309 warn("binkp: failed setting %s to size %ld: %d", PtoCstr(path),
1310 size, error);
1311 goto done;
1312 }
1313
1314 error = FSWrite(frefnum, &size, buf);
1315 if (error) {
1316 logger_printf("[binkp] Error writing %lu to %s: %d",
1317 size, PtoCstr(path), error);
1318 goto done;
1319 }
1320
1321 ret = true;
1322
1323 done:
1324 if (frefnum)
1325 FSClose(frefnum);
1326 xfree(&buf);
1327 return ret;
1328 }