AmendHub

Download

jcs

/

subtext

/

binkp.c

 

(View History)

jcs   binkp: Always set next poll to interval seconds even on failure Latest amendment: 489 on 2023-04-26

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