///////////////////////////////////////////////////////////////// // POP65 // Post Office Protocol v3 (POP3) Client for IP65 // https://www.ietf.org/rfc/rfc1939.txt // (Based on IP65's wget65.c) // Bobbi June 2020 ///////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include #include #include #include "../inc/ip65.h" #include "w5100.h" #include "w5100_http.h" #include "linenoise.h" #define BACKSPACE 8 // Both pragmas are obligatory to have cc65 generate code // suitable to access the W5100 auto-increment registers. #pragma optimize (on) #pragma static-locals (on) unsigned char buf[1500+1]; // One extra byte for null terminator char filename[80]; int len; FILE *fp; uint32_t filesize; // Configuration params from POP65.CFG char cfg_server[80]; // IP of POP3 server char cfg_user[80]; // Username char cfg_pass[80]; // Password char cfg_spooldir[80]; // ProDOS directory to spool email to char cfg_inboxdir[80]; // ProDOS directory for email inbox /* * Keypress before quit */ void confirm_exit(void) { printf("\nPress any key "); cgetc(); exit(0); } /* * Called for all non IP65 errors */ void error_exit() { confirm_exit(); } /* * Called if IP65 call fails */ void ip65_error_exit(void) { printf("%s\n", ip65_strerror(ip65_error)); confirm_exit(); } /* * Print message to the console, stripping extraneous CRLF stuff * from the end. */ void print_strip_crlf(char *s) { uint8_t i = 0; while ((s[i] != '\0') && (s[i] != '\r') && (s[i] != '\n')) putchar(s[i++]); putchar('\n'); } /* * Spinner while downloading files */ void spinner(uint32_t sz, uint8_t final) { static char chars[] = "|/-\\"; static char buf[10] = ""; static uint8_t i = 0; uint8_t j; for (j = 0; j < strlen(buf); ++j) putchar(BACKSPACE); if (final) { sprintf(buf, " [%lu]\n", sz); printf("%s", buf); strcpy(buf, ""); } else { sprintf(buf, "%c %lu", chars[(i++) % 4], sz); printf("%s", buf); } } #define DO_SEND 1 // For do_send param #define DONT_SEND 0 // For do_send param #define CMD_MODE 0 // For mode param #define DATA_MODE 1 // For mode param // Modified verson of w5100_http_open from w5100_http.c // Sends a TCP message and receives a the first packet of the response. // sendbuf is the buffer to send (null terminated) // recvbuf is the buffer into which the received message will be written // length is the length of recvbuf[] // do_send Do the sending if true, otherwise skip // mode Binary mode for received message, maybe first block of long message bool w5100_tcp_send_recv(char* sendbuf, char* recvbuf, size_t length, uint8_t do_send, uint8_t mode) { if (do_send == DO_SEND) { uint16_t snd; uint16_t pos = 0; uint16_t len = strlen(sendbuf); if (strncmp(sendbuf, "PASS", 4) == 0) printf(">PASS ****\n"); else { putchar('>'); print_strip_crlf(sendbuf); } while (len) { if (input_check_for_abort_key()) { printf("User abort\n"); w5100_disconnect(); return false; } snd = w5100_send_request(); if (!snd) { if (!w5100_connected()) { printf("Connection lost\n"); return false; } continue; } if (len < snd) snd = len; { // One less to allow for faster pre-increment below const char *dataptr = sendbuf + pos - 1; uint16_t i; for (i = 0; i < snd; ++i) { // The variable is necessary to have cc65 generate code // suitable to access the W5100 auto-increment register. char data = *++dataptr; *w5100_data = data; } } w5100_send_commit(snd); len -= snd; pos += snd; } } if (mode == DATA_MODE) { // // Handle email body // uint16_t rcv, written; uint16_t len = 0; uint8_t cont = 1; // Backspace to put spinner on same line as RETR for (rcv = 0; rcv < 15; ++rcv) putchar(BACKSPACE); filesize = 0; while(cont) { if (input_check_for_abort_key()) { printf("User abort\n"); w5100_disconnect(); return false; } rcv = w5100_receive_request(); if (!rcv) { cont = w5100_connected(); if (cont) continue; } if (rcv > length - len) rcv = length - len; { // One less to allow for faster pre-increment below char *dataptr = recvbuf + len - 1; uint16_t i; for (i = 0; i < rcv; ++i) { // The variable is necessary to have cc65 generate code // suitable to access the W5100 auto-increment register. char data = *w5100_data; *++dataptr = data; if (!memcmp(dataptr - 4, "\r\n.\r\n", 5)) cont = 0; } } w5100_receive_commit(rcv); len += rcv; written = fwrite(recvbuf, 1, len, fp); if (written != len) { printf("Write error"); fclose(fp); error_exit(); } filesize += len; spinner(filesize, 0); len = 0; } } else { // // Handle short single packet ASCII text responses // uint16_t rcv; uint16_t len = 0; while(1) { if (input_check_for_abort_key()) { printf("User abort\n"); w5100_disconnect(); return false; } rcv = w5100_receive_request(); if (rcv) break; if (!w5100_connected()) { printf("Connection lost\n"); return false; } } if (rcv > length - len) rcv = length - len; { // One less to allow for faster pre-increment below char *dataptr = recvbuf + len - 1; uint16_t i; for (i = 0; i < rcv; ++i) { // The variable is necessary to have cc65 generate code // suitable to access the W5100 auto-increment register. char data = *w5100_data; *++dataptr = data; } w5100_receive_commit(rcv); len += rcv; } putchar('<'); print_strip_crlf(recvbuf); } return true; } /* * Check expected string from server */ void expect(char *buf, char *s) { if (strncmp(buf, s, strlen(s)) != 0) { printf("\nExpected '%s' got '%s\n", s, buf); error_exit(); } } /* * Read parms from POP65.CFG */ void readconfigfile(void) { fp = fopen("POP65.CFG", "r"); if (!fp) { puts("Can't open config file POP65.CFG"); error_exit(); } fscanf(fp, "%s", cfg_server); fscanf(fp, "%s", cfg_user); fscanf(fp, "%s", cfg_pass); fscanf(fp, "%s", cfg_spooldir); fscanf(fp, "%s", cfg_inboxdir); fclose(fp); } /* * Update INBOX * Copy messages from spool dir to inbox with following changes: * - Convert CR+LF endings to CR only (Apple ][ style) <-- TODO * - Look for headers of interest (Date, From, To, BCC, Subject) */ void update_inbox(uint16_t nummsgs) { uint16_t msg, l; char *p; FILE *destfp; for (msg = 1; msg <= nummsgs; ++msg) { sprintf(filename, "%s/EMAIL.%u", cfg_spooldir, msg); puts(filename); fp = fopen(filename, "r"); if (!fp) { printf("Can't open %s\n", filename); error_exit(); } sprintf(filename, "%s/EMAIL.%u", cfg_inboxdir, msg); destfp = fopen(filename, "wb"); if (!destfp) { printf("Can't open %s\n", filename); error_exit(); } while (!feof(fp)) { l = fread(buf, 1, 1024, fp); buf[l] = '\0'; p = strstr(buf, "\r\nDate:"); if (p) { printf("D "); print_strip_crlf(p+2); } p = strstr(buf, "\r\nFrom:"); if (p) { printf("F "); print_strip_crlf(p+2); } p = strstr(buf, "\r\nTo:"); if (p) { printf("T "); print_strip_crlf(p+2); } p = strstr(buf, "\r\nBcc:"); if (p) { printf("B "); print_strip_crlf(p+2); } p = strstr(buf, "\r\nSubject:"); if (p) { printf("S "); print_strip_crlf(p+2); } fwrite(buf, 1, l, destfp); } fclose(fp); close(destfp); } } int main(void) { uint8_t eth_init = ETH_INIT_DEFAULT; char sendbuf[80]; uint16_t msg, nummsgs; uint32_t bytes; videomode(VIDEOMODE_80COL); printf("\nReading POP65.CFG -"); readconfigfile(); printf(" Ok"); { int file; printf("\nSetting slot - "); file = open("ethernet.slot", O_RDONLY); if (file != -1) { read(file, ð_init, 1); close(file); eth_init &= ~'0'; } } printf("%d\nInitializing %s - ", eth_init, eth_name); if (ip65_init(eth_init)) { ip65_error_exit(); } // Abort on Ctrl-C to be consistent with Linenoise abort_key = 0x83; printf("Ok\nObtaining IP address - "); if (dhcp_init()) { ip65_error_exit(); } // Copy IP config from IP65 to W5100 w5100_config(eth_init); printf("Ok\nConnecting to %s - ", cfg_server); if (!w5100_connect(parse_dotted_quad(cfg_server), 110)) { printf("Fail\n"); error_exit(); } printf("Ok\n\n"); if (!w5100_tcp_send_recv(sendbuf, buf, 1500, DONT_SEND, CMD_MODE)) { error_exit(); } expect(buf, "+OK"); sprintf(sendbuf, "USER %s\r\n", cfg_user); if (!w5100_tcp_send_recv(sendbuf, buf, 1500, DO_SEND, CMD_MODE)) { error_exit(); } expect(buf, "+OK"); sprintf(sendbuf, "PASS %s\r\n", cfg_pass); if (!w5100_tcp_send_recv(sendbuf, buf, 1500, DO_SEND, CMD_MODE)) { error_exit(); } expect(buf, "+OK Logged in."); if (!w5100_tcp_send_recv("STAT\r\n", buf, 1500, DO_SEND, CMD_MODE)) { error_exit(); } sscanf(buf, "+OK %u %lu", &nummsgs, &bytes); printf(" %u message(s), %lu total bytes\n", nummsgs, bytes); for (msg = 1; msg <= nummsgs; ++msg) { sprintf(filename, "%s/EMAIL.%u", cfg_spooldir, msg); remove(filename); /// TO MAKE DEBUGGING EASIER - GET RID OF THIS fp = fopen(filename, "wb"); if (!fp) { printf("Can't create %s\n", filename); error_exit(); } sprintf(sendbuf, "RETR %u\r\n", msg); if (!w5100_tcp_send_recv(sendbuf, buf, 1500, DO_SEND, DATA_MODE)) { error_exit(); } fclose(fp); spinner(filesize, 1); // Cleanup spinner } // Ignore any error - can be a race condition where other side // disconnects too fast and we get an error w5100_tcp_send_recv("QUIT\r\n", buf, 1500, DO_SEND, CMD_MODE); printf("Disconnecting\n"); w5100_disconnect(); printf("Updating INBOX ...\n"); update_inbox(nummsgs); confirm_exit(); }