/* * 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 "nomodem.h" struct nomodem_session * nomodem_send(struct session *s, FILE *fp, char *filename) { struct nomodem_session *ns; if (!fp) return NULL; ns = xmalloczero(sizeof(struct nomodem_session)); if (ns == NULL) return NULL; ns->mode = NOMODEM_MODE_SENDING; ns->session = s; ns->file = fp; strlcpy(ns->file_name, filename, sizeof(ns->file_name)); fseek(ns->file, 0, SEEK_END); ns->file_size = ftell(ns->file); fseek(ns->file, 0, SEEK_SET); /* initiator */ session_printf(ns->session, "%c%c%c%c%c%c", (char)NOMODEM_START0, (char)NOMODEM_START1, (char)NOMODEM_START2, (char)NOMODEM_START3, (char)NOMODEM_VERSION, (char)NOMODEM_CMD_SEND); /* file name size (byte), file name */ session_printf(ns->session, "%c%s", (unsigned char)strlen(ns->file_name), ns->file_name); /* 32-bit file size (big-endian) */ session_printf(ns->session, "%c%c%c%c", (unsigned char)((ns->file_size >> 24) & 0xff), (unsigned char)((ns->file_size >> 16) & 0xff), (unsigned char)((ns->file_size >> 8) & 0xff), (unsigned char)(ns->file_size & 0xff)); session_flush(ns->session); ns->last_input = Time; ns->need_header_ack = true; return ns; } struct nomodem_session * nomodem_receive(struct session *s, char *path) { struct nomodem_session *ns; if (path == NULL) return NULL; ns = xmalloczero(sizeof(struct nomodem_session)); if (ns == NULL) return NULL; ns->mode = NOMODEM_MODE_RECEIVING; ns->session = s; ns->file_path = xstrdup(path); if (ns->file_path == NULL) { xfree(&ns); return NULL; } ns->buf_size = sizeof(ns->session->ibuf); ns->buf = xmalloc(ns->buf_size); if (ns->buf == NULL) { xfree(&ns->file_path); xfree(&ns); return NULL; } ns->file = fopen(ns->file_path, "wb+"); if (ns->file == NULL) { session_printf(ns->session, "[nomodem] Failed opening %s for writing", ns->file_path); xfree(&ns->file_path); xfree(&ns); return NULL; } /* initiator */ session_printf(ns->session, "%c%c%c%c%c%c", (char)NOMODEM_START0, (char)NOMODEM_START1, (char)NOMODEM_START2, (char)NOMODEM_START3, (char)NOMODEM_VERSION, (char)NOMODEM_CMD_RECEIVE); session_flush(ns->session); ns->last_input = Time; ns->need_header = true; return ns; } bool nomodem_timed_out(struct nomodem_session *ns) { if ((Time - ns->last_input) > NOMODEM_TIMEOUT) return true; return false; } unsigned short nomodem_get_char(struct nomodem_session *ns) { unsigned char b; while (session_get_char(ns->session, &b) == 0) { if (ns->session->ending || nomodem_timed_out(ns)) return -1; } ns->last_input = Time; return (short)b; } bool nomodem_continue(struct nomodem_session *ns) { size_t size, rsize, pos, tsize; short resp; unsigned short file_name_size, file_name_pos; switch (ns->mode) { case NOMODEM_MODE_SENDING: if (ns->need_header_ack) { resp = nomodem_get_char(ns); if (resp < 0) return false; size = ((unsigned long)(resp & 0xff)) << 24; resp = nomodem_get_char(ns); if (resp < 0) return false; size |= ((unsigned long)(resp & 0xff)) << 16; resp = nomodem_get_char(ns); if (resp < 0) return false; size |= ((unsigned long)(resp & 0xff)) << 8; resp = nomodem_get_char(ns); if (resp < 0) return false; size |= (unsigned long)(resp & 0xff); if (size != ns->file_size) { session_logf(ns->session, "[nomodem] Header ack size %ld != file size %ld, " "canceling", size, ns->file_size); return false; } ns->need_header_ack = false; } while (ns->session->obuflen > 0) { session_flush(ns->session); if (ns->session->ending) return false; if (nomodem_timed_out(ns)) return false; } pos = ftell(ns->file); if (feof(ns->file)) return false; size = fread(ns->session->obuf + 4, 1, sizeof(ns->session->obuf) - 4, ns->file); if (size == 0) return false; ns->session->obuf[0] = (size >> 24) & 0xff; ns->session->obuf[1] = (size >> 16) & 0xff; ns->session->obuf[2] = (size >> 8) & 0xff; ns->session->obuf[3] = size & 0xff; ns->session->obuflen = size + 4; session_flush(ns->session); /* wait for ack */ resp = nomodem_get_char(ns); if (resp != NOMODEM_ACK) { session_logf(ns->session, "[nomodem] Failed ack at chunk " "%ld/%ld: %d", pos, ns->file_size, resp); return false; } if (feof(ns->file)) return false; break; case NOMODEM_MODE_RECEIVING: if (ns->need_header) { file_name_size = nomodem_get_char(ns); if (file_name_size < 0) return false; /* get filename */ file_name_pos = 0; while (file_name_size != 0) { resp = nomodem_get_char(ns); if (resp < 0) return false; if (file_name_pos < sizeof(ns->file_name) - 1) { ns->file_name[file_name_pos++] = (char)resp; ns->file_name[file_name_pos] = 0; } file_name_size--; } /* get file size */ resp = nomodem_get_char(ns); if (resp < 0) return false; ns->file_size = ((unsigned long)(resp & 0xff)) << 24; resp = nomodem_get_char(ns); if (resp < 0) return false; ns->file_size |= ((unsigned long)(resp & 0xff)) << 16; resp = nomodem_get_char(ns); if (resp < 0) return false; ns->file_size |= ((unsigned long)(resp & 0xff)) << 8; resp = nomodem_get_char(ns); if (resp < 0) return false; ns->file_size |= (unsigned long)(resp & 0xff); /* ack */ session_printf(ns->session, "%c%c%c%c", (char)((ns->file_size >> 24) & 0xff), (char)((ns->file_size >> 16) & 0xff), (char)((ns->file_size >> 8) & 0xff), (char)(ns->file_size & 0xff)); ns->need_header = false; } pos = ftell(ns->file); if (pos >= ns->file_size) return false; /* get chunk size */ resp = nomodem_get_char(ns); if (resp < 0) return false; size = ((unsigned long)(resp & 0xff)) << 24; resp = nomodem_get_char(ns); if (resp < 0) return false; size |= ((unsigned long)(resp & 0xff)) << 16; resp = nomodem_get_char(ns); if (resp < 0) return false; size |= ((unsigned long)(resp & 0xff)) << 8; resp = nomodem_get_char(ns); if (resp < 0) return false; size |= (unsigned long)(resp & 0xff); if (size > (1024 * 10)) { session_logf(ns->session, "[nomodem] Chunk size too large: %ld", size); return false; } for (rsize = 0; rsize < size; ) { tsize = session_get_chars(ns->session, ns->buf, MIN(ns->buf_size, size - rsize)); if (ns->session->ending || nomodem_timed_out(ns)) return false; if (tsize == 0) { uthread_yield(); continue; } ns->last_input = Time; if (fwrite(ns->buf, 1, tsize, ns->file) != tsize) { session_logf(ns->session, "[nomodem] Failed writing to %s", ns->file_path); return false; } rsize += tsize; } session_printf(ns->session, "%c", (char)NOMODEM_ACK); session_flush(ns->session); pos = ftell(ns->file); if (pos >= ns->file_size) return false; } return true; } void nomodem_finish(struct nomodem_session **nsp) { struct nomodem_session *ns = (struct nomodem_session *)*nsp; if (ns->file_path != NULL) xfree(&ns->file_path); if (ns->file) fclose(ns->file); if (ns->buf) xfree(&ns->buf); xfree(nsp); }