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 | } |