diff --git a/apps/Makefile b/apps/Makefile index e73e904..8c1d1bd 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -45,7 +45,7 @@ TCP =\ tweet65 \ pop65-slow -bin: wget65.bin pop65.bin smtp65.bin email.bin rebuild.bin edit.bin +bin: wget65.bin pop65.bin smtp65.bin email.bin rebuild.bin edit.bin attacher.bin wget65.bin: w5100.c w5100_http.c linenoise.c wget65.bin: IP65LIB = ../ip65/ip65.lib @@ -135,27 +135,29 @@ ip65.d64: prg ip65.dsk: bin cp ../build/800k.po $@ - java -jar $(AC) -as $@ date65 < date65.bin - java -jar $(AC) -p $@ date65.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ edit < edit.bin - java -jar $(AC) -p $@ edit.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ email < email.bin - java -jar $(AC) -p $@ email.cfg txt < email.cfg - java -jar $(AC) -p $@ email.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ hfs65 < hfs65.bin - java -jar $(AC) -p $@ hfs65.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ pop65 < pop65.bin - java -jar $(AC) -p $@ pop65.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ rebuild < rebuild.bin - java -jar $(AC) -p $@ rebuild.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ smtp65 < smtp65.bin - java -jar $(AC) -p $@ smtp65.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -as $@ telnet65 < telnet65.bin - java -jar $(AC) -as $@ tweet65 < tweet65.bin - java -jar $(AC) -p $@ tweet65.system sys < $(CC65)/apple2enh/util/loader.system - java -jar $(AC) -p $@ tzone.txt txt < tzone.txt - java -jar $(AC) -as $@ wget65 < wget65.bin - java -jar $(AC) -p $@ wget65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ attacher < attacher.bin + java -jar $(AC) -p $@ attacher.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ date65 < date65.bin + java -jar $(AC) -p $@ date65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ edit < edit.bin + java -jar $(AC) -p $@ edit.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ email < email.bin + java -jar $(AC) -p $@ email.cfg txt < email.cfg + java -jar $(AC) -p $@ email.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ hfs65 < hfs65.bin + java -jar $(AC) -p $@ hfs65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ pop65 < pop65.bin + java -jar $(AC) -p $@ pop65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ rebuild < rebuild.bin + java -jar $(AC) -p $@ rebuild.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ smtp65 < smtp65.bin + java -jar $(AC) -p $@ smtp65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -as $@ telnet65 < telnet65.bin + java -jar $(AC) -as $@ tweet65 < tweet65.bin + java -jar $(AC) -p $@ tweet65.system sys < $(CC65)/apple2enh/util/loader.system + java -jar $(AC) -p $@ tzone.txt txt < tzone.txt + java -jar $(AC) -as $@ wget65 < wget65.bin + java -jar $(AC) -p $@ wget65.system sys < $(CC65)/apple2enh/util/loader.system ip65.atr: com mkdir atr diff --git a/apps/attacher.c b/apps/attacher.c new file mode 100644 index 0000000..801d5a3 --- /dev/null +++ b/apps/attacher.c @@ -0,0 +1,327 @@ +///////////////////////////////////////////////////////////////////////////// +// Handle attaching files to outgoing messages +// Bobbi July 2020 +///////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include "email_common.h" + +#define BELL 0x07 +#define BACKSPACE 0x08 +#define RETURN 0x0d +#define NORMAL 0x0e +#define INVERSE 0x0f +#define DELETE 0x7f + +#define NETBUFSZ 1500 +#define LINEBUFSZ 1000 // According to RFC2822 Section 2.1.1 (998+CRLF) +#define READSZ 1024 // Must be less than NETBUFSZ to fit in buf[] + +static unsigned char buf[NETBUFSZ+1]; // One extra byte for null terminator +static char linebuf[LINEBUFSZ]; +static char userentry[80]; +static uint8_t quit_to_email; // If 1, launch EMAIL.SYSTEM on quit +static char filename[80]; + +#define ERR_NONFATAL 0 +#define ERR_FATAL 1 + +/* + * Show error messages + */ +void error(uint8_t fatal, const char *fmt, ...) { + va_list v; + if (fatal) { + videomode(VIDEOMODE_80COL); + clrscr(); + printf("\n\n%cFATAL ERROR:%c\n\n", INVERSE, NORMAL); + va_start(v, fmt); + vprintf(fmt, v); + va_end(v); + printf("\n\n\n\n[Press Any Key To Quit]"); + cgetc(); + exit(1); + } else { + va_start(v, fmt); + vprintf(fmt, v); + va_end(v); + printf(" - [Press Any Key]"); + cgetc(); + } +} + +/* + * Spinner while encoding attachments + */ +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); + } +} + +/* + * Read parms from EMAIL.CFG + */ +void readconfigfile(void) { + FILE *fp = fopen("EMAIL.CFG", "r"); + if (!fp) + error(ERR_FATAL, "Can't open config file EMAIL.CFG"); + fscanf(fp, "%s", cfg_server); + fscanf(fp, "%s", cfg_user); + fscanf(fp, "%s", cfg_pass); + fscanf(fp, "%s", cfg_pop_delete); + fscanf(fp, "%s", cfg_smtp_server); + fscanf(fp, "%s", cfg_smtp_domain); + fscanf(fp, "%s", cfg_instdir); + fscanf(fp, "%s", cfg_emaildir); + fscanf(fp, "%s", cfg_emailaddr); + fclose(fp); +} + +/* + * Read a text file a line at a time leaving the line in linebuf[] + * Returns number of chars in the line, or -1 if EOF. + * Expects Apple ][ style line endings (CR) and does no conversion + * fp - file to read from + * reset - if 1 then just reset the buffer and return + */ +int16_t get_line(FILE *fp, uint8_t reset) { + static uint16_t rd = 0; // Read + static uint16_t wt = 0; // Write + uint8_t found = 0; + uint16_t j = 0; + uint16_t i; + if (reset) { + rd = wt = 0; + return 0; + } + while (1) { + while (rd < wt) { + linebuf[j++] = buf[rd++]; + if (linebuf[j - 1] == '\r') { + found = 1; + break; + } + } + linebuf[j] = '\0'; + if (rd == wt) // Empty buf + rd = wt = 0; + if (found) { + return j; + } + if (feof(fp)) { + return -1; + } + i = fread(&buf[wt], 1, READSZ - wt, fp); + wt += i; + } +} + +/* + * Base64 encode table + */ +static const char b64enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/* + * Encode Base64 format + * p - Pointer to source buffer + * q - Pointer to destination buffer + * len - Length of buffer to encode + * Returns length of encoded data + */ +uint16_t encode_base64(char *p, char *q, uint16_t len) { + uint16_t j = 0; + uint16_t i, ii; + for (i = 0; i < len / 3; ++i) { + ii = 3 * i; + q[j++] = b64enc[(p[ii] & 0xfc) >> 2]; + q[j++] = b64enc[((p[ii] & 0x03) << 4) | ((p[ii + 1] & 0xf0) >> 4)]; + q[j++] = b64enc[((p[ii + 1] & 0x0f) << 2) | ((p[ii + 2] & 0xc0) >> 6)]; + q[j++] = b64enc[(p[ii + 2] & 0x3f)]; + if (((i + 1) % 18) == 0) + q[j++] = '\r'; + } + ii += 3; + i = len - ii; // Bytes remaining to encode + switch (i) { + case 1: + q[j++] = b64enc[(p[ii] & 0xfc) >> 2]; + q[j++] = b64enc[(p[ii] & 0x03) << 4]; + q[j++] = '='; + q[j++] = '='; + break; + case 2: + q[j++] = b64enc[(p[ii] & 0xfc) >> 2]; + q[j++] = b64enc[((p[ii] & 0x03) << 4) | ((p[ii + 1] & 0xf0) >> 4)]; + q[j++] = b64enc[(p[ii + 1] & 0x0f) << 2]; + q[j++] = '='; + break; + } + q[j] = '\0'; + return j; +} + +/* + * Prompt for a name, store it in userentry + * Returns number of chars read. + * prompt - Message to display before > prompt + * is_file - if 1, restrict chars to those allowed in ProDOS filename + * Returns number of chars read + */ +uint8_t prompt_for_name(char *prompt, uint8_t is_file) { + uint16_t i; + char c; + printf("%s>", prompt); + i = 0; + while (1) { + c = cgetc(); + if (is_file && !isalnum(c) && + (c != RETURN) && (c != BACKSPACE) && (c != DELETE) && + (c != '.') && (c != '/')) { + putchar(BELL); + continue; + } + switch (c) { + case RETURN: + goto done; + case BACKSPACE: + case DELETE: + if (i > 0) { + putchar(BACKSPACE); + putchar(' '); + putchar(BACKSPACE); + --i; + } else + putchar(BELL); + break; + default: + putchar(c); + userentry[i++] = c; + } + if (i == 79) + goto done; + } +done: + userentry[i] = '\0'; + return i; +} + +/* + * Optionally attach files to outgoing email. + * filename - Name of file containing email message + */ +void attach(char *fname) { + FILE *fp, *fp2, *destfp; + uint16_t chars, i, size; + videomode(VIDEOMODE_80COL); + printf("%c%s ATTACHER%c\n\n", 0x0f, PROGNAME, 0x0e); + fp = fopen(fname, "rb+"); + if (!fp) + error(ERR_FATAL, "Can't open %s", fname); + sprintf(filename, "%s/OUTBOX/TMPFILE", cfg_emaildir); + destfp = fopen(filename, "wb"); + if (!destfp) + error(ERR_FATAL, "Can't open TMPFILE"); + + get_line(fp, 1); // Reset buffer + printf("Copying email content ... "); // Space is for spinner to eat + size = 0; + while ((chars = get_line(fp, 0)) != -1) { + size += chars; + if (linebuf[0] == '\r') + break; + fputs(linebuf, destfp); + spinner(size, 0); + } + fprintf(destfp, "MIME-Version: 1.0\r"); + fprintf(destfp, "Content-Type: multipart/mixed; boundary=a2forever\r\r"); + fprintf(destfp, "This is a multi-part message in MIME format.\r"); + fprintf(destfp, "--a2forever\r"); + fprintf(destfp, "Content-Type: text/plain; charset=US-ASCII\r"); + fprintf(destfp, "Content-Transfer-Encoding: 7bit\r\r"); + while ((chars = get_line(fp, 0)) != -1) { + size += chars; + fputs(linebuf, destfp); + spinner(size, 0); + } + spinner(size, 1); + + printf("\rEnter the filename or filenames to attach to the email.\n"); + printf("An empty entry means you are done attaching files.\n\n"); + while (prompt_for_name("File to attach", 1)) { + fp2 = fopen(userentry, "rb"); + if (!fp2) { + error(ERR_NONFATAL, "Can't open %s", userentry); + continue; + } + fprintf(destfp, "\r--a2forever\r"); + fprintf(destfp, "Content-Type: application/octet-stream\r"); + fprintf(destfp, "Content-Transfer-Encoding: base64\r"); +// TODO: filename should be just the basename in the following line + fprintf(destfp, "Content-Disposition: attachment; filename=%s;\r\r", userentry); + printf("\r "); // Space is for spinner to eat + size = 0; + do { + i = fread(buf, 1, 72 * 3 / 4 * 5, fp2); // Multiple of 72*3/4 bytes + size += i; + if (i == 0) + break; + i = encode_base64(buf, buf + READSZ / 2, i); + i = fwrite(buf + READSZ / 2, 1, i, destfp); + spinner(size, 0); + } while (!feof(fp2)); + fclose(fp2); + spinner(size, 1); + } + fprintf(destfp, "\r--a2forever--\r"); + fclose(fp); + fclose(destfp); + if (unlink(fname)) + error(ERR_FATAL, "Can't delete %s", fname); + if (rename(filename, fname)) + error(ERR_FATAL, "Can't rename %s to %s", filename, fname); +} + +/* + * Load EMAIL.SYSTEM to $2000 and jump to it + * (This code is in language card space so it can't possibly be trashed) + */ +#pragma code-name (push, "LC") +void load_email(void) { + revers(0); + clrscr(); + exec("EMAIL.SYSTEM", NULL); // Assume it is in current directory +} +#pragma code-name (pop) + +void main(int argc, char *argv[]) { + readconfigfile(); + if (argc == 2) { + quit_to_email = 1; + attach(argv[1]); + } else + error(ERR_FATAL, "No email file specified"); + if (quit_to_email) + load_email(); +} + + diff --git a/apps/edit.c b/apps/edit.c index ccf10f3..e86e2c8 100644 --- a/apps/edit.c +++ b/apps/edit.c @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -40,9 +41,6 @@ #define CLREOL 0x1d #define DELETE 0x7f -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; - #define BUFSZ (20 * 1024) char gapbuf[BUFSZ]; char padding = 0; // To null terminate for strstr() @@ -878,6 +876,18 @@ void load_email(void) { } #pragma code-name (pop) +/* + * Load ATTACHER.SYSTEM to $2000 and jump to it + * (This code is in language card space so it can't possibly be trashed) + */ +#pragma code-name (push, "LC") +void load_attacher(void) { + revers(0); + clrscr(); + exec("ATTACHER.SYSTEM", filename); // Assume it is in current directory +} +#pragma code-name (pop) + /* * Save file to disk, handle user interface */ @@ -1123,6 +1133,8 @@ int edit(char *fname) { if (modified) save(); if (quit_to_email) { + if (prompt_okay("Add attachments - ")) + load_attacher(); if (prompt_okay("Quit to EMAIL - ")) load_email(); } else { diff --git a/apps/email.c b/apps/email.c index a8de45a..7c1b64f 100644 --- a/apps/email.c +++ b/apps/email.c @@ -4,7 +4,6 @@ // Bobbi June, July 2020 ///////////////////////////////////////////////////////////////// -// - TODO: Feature to attach files to outgoing messages // - TODO: Get rid of all uses of malloc(). Don't need it. #include @@ -604,51 +603,6 @@ uint16_t decode_base64(char *p) { return j; } -/* - * Base64 encode table - */ -static const char b64enc[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -/* - * Encode Base64 format - * p - Pointer to source buffer - * q - Pointer to destination buffer - * len - Length of buffer to encode - * Returns length of encoded data - */ -uint16_t encode_base64(char *p, char *q, uint16_t len) { - uint16_t j = 0; - uint16_t i, ii; - for (i = 0; i < len / 3; ++i) { - ii = 3 * i; - q[j++] = b64enc[(p[ii] & 0xfc) >> 2]; - q[j++] = b64enc[((p[ii] & 0x03) << 4) | ((p[ii + 1] & 0xf0) >> 4)]; - q[j++] = b64enc[((p[ii + 1] & 0x0f) << 2) | ((p[ii + 2] & 0xc0) >> 6)]; - q[j++] = b64enc[(p[ii + 2] & 0x3f)]; - if (((i + 1) % 18) == 0) - q[j++] = '\r'; - } - ii += 3; - i = len - ii; // Bytes remaining to encode - switch (i) { - case 1: - q[j++] = b64enc[(p[ii] & 0xfc) >> 2]; - q[j++] = b64enc[(p[ii] & 0x03) << 4]; - q[j++] = '='; - q[j++] = '='; - break; - case 2: - q[j++] = b64enc[(p[ii] & 0xfc) >> 2]; - q[j++] = b64enc[((p[ii] & 0x03) << 4) | ((p[ii + 1] & 0xf0) >> 4)]; - q[j++] = b64enc[(p[ii + 1] & 0x0f) << 2]; - q[j++] = '='; - break; - } - q[j] = '\0'; - return j; -} - /* * Print line up to first '\r' or '\0' */ @@ -1386,8 +1340,7 @@ uint8_t write_email_headers(FILE *fp1, FILE *fp2, struct emailhdrs *h, prompt_for_name("cc", 0); if (strlen(userentry) > 0) fprintf(fp2, "cc: %s\r", userentry); - fprintf(fp2, "X-Mailer: %s - Apple II Forever!\r", PROGNAME); - fprintf(fp2, "MIME-Version: 1.0\r\r"); + fprintf(fp2, "X-Mailer: %s - Apple II Forever!\r\r", PROGNAME); if (mode == 'R') { truncate_header(h->date, buf, 40); fprintf(fp2, "On %s, ", buf); @@ -1617,47 +1570,6 @@ err: return 1; } -/* - * Optionally attach files to outgoing email. - * Expects the name of the email file in filename[] - */ -void attach_files(void) { - FILE *fp2; - uint16_t i; - if (prompt_okay("Attach file(s) - ")) { - fp = fopen(filename, "ab"); - if (!fp) { - error(ERR_NONFATAL, "Can't open %s", filename); - return; - } - while (prompt_for_name("File to attach", 1)) { - fp2 = fopen(userentry, "rb"); - if (!fp2) { - error(ERR_NONFATAL, "Can't open %s", userentry); - continue; - } - fprintf(fp, "--a2forever\r"); - fprintf(fp, "Content-Type: application/octet-stream\r"); - fprintf(fp, "Content-Transfer-Encoding: base64\r"); -// TODO: filename should be just the basename in the following line - fprintf(fp, "Content-Disposition: attachment; filename=%s;\r\r", userentry); - do { - i = fread(buf, 1, 72 * 3 / 4 * 5, fp2); // Multiple of 72*3/4 bytes - if (i == 0) - break; -//printf("Read %d bytes\n", i); - i = encode_base64(buf, buf + READSZ / 2, i); -//printf("Encoded to %d bytes\n", i); - i = fwrite(buf + READSZ / 2, 1, i, fp); -//printf("Wrote %d bytes\n", i); - } while (!feof(fp2)); - fclose(fp2); - } - } - fprintf(fp, "\r--a2forever--\r"); - fclose(fp); -} - /* * Create a blank outgoing message and put it in OUTBOX. * OUTBOX is not a 'proper' mailbox (no EMAIL.DB) @@ -1693,22 +1605,13 @@ void create_blank_outgoing() { prompt_for_name("cc", 0); if (strlen(userentry) > 0) fprintf(fp, "cc: %s\r", userentry); - fprintf(fp, "X-Mailer: %s - Apple II Forever!\r", PROGNAME); - fprintf(fp, "MIME-Version: 1.0\r"); - fprintf(fp, "Content-Type: multipart/mixed; boundary=a2forever\r\r"); - fprintf(fp, "This is a multi-part message in MIME format.\r"); - fprintf(fp, "--a2forever\r"); - fprintf(fp, "Content-Type: text/plain\r\r"); - fprintf(fp, "< insert email body here >\r"); + fprintf(fp, "X-Mailer: %s - Apple II Forever!\r\r", PROGNAME); fclose(fp); // Update dest/NEXT.EMAIL, incrementing count by 1 if (update_next_email("OUTBOX", num + 1)) return; - sprintf(filename, "%s/OUTBOX/EMAIL.%u", cfg_emaildir, num); - attach_files(); - sprintf(filename, "Open %s/OUTBOX/EMAIL.%u in editor - ", cfg_emaildir, num); if (prompt_okay(filename)) { sprintf(filename, "%s/OUTBOX/EMAIL.%u", cfg_emaildir, num); diff --git a/apps/email.cfg b/apps/email.cfg index 1f389c8..049e996 100644 --- a/apps/email.cfg +++ b/apps/email.cfg @@ -5,6 +5,6 @@ NODELETE 192.168.10.2 apple2.local /IP65 -/H1/EMAIL +/DATA/EMAIL bobbi.8bit@gmail.com diff --git a/apps/email_common.h b/apps/email_common.h index d2a4d8b..1cc5db0 100644 --- a/apps/email_common.h +++ b/apps/email_common.h @@ -4,7 +4,7 @@ // Bobbi June 2020 ///////////////////////////////////////////////////////////////// -#define PROGNAME "emai//er v0.85" +#define PROGNAME "emai//er v0.90" // Configuration params from POP65.CFG char cfg_server[40]; // IP of POP3 server diff --git a/apps/pop65.c b/apps/pop65.c index 7cb4aec..97f8103 100644 --- a/apps/pop65.c +++ b/apps/pop65.c @@ -456,8 +456,7 @@ void update_inbox(uint16_t nummsgs) { copyheader(hdrs.subject, linebuf + 9, 79); hdrs.subject[79] = '\0'; } - //if (linebuf[0] == '\r') { - if (strlen(linebuf) < 10) { + if (linebuf[0] == '\r') { headers = 0; hdrs.skipbytes = headerchars; } diff --git a/apps/rebuild.c b/apps/rebuild.c index 75e9a23..396e44b 100644 --- a/apps/rebuild.c +++ b/apps/rebuild.c @@ -10,15 +10,11 @@ #include #include #include +#include #include #include #include #include - -typedef unsigned long uint32_t; -typedef unsigned int uint16_t; -typedef unsigned char uint8_t; -typedef int int16_t; #include "email_common.h" #define NETBUFSZ 1500+4 // 4 extra bytes for overlap between packets