/* * FTN Binkp * https://www.ritlabs.com/binkp/ * * Copyright (c) 2023 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include "binkp.h" #include "board.h" #include "fidopkt.h" #include "logger.h" #include "mail.h" #include "subtext.h" #include "zip.h" /* #define BINKP_DEBUG */ #define BINKP_CONNECTION_ALIVE(bpc) \ (bpc->tcp_status_pb.connectionState == ConnectionStateEstablished || \ bpc->tcp_status_pb.connectionState == ConnectionStateCloseWait) bool binkp_ready = false; time_t binkp_next_poll = 0; bool binkp_last_poll_error = false; static struct fidopkt_address binkp_our_address = { 0 }; static struct binkp_connection *binkpc = NULL; static Str255 binkp_inbox_dir, binkp_done_dir, binkp_outbox_dir, binkp_bad_dir; static short binkp_fidopkt_skipped, binkp_fidopkt_imported; static bool binkp_temp_fail = false; struct uthread *binkp_thread = NULL; void binkp_mkdir(Str255 dir_str, char *suffix); bool binkp_connect(void); void binkp_free(void); size_t binkp_send_frame(short command, char *data, size_t data_size); bool binkp_read_frame(void); bool binkp_login(void); void binkp_ingest(void); bool binkp_zip_decider(char *filename, size_t size); void binkp_fidopkt_processor(char *filename, unsigned char *data, size_t size); ssize_t binkp_buffer_file(Str255 path, char **data); void binkp_toss_inbox(void); void binkp_deliver_outbox(void); void binkp_init(void) { Str255 base_dir; binkp_ready = false; binkp_mkdir(base_dir, NULL); binkp_mkdir(binkp_inbox_dir, "inbox"); binkp_mkdir(binkp_done_dir, "processed"); binkp_mkdir(binkp_outbox_dir, "outbox"); binkp_mkdir(binkp_bad_dir, "bad"); if (db->config.binkp_hostname[0] == '\0') return; if (!fidopkt_parse_address(db->config.ftn_node_addr, &binkp_our_address)) { logger_printf("[binkp] Failed parsing local FTN node address " "\"%s\", fix in settings", db->config.ftn_node_addr); return; } binkp_ready = true; } bool binkp_reinit(void) { binkp_init(); return true; } void binkp_mkdir(Str255 dir_str, char *suffix) { short error; long id; if (getpath(db->bile->vrefnum, db->bile->filename, dir_str, false) != 0) panic("getpath failed on %s", PtoCstr(db->bile->filename)); PtoCstr(dir_str); strlcat((char *)dir_str, ":binkp", sizeof(Str255)); if (suffix != NULL) { strlcat((char *)dir_str, ":", sizeof(Str255)); strlcat((char *)dir_str, suffix, sizeof(Str255)); } CtoPstr(dir_str); if (!FIsDir(dir_str)) { error = DirCreate(db->bile->vrefnum, 0, dir_str, &id); if (error) panic("Failed creating %s: %d", PtoCstr(dir_str), error); } } void binkp_atexit(void) { if (binkpc != NULL) binkp_free(); } void binkp_poll(void) { unsigned long started = Time, elapsed; binkp_last_poll_error = false; binkp_connect(); if (!binkpc) { binkp_last_poll_error = true; goto done; } if (!binkp_login()) { binkp_last_poll_error = true; goto done; } while (binkpc != NULL && !binkpc->done_receiving && !binkpc->error) { binkp_read_frame(); uthread_yield(); } if (binkpc) binkp_deliver_outbox(); if (binkpc) _TCPClose(&binkpc->tcp_send_iopb, binkpc->tcp_stream, nil, nil, false); elapsed = Time - started; logger_printf("[binkp] Finished polling in %ld sec%s", elapsed, elapsed == 1 ? "" : "s"); done: if (binkpc->error) binkp_last_poll_error = true; if (binkpc != NULL) binkp_free(); binkp_next_poll = Time + db->config.binkp_interval_seconds; binkp_toss_inbox(); } bool binkp_connect(void) { char ip_s[16]; char *hostname; ip_addr local_ip, host_ip; tcp_port local_port; short error; if (_TCPInit() != noErr) panic("Failed initializing MacTCP"); if (binkpc != NULL) binkp_free(); binkpc = xmalloczero(sizeof(struct binkp_connection)); if (binkpc == NULL) { logger_printf("[binkp] Failed allocating connection of size %ld", sizeof(struct binkp_connection)); goto error; } binkpc->buf_size = 1024; binkpc->buf = xmalloc(binkpc->buf_size); if (binkpc->buf == NULL) { logger_printf("[binkp] Failed allocating connection buffer of " "size %ld", binkpc->buf_size); goto error; } binkpc->tcp_buf_size = (4 * 1500) + binkpc->buf_size; binkpc->tcp_buf = xmalloc(binkpc->tcp_buf_size); if (binkpc->tcp_buf == NULL) { logger_printf("[binkp] Failed allocating TCP buffer of " "size %ld", binkpc->tcp_buf_size); goto error; } hostname = db->config.binkp_hostname; error = DNSResolveName(hostname, &host_ip, uthread_yield); if (error) { logger_printf("[binkp] Failed resolving binkp host %s: %d", db->config.binkp_hostname, error); goto error; } error = _TCPCreate(&binkpc->tcp_send_iopb, &binkpc->tcp_stream, (Ptr)binkpc->tcp_buf, binkpc->tcp_buf_size, nil, nil, nil, false); if (error) { logger_printf("[binkp] Failed creating TCP stream: %d", error); goto error; } long2ip(host_ip, (char *)&ip_s); logger_printf("[binkp] Connecting to %s (%s) port %d", db->config.binkp_hostname, ip_s, db->config.binkp_port); error = _TCPActiveOpen(&binkpc->tcp_send_iopb, binkpc->tcp_stream, host_ip, db->config.binkp_port, &local_ip, &local_port, nil, nil, true); while (!error && binkpc->tcp_send_iopb.ioResult > 0) uthread_yield(); if (error || binkpc->tcp_send_iopb.ioResult != 0) { logger_printf("[binkp] Failed connecting to %s (%s) port %d: %d", db->config.binkp_hostname, ip_s, db->config.binkp_port, binkpc->tcp_send_iopb.ioResult); goto error; } binkpc->last_frame_at = Time; return true; error: binkp_free(); return false; } void binkp_free(void) { if (binkpc == NULL) return; if (binkpc->tcp_stream) _TCPRelease(&binkpc->tcp_send_iopb, binkpc->tcp_stream, nil, nil, false); if (binkpc->tcp_buf != NULL) xfree(&binkpc->tcp_buf); if (binkpc->buf != NULL) xfree(&binkpc->buf); xfree(&binkpc); } size_t binkp_send_frame(short command, char *data, size_t data_size) { u_int16_t flen; short error; if (binkpc == NULL) return 0; flen = data_size + 2; if (command != BINKP_DATA) flen++; if (flen > binkpc->buf_size) { logger_printf("[binkp] Frame too large (%u), can't send", flen); return 0; } while (binkpc->tcp_send_iopb.ioResult > 0) { /* previous _TCPSend has not completed yet */ if (!BINKP_CONNECTION_ALIVE(binkpc)) { binkpc->error = true; logger_printf("[binkp] Connection lost (%d) while sending frame", binkpc->tcp_status_pb.connectionState); return false; } uthread_yield(); } if (command == BINKP_DATA) { binkpc->buf[0] = (data_size >> 8) & 0xff; binkpc->buf[1] = data_size & 0xff; flen = data_size + 2; memcpy(binkpc->buf + 2, data, data_size); } else { binkpc->buf[0] = (((data_size + 1) | 0x8000) >> 8) & 0xff; binkpc->buf[1] = (data_size + 1) & 0xff; binkpc->buf[2] = command; flen = data_size + 3; memcpy(binkpc->buf + 3, data, data_size); /* not sent, just for debugging */ binkpc->buf[3 + data_size] = '\0'; } memset(&binkpc->tcp_wds, 0, sizeof(binkpc->tcp_wds)); binkpc->tcp_wds[0].ptr = (Ptr)binkpc->buf; binkpc->tcp_wds[0].length = flen; #ifdef BINKP_DEBUG switch (command) { case BINKP_COMMAND_M_PWD: logger_printf("[binkp] Sending password command of size %ld", data_size); break; case BINKP_COMMAND_M_FILE: logger_printf("[binkp] Sending new file: %s", binkpc->buf + 3); break; case BINKP_COMMAND_M_EOB: logger_printf("[binkp] Sending EOB"); break; case BINKP_COMMAND_M_GOT: logger_printf("[binkp] Sending GOT for %s", binkpc->cur_incoming_file.filename); break; case BINKP_DATA: logger_printf("[binkp] Sending data of size %ld", data_size); break; default: logger_printf("[binkp] Sending command %d of size %ld: %s", command, data_size, binkpc->buf + 3); } #endif error = _TCPSend(&binkpc->tcp_send_iopb, binkpc->tcp_stream, binkpc->tcp_wds, nil, nil, false); if (error) { if (BINKP_CONNECTION_ALIVE(binkpc)) { logger_printf("[binkp] TCPSend of %d failed (%d), aborting", binkpc->tcp_wds[0].length, error); } else { logger_printf("[binkp] Connection lost (%d) while sending frame", binkpc->tcp_status_pb.connectionState); } binkpc->error = true; return 0; } binkpc->last_frame_at = Time; uthread_yield(); return binkpc->tcp_wds[0].length; } bool binkp_read_frame(void) { char tmp[128]; size_t len, off, frame_data_read; unsigned short rlen; short error; Ptr read_dest; if (binkpc == NULL) return false; error = _TCPStatus(&binkpc->tcp_read_iopb, binkpc->tcp_stream, &binkpc->tcp_status_pb, nil, nil, false); if (error) { binkpc->error = true; return false; } if (!BINKP_CONNECTION_ALIVE(binkpc)) { logger_printf("[binkp] Connection lost (%d) while reading frame", binkpc->tcp_status_pb.connectionState); binkpc->error = true; return false; } if (binkpc->tcp_status_pb.amtUnreadData < 2) return false; rlen = 2; error = _TCPRcv(&binkpc->tcp_read_iopb, binkpc->tcp_stream, (Ptr)&binkpc->cur_frame, &rlen, nil, nil, false); if (error) { binkpc->error = true; return false; } if (binkpc->cur_frame.data_size == 0) { logger_printf("[binkp] Received bogus frame, no data_size"); return false; } binkpc->last_frame_at = Time; binkpc->cur_frame.type = (binkpc->cur_frame.data_size & (1 << 15)) == 0 ? BINKP_TYPE_DATA : BINKP_TYPE_COMMAND; binkpc->cur_frame.data_size &= 0x7fff; frame_data_read = 0; while (frame_data_read < binkpc->cur_frame.data_size) { error = _TCPStatus(&binkpc->tcp_read_iopb, binkpc->tcp_stream, &binkpc->tcp_status_pb, nil, nil, false); if (error) goto failed_read; if (!BINKP_CONNECTION_ALIVE(binkpc)) { logger_printf("[binkp] Connection lost (%d) while reading frame", binkpc->tcp_status_pb.connectionState); goto failed_read; } if (binkpc->tcp_status_pb.amtUnreadData == 0) { uthread_yield(); continue; } if (binkpc->cur_frame.type == BINKP_TYPE_COMMAND) { if (frame_data_read >= binkpc->buf_size) { /* * Frame is too big but we can't overwrite buf since we * need the start of the frame, so just read into a junk * buffer and discard it */ read_dest = tmp; rlen = MIN(binkpc->cur_frame.data_size - frame_data_read, sizeof(tmp)); } else { read_dest = binkpc->buf + frame_data_read; rlen = MIN(binkpc->buf_size - frame_data_read, binkpc->cur_frame.data_size - frame_data_read); } } else { read_dest = binkpc->buf; rlen = MIN(binkpc->cur_frame.data_size - frame_data_read, binkpc->buf_size); } rlen = MIN(rlen, binkpc->tcp_status_pb.amtUnreadData); error = _TCPRcv(&binkpc->tcp_read_iopb, binkpc->tcp_stream, read_dest, &rlen, nil, nil, false); if (error) goto failed_read; binkpc->last_frame_at = Time; frame_data_read += rlen; if (binkpc->cur_frame.type == BINKP_TYPE_DATA) { binkpc->cur_incoming_file.data_read += rlen; #ifdef BINKP_DEBUG logger_printf("[binkp] Read %d TCP chunk (%ld / %d)", rlen, frame_data_read, binkpc->cur_frame.data_size); #endif if (binkpc->cur_incoming_file.frefnum == 0) panic("binkpc: no frefnum for data, bogus state"); len = rlen; error = FSWrite(binkpc->cur_incoming_file.frefnum, &len, binkpc->buf); if (error) { logger_printf("[binkp] Error writing %u to %s: %d", rlen, PtoCstr(binkpc->cur_incoming_file.pfilename), error); CtoPstr(binkpc->cur_incoming_file.pfilename); binkpc->done_receiving = binkpc->done_sending = true; goto failed_read; } } } if (frame_data_read > binkpc->cur_frame.data_size) panic("binkp data overread"); if (binkpc->cur_frame.type == BINKP_TYPE_COMMAND) { binkpc->cur_frame.command_id = binkpc->buf[0]; if (frame_data_read < binkpc->buf_size) binkpc->buf[frame_data_read] = '\0'; else binkpc->buf[binkpc->buf_size - 1] = '\0'; #ifdef BINKP_DEBUG logger_printf("[binkp] Read command 0x%x frame [%ld]: %s", binkpc->cur_frame.command_id, frame_data_read, binkpc->buf + (frame_data_read > 0 ? 1 : 0)); #endif switch (binkpc->cur_frame.command_id) { case BINKP_COMMAND_M_NUL: if (strncmp(binkpc->buf + 1, "SYS ", 4) == 0) logger_printf("[binkp] Connected to %s", binkpc->buf + 1 + 4); else if (strncmp(binkpc->buf + 1, "TIME ", 5) == 0) { /* * TODO: try to parse timezone and pass to fidopkt, so * any packets without a TZUTC line can use the hub's * timezone. */ } break; case BINKP_COMMAND_M_FILE: if (binkpc->cur_incoming_file.filename[0]) { logger_printf("[binkp] Received M_FILE but not done " "with file %s!", binkpc->cur_incoming_file.filename); binkpc->cur_incoming_file.filename[0] = '\0'; if (binkpc->cur_incoming_file.frefnum > 0) { FSClose(binkpc->cur_incoming_file.frefnum); binkpc->cur_incoming_file.frefnum = 0; } } if (sscanf(binkpc->buf + 1, "%128s %lu %lu %lu", &binkpc->cur_incoming_file.filename, &binkpc->cur_incoming_file.size, &binkpc->cur_incoming_file.mtime, &off) == 4) { logger_printf("[binkp] Receiving file \"%s\" size %ld", binkpc->cur_incoming_file.filename, binkpc->cur_incoming_file.size); if (off != 0) logger_printf("[binkp] Non-zero file fetch offset not " "supported"); binkpc->cur_incoming_file.data_read = 0; PtoCstr(binkp_inbox_dir); snprintf((char *)binkpc->cur_incoming_file.pfilename, sizeof(binkpc->cur_incoming_file.pfilename), "%s:%s", binkp_inbox_dir, binkpc->cur_incoming_file.filename); CtoPstr(binkp_inbox_dir); CtoPstr(binkpc->cur_incoming_file.pfilename); error = Create(binkpc->cur_incoming_file.pfilename, 0, SUBTEXT_CREATOR, 'BINK'); if (error == dupFNErr) { FSDelete(binkpc->cur_incoming_file.pfilename, 0); error = Create(binkpc->cur_incoming_file.pfilename, 0, SUBTEXT_CREATOR, 'BINK'); } if (error) { warn("Failed creating %s: %d", PtoCstr(binkpc->cur_incoming_file.pfilename), error); CtoPstr(binkpc->cur_incoming_file.pfilename); goto failed_read; } error = FSOpen(binkpc->cur_incoming_file.pfilename, 0, &binkpc->cur_incoming_file.frefnum); if (error) { warn("Failed opening %s: %d", PtoCstr(binkpc->cur_incoming_file.pfilename), error); CtoPstr(binkpc->cur_incoming_file.pfilename); goto failed_read; } len = binkpc->cur_incoming_file.size; error = Allocate(binkpc->cur_incoming_file.frefnum, &len); if (error) { warn("Failed setting %s to size %ld: %d", PtoCstr(binkpc->cur_incoming_file.pfilename), binkpc->cur_incoming_file.size, error); CtoPstr(binkpc->cur_incoming_file.pfilename); } } else { logger_printf("[binkp] Failed parsing M_FILE: %s", binkpc->buf + 1); } break; case BINKP_COMMAND_M_EOB: binkpc->done_receiving = true; break; case BINKP_COMMAND_M_ERR: logger_printf("[binkp] Error from remote: %s", binkpc->buf + 1); binkpc->done_receiving = binkpc->done_sending = true; break; case BINKP_COMMAND_M_BSY: logger_printf("[binkp] Remote is busy: %s", binkpc->buf + 1); binkpc->done_receiving = binkpc->done_sending = true; break; } } else { #ifdef BINKP_DEBUG logger_printf("[binkp] Read %d data frame of file (%ld / %ld)", binkpc->cur_frame.data_size, binkpc->cur_incoming_file.data_read, binkpc->cur_incoming_file.size); #endif if (binkpc->cur_incoming_file.data_read == binkpc->cur_incoming_file.size) { #ifdef BINKP_DEBUG logger_printf("[binkp] Done reading file %s (%ld)", binkpc->cur_incoming_file.filename, binkpc->cur_incoming_file.size); #endif FSClose(binkpc->cur_incoming_file.frefnum); binkpc->cur_incoming_file.frefnum = 0; len = snprintf(tmp, sizeof(tmp), "%s %lu %lu", binkpc->cur_incoming_file.filename, binkpc->cur_incoming_file.size, binkpc->cur_incoming_file.mtime); if (!binkp_send_frame(BINKP_COMMAND_M_GOT, tmp, len)) logger_printf("[binkp] Failed sending M_GOT %s", binkpc->cur_incoming_file.filename); binkpc->cur_incoming_file.filename[0] = '\0'; } } return true; failed_read: if (binkpc->cur_incoming_file.frefnum > 0) { FSClose(binkpc->cur_incoming_file.frefnum); binkpc->cur_incoming_file.frefnum = 0; } binkpc->done_receiving = binkpc->done_sending = true; binkpc->error = true; return false; } bool binkp_login(void) { char command[50]; size_t len; /* discard early frames */ while (binkpc != NULL && binkp_read_frame()) ; if (!binkpc || binkpc->error) return false; len = snprintf(command, sizeof(command), "SYS %s", db->config.name); if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len)) return false; len = snprintf(command, sizeof(command), "LOC %s", db->config.location); if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len)) return false; len = snprintf(command, sizeof(command), "NDL 14400,TCP,BINKP"); if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len)) return false; len = snprintf(command, sizeof(command), "VER Subtext/%s binkp/1.0", get_version(false)); if (!binkp_send_frame(BINKP_COMMAND_M_NUL, command, len)) return false; if (!binkp_send_frame(BINKP_COMMAND_M_ADR, db->config.ftn_node_addr, strlen(db->config.ftn_node_addr))) return false; if (!binkp_send_frame(BINKP_COMMAND_M_PWD, db->config.binkp_password, strlen(db->config.binkp_password))) return false; while (binkpc && !binkpc->error) { if (!binkp_read_frame()) { uthread_yield(); if (Time - binkpc->last_frame_at > 30) { logger_printf("[binkp] No frame received in %ld seconds, " "aborting", (Time - binkpc->last_frame_at)); binkpc->error = true; break; } continue; } if (binkpc->cur_frame.type != BINKP_TYPE_COMMAND) continue; if (binkpc->cur_frame.command_id == BINKP_COMMAND_M_ERR) { logger_printf("[binkp] Login as %s failed, disconnecting", db->config.ftn_node_addr); binkp_free(); return false; } if (binkpc->cur_frame.command_id == BINKP_COMMAND_M_OK) { logger_printf("[binkp] Logged in successfully as %s", db->config.ftn_node_addr); return true; } } return false; } void binkp_toss_inbox(void) { CInfoPBRec cipbr = { 0 }; HFileInfo *fpb = (HFileInfo *)&cipbr; DirInfo *dpb = (DirInfo *)&cipbr; CMovePBRec cmpbr = { 0 }; Str255 file_name_c, path, done_path; short dir_id, error, zret; unsigned long started = Time, elapsed; ssize_t data_size; char *data, *errmail; bool bad; binkp_fidopkt_skipped = 0; binkp_fidopkt_imported = 0; binkp_temp_fail = false; fpb->ioVRefNum = 0; fpb->ioNamePtr = (StringPtr)&binkp_inbox_dir; error = PBGetCatInfo(&cipbr, false); if (error) { logger_printf("[binkp] PBGetCatInfo on binkp dir failed: %d", error); return; } dir_id = dpb->ioDrDirID; fpb->ioNamePtr = (StringPtr)&file_name_c; for (;;) { file_name_c[0] = 0; fpb->ioDirID = dir_id; /* * Keep requesting the first item in the directory since we're * deleting or moving each file as we process it */ fpb->ioFDirIndex = 1; error = PBGetCatInfo(&cipbr, false); if (error) break; PtoCstr(file_name_c); PtoCstr(binkp_inbox_dir); snprintf((char *)path, sizeof(path), "%s:%s", binkp_inbox_dir, file_name_c); logger_printf("[binkp] Tossing file %s", (char *)path); CtoPstr(path); CtoPstr(binkp_inbox_dir); binkp_fidopkt_skipped = 0; binkp_fidopkt_imported = 0; bad = false; if (zip_is_zip_file(path)) { zret = zip_read_file(path, binkp_zip_decider, binkp_fidopkt_processor); if (zret == ZIP_NO_MEMORY) binkp_temp_fail = true; else if (zret != ZIP_OK) { bad = true; logger_printf("[binkp] Failed processing ZIP file %s: %d", file_name_c, zret); } } else if (strstr((char *)file_name_c, ".pkt")) { data_size = binkp_buffer_file(path, &data); if (data_size == 0) binkp_temp_fail = true; else if (data_size == -1) { logger_printf("[binkp] Permanent failure, skipping file"); bad = true; } else { binkp_fidopkt_processor((char *)file_name_c, (unsigned char *)data, data_size); xfree(&data); } } if (binkp_temp_fail) { logger_printf("[binkp] Temporary failure during tossing, " "aborting"); binkp_temp_fail = false; return; } logger_printf("[binkp] %s: tossed %d, skipped %d", (char *)file_name_c, binkp_fidopkt_imported, binkp_fidopkt_skipped); if (db->config.binkp_delete_done && !bad) { if ((error = FSDelete(path, 0)) != 0) { logger_printf("[binkp] Failed deleting inbox file %s: %d", PtoCstr(path), error); break; } } else { if (bad) { PtoCstr(binkp_bad_dir); snprintf((char *)done_path, sizeof(done_path), "%s:%s", binkp_bad_dir, file_name_c); errmail = xmalloc(512); if (errmail != NULL) { snprintf(errmail, 512, "A permanent error occurred while tossing file " "\"%s\" and it\r\n" "has been moved to the \"bad\" folder for further " "inspection:\r\n\r\n%s", file_name_c, done_path); mail_to_sysop("Failed tossing file", errmail); xfree(&errmail); } CtoPstr(done_path); CtoPstr(binkp_bad_dir); } else { PtoCstr(binkp_done_dir); snprintf((char *)done_path, sizeof(done_path), "%s:%s", binkp_done_dir, file_name_c); CtoPstr(done_path); CtoPstr(binkp_done_dir); } try_rename: cmpbr.ioNamePtr = path; if (bad) cmpbr.ioNewName = binkp_bad_dir; else cmpbr.ioNewName = binkp_done_dir; cmpbr.ioDirID = 0; cmpbr.ioNewDirID = 0; cmpbr.ioVRefNum = 0; error = PBCatMove(&cmpbr, false); if (error == dupFNErr) { if ((error = FSDelete(done_path, 0)) != 0) panic("[binkp] Failed deleting %s: %d", PtoCstr(done_path), error); goto try_rename; } else if (error) { panic("[binkp] Failed moving %s to %s: %d", PtoCstr(path), PtoCstr(done_path), error); } } } if (binkp_fidopkt_skipped == 0 && binkp_fidopkt_imported == 0) return; elapsed = Time - started; logger_printf("[binkp] Done tossing in %ld sec%s", elapsed, elapsed == 1 ? "" : "s"); } bool binkp_zip_decider(char *filename, size_t size) { size_t flen = strlen(filename); if (strcmp(filename + flen - 4, ".pkt") == 0) return true; return false; } void binkp_fidopkt_processor(char *filename, unsigned char *data, size_t size) { struct fidopkt_message *msg = NULL; struct fidopkt_header *header = NULL; struct board *board = NULL; short n, ret; uthread_yield(); while (size != 0 && (msg = fidopkt_parse_message(filename, header, (char **)&data, &size)) != NULL) { if (msg->time) /* convert to local time zone */ msg->time += (((long)db->config.timezone_utcoff * 60 * 60) / 100); else msg->time = Time; if (msg->area[0]) { board = NULL; for (n = 0; n < db->nboards; n++) { if (db->boards[n].ftn_area[0] && strcasecmp(msg->area, db->boards[n].ftn_area) == 0) { board = &db->boards[n]; break; } } if (!board) { #ifdef BINKP_DEBUG logger_printf("[fidopkt] No local board for %s, skipping", msg->area); #endif binkp_fidopkt_skipped++; goto next_message; } ret = board_toss_ftn_message(board, msg, false); } else ret = mail_toss_ftn_message(msg); if (ret == -1) { binkp_temp_fail = true; fidopkt_message_free(&msg); break; } if (ret == 0) binkp_fidopkt_skipped++; else binkp_fidopkt_imported++; next_message: if (header == NULL && size > 0) { header = xmalloc(sizeof(msg->header)); if (header == NULL) { fidopkt_message_free(&msg); binkp_temp_fail = true; break; } memcpy(header, &msg->header, sizeof(struct fidopkt_header)); } fidopkt_message_free(&msg); } if (header) xfree(&header); } ssize_t binkp_buffer_file(Str255 path, char **data) { struct stat sb; long count; short error, frefnum; if (FStat(path, &sb) != 0) return -1; if (sb.st_size == 0) return -1; *data = xmalloc(sb.st_size); if (*data == NULL) return 0; error = FSOpen(path, 0, &frefnum); if (error) { xfree(data); return -1; } count = sb.st_size; error = FSRead(frefnum, &count, *data); FSClose(frefnum); if (error && error != eofErr) { xfree(data); return -1; } return count; } void binkp_deliver_outbox(void) { Str255 file_name_c, path, done_path; char command[50]; CInfoPBRec cipbr = { 0 }; HFileInfo *fpb = (HFileInfo *)&cipbr; DirInfo *dpb = (DirInfo *)&cipbr; CMovePBRec cmpbr = { 0 }; struct stat sb; size_t len, fsize_left; short dir_id, error, frefnum; char *data = NULL; fpb->ioVRefNum = 0; fpb->ioNamePtr = (StringPtr)&binkp_outbox_dir; error = PBGetCatInfo(&cipbr, false); if (error) { logger_printf("[binkp] PBGetCatInfo on binkp outbox failed: %d", error); return; } dir_id = dpb->ioDrDirID; fpb->ioNamePtr = (StringPtr)&file_name_c; for (;;) { file_name_c[0] = 0; fpb->ioDirID = dir_id; /* * Keep requesting the first item in the directory since we're * deleting or moving each file as we process it. */ fpb->ioFDirIndex = 1; error = PBGetCatInfo(&cipbr, false); if (error) break; if (data == NULL) { data = xmalloc(binkpc->buf_size); if (data == NULL) { logger_printf("[binkp] Failed allocating outbound buffer " "%lu", binkpc->buf_size); return; } } PtoCstr(file_name_c); PtoCstr(binkp_outbox_dir); snprintf((char *)path, sizeof(path), "%s:%s", binkp_outbox_dir, file_name_c); CtoPstr(path); CtoPstr(binkp_outbox_dir); FStat(path, &sb); logger_printf("[binkp] Sending file %s size %lu", file_name_c, sb.st_size); len = snprintf(command, sizeof(command), "%s %lu %lu 0", file_name_c, sb.st_size, MAC_TO_UNIX_TIME(sb.st_ctime)); if (!binkp_send_frame(BINKP_COMMAND_M_FILE, command, len)) goto done; error = FSOpen(path, 0, &frefnum); if (error) { warn("binkp: failed opening %s: %d", PtoCstr(path), error); goto done; } for (fsize_left = sb.st_size; fsize_left != 0; ) { /* leave 2 bytes for size */ len = MIN(fsize_left, binkpc->buf_size - 2); error = FSRead(frefnum, &len, data); if (error && error != eofErr) { warn("binkp: error reading from %s: %d", PtoCstr(path), error); goto done; } if (!binkp_send_frame(BINKP_DATA, data, len)) goto done; fsize_left -= len; } FSClose(frefnum); frefnum = 0; /* wait for frame */ while (binkpc != NULL && !binkp_read_frame() && !binkpc->error) { if (Time - binkpc->last_frame_at > 30) { logger_printf("[binkp] No frame received in %ld seconds, " "aborting", (Time - binkpc->last_frame_at)); binkpc->error = true; goto done; } uthread_yield(); continue; } if (!binkpc || binkpc->error) { logger_printf("[binkp] Connection lost waiting for M_GOT"); goto done; } if (binkpc->cur_frame.type == BINKP_TYPE_COMMAND && binkpc->cur_frame.command_id == BINKP_COMMAND_M_SKIP) { logger_printf("[binkp] Received SKIP response " "to sending file %s, will retry during next connection", file_name_c); goto send_eob; } if (binkpc->cur_frame.type != BINKP_TYPE_COMMAND || binkpc->cur_frame.command_id != BINKP_COMMAND_M_GOT) { logger_printf("[binkp] Received unexpected response " "(0x%x 0x%x) to sending file, aborting", binkpc->cur_frame.type, binkpc->cur_frame.command_id); goto done; } logger_printf("[binkp] Successfully sent file %s", file_name_c); if (db->config.binkp_delete_done) { if ((error = FSDelete(path, 0))) /* this must be fatal so we don't keep trying this file */ panic("[binkp] Failed deleting outbox file %s: %d", PtoCstr(path), error); } else { PtoCstr(binkp_done_dir); snprintf((char *)done_path, sizeof(done_path), "%s:%s", binkp_done_dir, file_name_c); CtoPstr(done_path); CtoPstr(binkp_done_dir); try_rename: cmpbr.ioNamePtr = path; cmpbr.ioNewName = binkp_done_dir; cmpbr.ioDirID = 0; cmpbr.ioNewDirID = 0; cmpbr.ioVRefNum = 0; error = PBCatMove(&cmpbr, false); if (error == dupFNErr) { error = FSDelete(done_path, 0); if (error) panic("[binkp] Failed deleting %s: %d", PtoCstr(done_path), error); goto try_rename; } else if (error) { panic("[binkp] Failed moving %s to %s: %d", PtoCstr(path), PtoCstr(done_path), error); } } } send_eob: if (!binkp_send_frame(BINKP_COMMAND_M_EOB, "", 0)) logger_printf("[binkp] Failed sending M_EOB"); if (binkpc) binkpc->done_sending = true; done: if (data) xfree(&data); if (frefnum) FSClose(frefnum); } bool binkp_packets_in_outbox(void) { CInfoPBRec cipbr = { 0 }; HFileInfo *fpb = (HFileInfo *)&cipbr; DirInfo *dpb = (DirInfo *)&cipbr; Str255 file_name_c; short dir_id, error; fpb->ioVRefNum = 0; fpb->ioNamePtr = (StringPtr)&binkp_outbox_dir; error = PBGetCatInfo(&cipbr, false); if (error) { logger_printf("[binkp] PBGetCatInfo on binkp outbox failed: %d", error); return false; } dir_id = dpb->ioDrDirID; fpb->ioNamePtr = (StringPtr)&file_name_c; file_name_c[0] = 0; fpb->ioDirID = dir_id; fpb->ioFDirIndex = 1; error = PBGetCatInfo(&cipbr, false); if (error) return false; return true; } bool binkp_scan_message(struct fidopkt_message *msg) { Str255 path; char filename[32]; char *buf = NULL; short error, frefnum = 0; size_t size, asize; bool ret = false; size = fidopkt_encode_message(msg, &buf, db->config.ftn_hub_pkt_password, db->config.timezone_utcoff); if (size == 0) { logger_printf("[binkp] Failed encoding fidopkt during scan"); return false; } if (binkp_outbox_dir[0] == 0) { warn("binkp: outbox dir is empty, can't store scanned packet"); goto done; } snprintf(filename, sizeof(filename), "%c%07lx.pkt", msg->area[0] ? 'b' : 'm', msg->msgid.id & 0x0fffffff); PtoCstr(binkp_outbox_dir); snprintf((char *)path, sizeof(path), "%s:%s", binkp_outbox_dir, filename); CtoPstr(binkp_outbox_dir); CtoPstr(path); error = Create(path, 0, SUBTEXT_CREATOR, FTN_SPOOL_PACKET_TYPE); if (error == dupFNErr) { warn("binkp: collision saving packet to %s", PtoCstr(path)); goto done; } if (error) { warn("binkp: failed creating %s: %d", PtoCstr(path), error); goto done; } error = FSOpen(path, 0, &frefnum); if (error) { warn("binkp: failed opening newly created %s: %d", PtoCstr(path), error); goto done; } asize = size; error = Allocate(frefnum, &asize); if (error) { warn("binkp: failed setting %s to size %ld: %d", PtoCstr(path), size, error); goto done; } error = FSWrite(frefnum, &size, buf); if (error) { logger_printf("[binkp] Error writing %lu to %s: %d", size, PtoCstr(path), error); goto done; } ret = true; done: if (frefnum) FSClose(frefnum); xfree(&buf); return ret; }