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