AmendHub

Download

jcs

/

subtext

/

binkp.c

 

(View History)

jcs   *: Minor cleanups, remove dead variables, fix some error paths Latest amendment: 580 on 2024-01-24

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