diff --git a/src/vdmodem/vdmodem.c b/src/vdmodem/vdmodem.c new file mode 100644 index 0000000000000000000000000000000000000000..d3abc8fd40e5626fedac5e41afcb69288d1b98cd --- /dev/null +++ b/src/vdmodem/vdmodem.c @@ -0,0 +1,969 @@ +/* Synchronet Virtual DOS Modem for Windows */ + +/**************************************************************************** + * @format.tab-size 4 (Plain Text/Source Code File Header) * + * @format.use-tabs true (see http://www.synchro.net/ptsc_hdr.html) * + * * + * Copyright Rob Swindell - http://www.synchro.net/copyright.html * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * See the GNU General Public License for more details: gpl.txt or * + * http://www.fsf.org/copyleft/gpl.html * + * * + * For Synchronet coding style and modification guidelines, see * + * http://www.synchro.net/source.html * + * * + * Note: If this box doesn't appear square, then you need to fix your tabs. * + ****************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <time.h> +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#include <process.h> + +#include "genwrap.h" +#include "gen_defs.h" +#include "sockwrap.h" + +#define TITLE "Synchronet Virtual DOS Modem for Windows" +#define VERSION "0.0" + +SOCKET sock = INVALID_SOCKET; +SOCKET listening_sock = INVALID_SOCKET; +HANDLE hangup_event = INVALID_HANDLE_VALUE; // e.g. program drops DTR +HANDLE hungup_event = INVALID_HANDLE_VALUE; // e.g. ATH0 +HANDLE carrier_event = INVALID_HANDLE_VALUE; +HANDLE rdslot = INVALID_HANDLE_VALUE; +HANDLE wrslot = INVALID_HANDLE_VALUE; +union xp_sockaddr listening_interface; + +#define XTRN_IO_BUF_LEN 10000 +#define RING_DELAY 6000 /* US standard is 6 seconds */ + +struct { + int node_num; + bool listen; + bool debug; + bool terminate_on_disconnect; + ulong data_rate; + enum { + ADDRESS_FAMILY_UNSPEC + ,ADDRESS_FAMILY_INET + ,ADDRESS_FAMILY_INET6 + } address_family; +} cfg; + +static void dprintf(const char *fmt, ...) +{ + char buf[1024] = "SBBSVDM: "; + va_list argptr; + + va_start(argptr,fmt); + size_t offset = strlen(buf); + _vsnprintf(buf + offset, sizeof(buf) - offset, fmt, argptr); + TERMINATE(buf); + va_end(argptr); + OutputDebugString(buf); +} + +void usage(void) +{ + fprintf(stderr, "usage:\n"); + exit(EXIT_SUCCESS); +} + +const char* supported_cmds = "ADEHIMOQSVXZ&"; +const char* string_cmds = "D"; +struct modem { + enum { + INIT + ,A + ,AT + } cmdstate; + char cr; + char lf; + char bs; + char esc; + bool echo_off; + bool numeric_mode; + bool offhook; + bool online; // false means "command mode" + bool ringing; + ulong ringcount; + ulong auto_answer; + ulong dial_wait; + ulong guard_time; + ulong esc_count; + ulong ext_results; + ulong quiet; + uint8_t buf[128]; + size_t buflen; +}; + +void newcmd(struct modem* modem) +{ + modem->cmdstate = INIT; + modem->buflen = 0; +} + +void init(struct modem* modem) +{ + memset(modem, 0, sizeof(*modem)); + modem->cr = '\r'; + modem->lf = '\n'; + modem->bs = '\b'; + modem->esc = '+'; + modem->ext_results = 4; + modem->dial_wait = 60; + modem->guard_time = 50; +} + +ulong guard_time(struct modem* modem) +{ + return modem->guard_time * 20; +} + +ulong count_esc(struct modem* modem, uint8_t* buf, size_t rd) +{ + if(modem->esc < 0 || modem->esc > 127) + return 0; + + ulong count = 0; + for(size_t i = 0; i < rd; i++) { + if(buf[i] == modem->esc) + count++; + } + return count; +} + +// Basic Hayes modem responses +enum modem_response { + OK = 0, + CONNECT = 1, + RING = 2, + NO_CARRIER = 3, + ERROR = 4, + CONNECT_1200 = 5, + NO_DIAL_TONE = 6, + BUSY = 7, + NO_ANSWER = 8, + RESERVED1 = 9, + CONNECT_2400 = 10, + RESERVED2 = 11, + RESERVED3 = 12, + CONNECT_9600 = 13 +}; + +const char* response_str[] = { + "OK", + "CONNECT", + "RING", + "NO CARRIER", + "ERROR", + "CONNECT 1200", + "NO DIAL TONE", + "BUSY", + "NO ANSWER", + "RESERVED1", + "CONNECT 2400", + "RESERVED2", + "RESERVED3", + "CONNECT 9600" +}; + +char* response(struct modem* modem, enum modem_response code) +{ + static char str[128]; + + if(modem->quiet) + return ""; + if(modem->numeric_mode) + sprintf(str, "%u%c", code, modem->cr); + else + sprintf(str, "%c%c%s%c%c", modem->cr, modem->lf, response_str[code], modem->cr, modem->lf); + return str; +} + +char* ok(struct modem* modem) +{ + return response(modem, OK); +} + +char* error(struct modem* modem) +{ + return response(modem, ERROR); +} + +char* connect_result(struct modem* modem) +{ + return response(modem, modem->ext_results ? CONNECT_9600 : CONNECT); +} + +char* connected(struct modem* modem) +{ + modem->online = true; + modem->ringing = false; + ResetEvent(hungup_event); + SetEvent(carrier_event); + return connect_result(modem); +} + +void disconnect(struct modem* modem) +{ + modem->online = false; + shutdown(sock, SD_SEND); + closesocket(sock); + sock = INVALID_SOCKET; + SetEvent(hungup_event); + SetEvent(carrier_event); +} + +bool kbhit() +{ + unsigned long waiting = 0; + if(!GetMailslotInfo( + rdslot, // mailslot handle + NULL, // address of maximum message size + NULL, // address of size of next message + &waiting, // address of number of messages + NULL // address of read time-out + )) + return false; + return waiting != 0; +} + +// Significant portions copies from syncterm/conn.c +char* dial(struct modem* modem, const char* number) +{ + struct addrinfo hints; + struct addrinfo *res=NULL; + char host[128]; + char* portnum = "23"; + + SAFECOPY(host, number); + char* p = strrchr(host, ':'); + char* b = strrchr(host, ']'); + if(p != NULL && p > b) { + portnum = p + 1; + *p = 0; + } + dprintf("Connecting to host '%s', port: %u", host, portnum); + memset(&hints, 0, sizeof(hints)); + hints.ai_flags=PF_UNSPEC; + switch(cfg.address_family) { + case ADDRESS_FAMILY_INET: + hints.ai_family=PF_INET; + break; + case ADDRESS_FAMILY_INET6: + hints.ai_family=PF_INET6; + break; + case ADDRESS_FAMILY_UNSPEC: + default: + hints.ai_family=PF_UNSPEC; + break; + } + hints.ai_socktype=SOCK_STREAM; + hints.ai_protocol=IPPROTO_TCP; + hints.ai_flags=AI_NUMERICSERV; +#ifdef AI_ADDRCONFIG + hints.ai_flags|=AI_ADDRCONFIG; +#endif + dprintf("%s %d calling getaddrinfo", __FILE__, __LINE__); + int result = getaddrinfo(host, portnum, &hints, &res); + if(result != 0) { + dprintf("getaddrinfo(%s, %s) returned %d", host, portnum, result); + return response(modem, NO_ANSWER); + } + + int nonblock; + struct addrinfo *cur; + for(cur=res; cur && sock == INVALID_SOCKET; cur=cur->ai_next) { + if(sock==INVALID_SOCKET) { + sock=socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); + if(sock==INVALID_SOCKET) { + dprintf("Error %ld creating socket", WSAGetLastError()); + return response(modem, NO_DIAL_TONE); + } + /* Set to non-blocking for the connect */ + nonblock=-1; + ioctlsocket(sock, FIONBIO, &nonblock); + } + + dprintf("%s %d calling connect", __FILE__, __LINE__); + if(connect(sock, cur->ai_addr, cur->ai_addrlen)) { + switch(ERROR_VALUE) { + case EINPROGRESS: + case EINTR: + case EAGAIN: + case EWOULDBLOCK: + for(;sock!=INVALID_SOCKET;) { + if (socket_writable(sock, 1000)) { + if (socket_recvdone(sock, 0)) { + closesocket(sock); + sock=INVALID_SOCKET; + continue; + } + else { + goto connected; + } + } + else { + if (kbhit()) { + dprintf("%s %d kbhit", __FILE__, __LINE__); + closesocket(sock); + sock = INVALID_SOCKET; + return response(modem, NO_CARRIER); + } + } + } + +connected: + break; + default: + closesocket(sock); + sock=INVALID_SOCKET; + continue; + } + } + } + if (sock == INVALID_SOCKET) { + dprintf("%s %d invalid hostname?", __FILE__, __LINE__); + return response(modem, NO_ANSWER); + } + + freeaddrinfo(res); + res=NULL; + nonblock=0; + ioctlsocket(sock, FIONBIO, &nonblock); + if(socket_recvdone(sock, 0)) { + dprintf("%s %d socket_recvdone", __FILE__, __LINE__); + return response(modem, NO_CARRIER); + } + + dprintf("%s %d connected!", __FILE__, __LINE__); + int keepalives = TRUE; + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepalives, sizeof(keepalives)); + return connected(modem); +} + +char* answer(struct modem* modem) +{ + if(listening_sock == INVALID_SOCKET) + return response(modem, NO_DIAL_TONE); + + fd_set fds = {0}; + FD_SET(listening_sock, &fds); + struct timeval tv = { 0, 0 }; + if(select(/* ignored: */0, &fds, NULL, NULL, &tv) != 1) + return response(modem, NO_CARRIER); + + union xp_sockaddr addr; + socklen_t addrlen = sizeof(addr); + sock = accept(listening_sock, (SOCKADDR*)&addr, &addrlen); + if(sock == INVALID_SOCKET) { + dprintf("accept returned %d (errno=%ld)", sock, WSAGetLastError()); + return response(modem, NO_CARRIER); + } + char tmp[256]; + dprintf("Connection accepted from TCP port %hu at %s", inet_addrport(&addr), inet_addrtop(&addr, tmp, sizeof(tmp))); + return connected(modem); +} + +char* atmodem_exec(struct modem* modem) +{ + static char respbuf[128]; + char* resp = ok(modem); + modem->buf[modem->buflen] = '\0'; + for(char* p = modem->buf; *p != '\0';) { + char ch = toupper(*p); + p++; + if(strchr(supported_cmds, ch) == NULL) + return error(modem); + if(strchr(string_cmds, ch) == NULL) { + if(ch == '&') { + p++; + ch = toupper(*p); // unused + ulong val = strtoul(p, &p, 10); // unused + continue; + } + // Numeric argument commands + ulong val = strtoul(p, &p, 10); + switch(ch) { + case 'A': + return answer(modem); + case 'E': + modem->echo_off = !val; + break; + case 'H': + modem->offhook = val; + modem->ringing = false; + if(!modem->offhook) { + if(sock != INVALID_SOCKET) { + disconnect(modem); + } + } + break; + case 'I': + sprintf(respbuf, "\r\n" TITLE " v" VERSION " Copyright %s Rob Swindell\r\n", &__DATE__[7]); + return respbuf; + case 'O': + if(sock == INVALID_SOCKET) + return error(modem); + modem->online = true; + return connect_result(modem); + break; + case 'V': + modem->numeric_mode = !val; + resp = ok(modem); // Use the new verbal/numeric mode in response (ala USRobotics) + break; + case 'Q': + modem->quiet = val; + resp = ok(modem); // Use the new quiet/verbose mode in response (ala USRobotics) + break; + case 'S': + if(*p == '=') { + ulong sreg = val; + ulong val = strtoul(p + 1, &p, 10); + dprintf("S%lu = %lu", sreg, val); + switch(sreg) { + case 0: + if(val && listening_sock == INVALID_SOCKET) + return error(modem); + modem->auto_answer = val; + break; + case 1: + modem->ringcount = val; + break; + case 2: + modem->esc = (char)val; + break; + case 3: + modem->cr = (char)val; + break; + case 4: + modem->lf = (char)val; + break; + case 5: + modem->bs = (char)val; + break; + case 7: + modem->dial_wait = val; + break; + case 12: + modem->guard_time = val; + break; + } + } else if(*p == '?') { + switch(val) { + case 0: + val = modem->auto_answer; + break; + case 1: + val = modem->ringcount; + break; + case 2: + val = modem->esc; + break; + case 3: + val = modem->cr; + break; + case 4: + val = modem->lf; + break; + case 5: + val = modem->bs; + break; + case 7: + val = modem->dial_wait; + break; + case 12: + val = modem->guard_time; + break; + default: + val = 0; + break; + } + sprintf(respbuf, "%c%03lu%c%c%s", modem->lf, val, modem->cr, modem->lf, ok(modem)); + return respbuf; + } else + return error(modem); + break; + case 'X': + modem->ext_results = val; + break; + case 'Z': + init(modem); + break; + } + } else { // string argument commands + switch(ch) { + case 'D': + if(sock != INVALID_SOCKET) + return error(modem); + if(*p == 'T' /* tone */|| *p == 'P' /* pulse */) + p++; + return dial(modem, p); + } + } + } + return resp; +} + +char* atmodem_parsech(struct modem* modem, uint8_t ch) +{ + switch(modem->cmdstate) { + case INIT: + if(toupper(ch) == 'A') + modem->cmdstate = A; + break; + case A: + if(toupper(ch) == 'T') + modem->cmdstate = AT; + else + newcmd(modem); + break; + case AT: + if(ch == modem->cr) { + char* retval = atmodem_exec(modem); + newcmd(modem); + return retval; + } else if(ch == modem->bs) { + if(modem->buflen) + modem->buflen--; + } else { + if(modem->buflen >= sizeof(modem->buf)) + return error(modem); + if(ch != ' ') + modem->buf[modem->buflen++] = ch; + } + break; + } + return NULL; +} + +char* atmodem_parse(struct modem* modem, uint8_t* buf, size_t len) +{ + for(size_t i = 0; i < len; i++) { + char* resp = atmodem_parsech(modem, buf[i]); + if(resp != NULL) + return resp; + } + return NULL; +} + +BOOL vdd_write(HANDLE* slot, uint8_t* buf, size_t buflen) +{ + if(*slot == INVALID_HANDLE_VALUE) { + char path[MAX_PATH + 1]; + sprintf(path, "\\\\.\\mailslot\\sbbsexec\\wr%d", cfg.node_num); + *slot = CreateFile(path + ,GENERIC_WRITE + ,FILE_SHARE_READ + ,NULL + ,OPEN_EXISTING + ,FILE_ATTRIBUTE_NORMAL + ,(HANDLE) NULL); + if(*slot == INVALID_HANDLE_VALUE) { + dprintf("!ERROR %u (%s) opening '%s'", GetLastError(), strerror(errno), path); + exit(1); + } + } + DWORD wr = 0; + BOOL result = WriteFile(*slot, buf, buflen, &wr, /* LPOVERLAPPED */NULL); + if(wr != buflen) + dprintf("WriteFile wrote %ld instead of %ld", wr, buflen); + return result; +} + +BOOL vdd_writestr(HANDLE* slot, char* str) +{ + return vdd_write(slot, str, strlen(str)); +} + +void listen_thread(void* arg) +{ + struct modem* modem = (struct modem*)arg; + + for(;;) { + fd_set fds = {0}; + FD_SET(listening_sock, &fds); + struct timeval tv = { 1, 0 }; + if(select(/* ignored: */0, &fds, NULL, NULL, &tv) == 1) { + if(sock != INVALID_SOCKET) { // In-use + SOCKADDR_IN addr; + socklen_t addrlen = sizeof(addr); + SOCKET newsock = accept(listening_sock, (SOCKADDR*)&addr, &addrlen); + if(newsock != INVALID_SOCKET) { + char* busy_notice = "\r\nSorry, not available right now\r\n"; + send(newsock, busy_notice, strlen(busy_notice), /* flags: */0); + shutdown(newsock, SD_SEND); + closesocket(newsock); + } + continue; + } + if(!modem->offhook && !modem->online) + modem->ringing = true; + } + } +} + +int main(int argc, char** argv) +{ + int argn = 1; + char tmp[256]; + char path[MAX_PATH + 1]; + char fullmodemline[MAX_PATH + 1]; + uint8_t buf[XTRN_IO_BUF_LEN]; + size_t rx_buflen = sizeof(buf); + ULONGLONG rx_delay = 0; + WSADATA WSAData; + int result; + + fprintf(stderr, TITLE " v" VERSION " Copyright %s Rob Swindell\n", &__DATE__[7]); + if((result = WSAStartup(MAKEWORD(1,1), &WSAData)) == 0) + dprintf("%s %s",WSAData.szDescription, WSAData.szSystemStatus); + else { + fprintf(stderr,"!WinSock startup ERROR %d", result); + return EXIT_FAILURE; + } + + listening_interface.addr.sa_family = cfg.address_family; + + for(; argn < argc; argn++) { + char* arg = argv[argn]; + if(*arg != '-') + break; + while(*arg == '-') + arg++; + switch(*arg) { + case '6': + listening_interface.addr.sa_family = AF_INET6; + break; + case 'l': + cfg.listen = true; + arg++; + if(*arg != '\0') { + if(inet_ptoaddr(arg, &listening_interface, sizeof(listening_interface)) == NULL) { + fprintf(stderr, "!Error parsing network address: %s", arg); + return EXIT_FAILURE; + } + } + break; + case 'p': + inet_setaddrport(&listening_interface, atoi(arg + 1)); + break; + case 'd': + cfg.debug = true; + break; + case 'h': + sock = strtoul(arg + 1, NULL, 10); + break; + case 'r': + cfg.data_rate = strtoul(arg + 1, NULL, 10); + break; + case 'b': + rx_buflen = min(strtoul(arg + 1, NULL, 10), sizeof(buf)); + case 'R': + rx_delay = strtoul(arg + 1, NULL, 10); + break; + default: + usage(); + break; + } + } + if(argn >= argc) { + usage(); + } + + struct modem modem = {0}; + init(&modem); + + if(cfg.listen) { + if(sock != INVALID_SOCKET) + listening_sock = sock; + else { + listening_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); + if(listening_sock == INVALID_SOCKET) { + fprintf(stderr, "Error %ld creating socket\n", WSAGetLastError()); + return EXIT_FAILURE; + } + } + result = bind(listening_sock, &listening_interface.addr, xp_sockaddr_len(&listening_interface)); + if(result != 0) { + fprintf(stderr, "Error %d binding socket\n", WSAGetLastError()); + return EXIT_FAILURE; + } + if(listen(listening_sock, /* backlog: */1) != 0) { + fprintf(stderr, "Error %d listening on socket\n", WSAGetLastError()); + return EXIT_FAILURE; + } + fprintf(stderr, "Listening on TCP port %u at %s\n" + ,inet_addrport(&listening_interface), inet_addrtop(&listening_interface, tmp, sizeof(tmp))); + + _beginthread(listen_thread, /* stack_size: */0, &modem); + } else { + if(sock != INVALID_SOCKET) + connected(&modem); + } + + const char* dropfile = "dosxtrn.env"; + FILE* fp = fopen(dropfile, "w"); + if(fp == NULL) { + perror(dropfile); + return EXIT_FAILURE; + } + char* dospgm = argv[argn]; + for(; argn < argc; argn++) { + fprintf(fp, "%s ", argv[argn]); + } + fputc('\n', fp); + fclose(fp); + + while(1) { + sprintf(path, "\\\\.\\mailslot\\sbbsexec\\rd%d", cfg.node_num); + rdslot = CreateMailslot(path + ,sizeof(buf)/2 // Maximum message size (0=unlimited) + ,0 // Read time-out + ,NULL); // Security + if(rdslot!=INVALID_HANDLE_VALUE) + break; + if(cfg.node_num == 0xff) { + fprintf(stderr, "Error %ld creating '%s'\n", GetLastError(), path); + return EXIT_FAILURE; + } + ++cfg.node_num; + } + + sprintf(path, "sbbsexec_carrier%d", cfg.node_num); + carrier_event = CreateEvent( + NULL // pointer to security attributes + ,FALSE // flag for manual-reset event + ,FALSE // flag for initial state + ,path // pointer to event-object name + ); + if(carrier_event == NULL) { + fprintf(stderr, "Error %ld creating '%s'\n", GetLastError(), path); + return EXIT_FAILURE; + } + + sprintf(path, "sbbsexec_hangup%d", cfg.node_num); + hangup_event = CreateEvent( + NULL // pointer to security attributes + ,FALSE // flag for manual-reset event + ,FALSE // flag for initial state (DTR = high) + ,path // pointer to event-object name + ); + if(hangup_event == NULL) { + fprintf(stderr, "Error %ld creating '%s'\n", GetLastError(), path); + return EXIT_FAILURE; + } + + sprintf(path, "sbbsexec_hungup%d", cfg.node_num); + hungup_event = CreateEvent( + NULL // pointer to security attributes + ,TRUE // flag for manual-reset event + ,TRUE // flag for initial state (DCD = low) + ,path // pointer to event-object name + ); + if(hungup_event == NULL) { + fprintf(stderr, "Error %ld creating '%s'\n", GetLastError(), path); + return EXIT_FAILURE; + } + + STARTUPINFO startup_info={0}; + startup_info.cb=sizeof(startup_info); + + BOOL x64 = FALSE; + IsWow64Process(GetCurrentProcess(), &x64); + sprintf(fullmodemline, "dosxtrn.exe %s %s %u svdm.ini", dropfile, x64 ? "x64" : "NT", cfg.node_num); + + PROCESS_INFORMATION process_info; + if(!CreateProcess( + NULL, // pointer to name of executable module + fullmodemline, // pointer to command line string + NULL, // process security attributes + NULL, // thread security attributes + FALSE, // handle inheritance flag + 0, //CREATE_NEW_CONSOLE/*|CREATE_SEPARATE_WOW_VDM*/, // creation flags + NULL, // pointer to new environment block + NULL , // pointer to current directory name + &startup_info, // pointer to STARTUPINFO + &process_info // pointer to PROCESS_INFORMATION + )) { + fprintf(stderr, "Error %ld executing '%s'", GetLastError(), fullmodemline); + return EXIT_FAILURE; + } + printf("Executed '%s' successfully\n", fullmodemline); + + CloseHandle(process_info.hThread); + + if(cfg.data_rate > 0) { + rx_buflen = max(cfg.data_rate / 100, 1); + rx_delay = (ULONGLONG) (1000 * ((double)rx_buflen / cfg.data_rate)); + } + + ULONGLONG lastring = 0; + ULONGLONG lasttx = 0; + ULONGLONG lastrx = 0; + int largest_recv = 0; + + while(WaitForSingleObject(process_info.hProcess,0) != WAIT_OBJECT_0) { + ULONGLONG now = xp_timer64(); + if(modem.online) { + fd_set fds = {0}; + FD_SET(sock, &fds); + struct timeval tv = { 0, 0 }; + if(now - lastrx >= rx_delay && select(/* ignored: */0, &fds, NULL, NULL, &tv) == 1) { + dprintf("select returned 1"); + int rd = recv(sock, buf, rx_buflen, /* flags: */0); + dprintf("recv returned %d", rd); + if(rd <= 0) { + int error = WSAGetLastError(); + if(rd == 0 || error == WSAECONNRESET) { + dprintf("Connection reset detected"); + disconnect(&modem); + vdd_writestr(&wrslot, response(&modem, NO_CARRIER)); + continue; + } + dprintf("Socket error %ld on recv", error); + continue; + } + if(rd > largest_recv) + largest_recv = rd; + vdd_write(&wrslot, buf, rd); + lastrx = now; + } + if(WaitForSingleObject(hangup_event, 0) == WAIT_OBJECT_0) { + dprintf("hangup_event signaled"); + disconnect(&modem); + vdd_writestr(&wrslot, response(&modem, NO_CARRIER)); + } + } else { + if(modem.ringing) { + if(modem.ringcount < 1) + dprintf("Incoming connection"); + if(now - lastring > RING_DELAY) { + dprintf("RING"); + vdd_writestr(&wrslot, response(&modem, RING)); + lastring = now; + modem.ringcount++; + if(modem.auto_answer > 0 && modem.ringcount >= modem.auto_answer) { + vdd_writestr(&wrslot, answer(&modem)); + } + } + } + if(cfg.terminate_on_disconnect) { + dprintf("Terminating process on disconnect"); + TerminateProcess(process_info.hProcess, 2112); + } + } + + size_t rd = 0; + size_t len = sizeof(buf); +// avail=RingBufFree(&outbuf)/2; // leave room for telnet expansion +// if(len>avail) +// len=avail; + + while(rd<len) { + unsigned long waiting = 0; + unsigned long msglen = 0; + + GetMailslotInfo( + rdslot, // mailslot handle + NULL, // address of maximum message size + NULL, // address of size of next message + &waiting, // address of number of messages + NULL // address of read time-out + ); + if(!waiting) + break; + if(ReadFile(rdslot, buf+rd, len-rd, &msglen, NULL)==FALSE || msglen<1) + break; + rd+=msglen; + } + if(rd) { + if(modem.online) { + if(modem.esc_count) { + if(modem.esc_count >= 3) + modem.esc_count = 0; + else { + if(now - lasttx < guard_time(&modem)) + if(*buf == modem.esc) + modem.esc_count += count_esc(&modem, buf, rd); + else + modem.esc_count = 0; + } + } else { + if(now - lasttx > guard_time(&modem)) + modem.esc_count = count_esc(&modem, buf, rd); + } + int wr = send(sock, buf, rd, /* flags: */0); + if(wr != rd) + dprintf("Sent %d instead of %d", wr, rd); + else if(cfg.debug) + dprintf("TX: %d bytes", wr); + } else { // Command mode + dprintf("RX command: '%.*s'\n", rd, buf); + if(!modem.echo_off) + vdd_write(&wrslot, buf, rd); + char* response = atmodem_parse(&modem, buf, rd); + if(response != NULL) { + vdd_writestr(&wrslot, response); + SKIP_WHITESPACE(response); + dprintf("Modem response: %s", response); + } + } + lasttx = now; + } else { + if(modem.online && modem.esc_count == 3 && now - lasttx >= guard_time(&modem)) { + dprintf("Entering command mode"); + modem.online = false; + modem.esc_count = 0; + vdd_writestr(&wrslot, ok(&modem)); + } + } + } + + int retval = EXIT_SUCCESS; + fp = fopen("DOSXTRN.RET", "r"); + if(fp == NULL) { + perror("DOSXTRN.RET"); + } else { + if(fscanf(fp, "%d", &retval) != 1) { + fprintf(stderr, "Error reading return value from DOSXTRN.REG"); + retval = EXIT_FAILURE; + } + fclose(fp); + if(retval == -1) { + fprintf(stderr, "DOSXTRN failed to execute '%s': ", dospgm); + fp = fopen("DOSXTRN.ERR", "r"); + if(fp == NULL) { + perror("DOSXTRN.ERR"); + } else { + char errstr[256] = ""; + int errval = 0; + if(fscanf(fp, "%d\n", &errval) == 1) { + fgets(errstr, sizeof(errstr), fp); + truncsp(errstr); + fprintf(stderr, "Error %d (%s)\n", errval, errstr); + } else + fprintf(stderr, "Failed to parse DOSXTRN.ERR\n"); + fclose(fp); + } + } + } + + if(cfg.debug) { + printf("rx_delay: %lld\n", rx_delay); + printf("rx_buflen: %ld\n", rx_buflen); + printf("largest recv: %d\n", largest_recv); + } + return retval; +} diff --git a/src/vdmodem/vdmodem.sln b/src/vdmodem/vdmodem.sln new file mode 100644 index 0000000000000000000000000000000000000000..ef3ad4cb388e576bbeb46d34a19a3708af3768e8 --- /dev/null +++ b/src/vdmodem/vdmodem.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.32106.194 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vdmodem", "vdmodem.vcxproj", "{20051597-6298-4098-8F26-E408C2880FE4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {20051597-6298-4098-8F26-E408C2880FE4}.Debug|x86.ActiveCfg = Debug|Win32 + {20051597-6298-4098-8F26-E408C2880FE4}.Debug|x86.Build.0 = Debug|Win32 + {20051597-6298-4098-8F26-E408C2880FE4}.Release|x86.ActiveCfg = Release|Win32 + {20051597-6298-4098-8F26-E408C2880FE4}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9F30FE81-9971-48D0-AC51-EE3F15248CB0} + EndGlobalSection +EndGlobal diff --git a/src/vdmodem/vdmodem.vcxproj b/src/vdmodem/vdmodem.vcxproj new file mode 100644 index 0000000000000000000000000000000000000000..584f332ccf30b33cad25b020ec7cd7e3a8530c85 --- /dev/null +++ b/src/vdmodem/vdmodem.vcxproj @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>16.0</VCProjectVersion> + <Keyword>Win32Proj</Keyword> + <ProjectGuid>{20051597-6298-4098-8f26-e408c2880fe4}</ProjectGuid> + <RootNamespace>svdmodem</RootNamespace> + <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>NotSet</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>NotSet</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>NotSet</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v142</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>NotSet</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\build\undeprecate.props" /> + <Import Project="..\xpdev\xpdev_mt.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\build\undeprecate.props" /> + <Import Project="..\xpdev\xpdev_mt.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\build\undeprecate.props" /> + <Import Project="..\xpdev\xpdev_mt.props" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + <Import Project="..\build\undeprecate.props" /> + <Import Project="..\xpdev\xpdev_mt.props" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <TargetName>svdm</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <TargetName>svdm</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>true</LinkIncremental> + <TargetName>svdm</TargetName> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <TargetName>svdm</TargetName> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + </ClCompile> + <Link> + <SubSystem>Console</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\xpdev\genwrap.c" /> + <ClCompile Include="..\xpdev\sockwrap.c" /> + <ClCompile Include="vdmodem.c" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file