/* I'm assuming I can read the cookie file, process it, * and if necessary, write it out again, with the expired cookies deleted, * all before another edbrowse process interferes. * I've given it some thought, and I think I can ignore the race conditions. */ void cookiesFromJar(void) { char *cbuf, *s, *t; FILE *f; int n, cnt, expired, displaced; char *cbuf_end; time_t now; struct cookie *c; if (!cookieFile) return; if (!fileIntoMemory(cookieFile, &cbuf, &n)) showErrorAbort(); cbuf[n] = 0; cbuf_end = cbuf + n; time(&now); cnt = expired = displaced = 0; s = cbuf; while (s < cbuf_end) { t = s + strcspn(s, "\r\n"); /* t points to the first newline past s. If there is no newline in s, * then it points to the NUL byte at end of s. */ *t = '\0'; c = cookie_from_netscape_line(s); if (c) { /* Got a valid cookie line. */ cnt++; if (c->expires < now) { freeCookie(c); nzFree(c); ++expired; } else { acceptCookie(c); displaced += displacedCookie; } } s = t + 1; /* Get ready to read more lines. */ /* Skip over blank lines, if necessary. */ while (s < cbuf_end && (*s == '\r' || *s == '\n')) s++; } debugPrint(3, "%d persistent cookies, %d expired, %d displaced", cnt, expired, displaced); nzFree(cbuf); if (!(expired + displaced)) return; /* Pour the cookies back into the jar */ f = fopen(cookieFile, "w"); if (!f) i_printfExit(MSG_NoRebCookie, cookieFile); foreach(c, cookies) { char *cookLine = netscapeCookieLine(c); fputs(cookLine, f); nzFree(cookLine); }
static void freeCookie(struct cookie *c) { nzFree(c->name); nzFree(c->value); nzFree(c->server); nzFree(c->path); nzFree(c->domain); } /* freeCookie */
static void freeMailInfo(struct MHINFO *w) { struct MHINFO *v; while (!listIsEmpty(&w->components)) { v = w->components.next; delFromList(v); freeMailInfo(v); } nzFree(w->tolist); nzFree(w->cclist); nzFree(w); } /* freeMailInfo */
/* the entry point */ void html2nodes(const char *htmltext, bool startpage) { char *htmlfix = 0; tdoc = tidyCreate(); if (!startpage) tidyOptSetInt(tdoc, TidyBodyOnly, yes); tidySetReportFilter(tdoc, tidyErrorHandler); // tidySetReportFilter(tdoc, tidyReportFilter); tidySetCharEncoding(tdoc, (cons_utf8 ? "utf8" : "latin1")); htmlfix = tidyPreprocess(htmltext); if (htmlfix) { tidyParseString(tdoc, htmlfix); nzFree(htmlfix); } else tidyParseString(tdoc, htmltext); tidyCleanAndRepair(tdoc); if (debugLevel >= 5) { traverse_tidycall = printNode; traverseTidy(); } /* convert tidy nodes into edbrowse nodes */ traverse_tidycall = convertNode; traverseTidy(); tidyRelease(tdoc); } /* html2nodes */
/* run a function with no args that returns an object */ jsobjtype run_function_object(jsobjtype obj, const char *name) { run_function(obj, name, 0, NULL); if (!propval) return NULL; if (head.proptype == EJ_PROP_OBJECT || head.proptype == EJ_PROP_ARRAY) { jsobjtype p; sscanf(propval, "%p", &p); nzFree(propval); propval = 0; return p; } /* wrong type, just return NULL */ nzFree(propval); propval = 0; return NULL; } /* run_function_object */
/* Tell libcurl about a new cookie. Called when setting cookies from * JavaScript. * The function is pretty simple. Construct a line of the form used by * the Netscape cookie file format, and pass that to libcurl. */ static CURLcode cookieForLibcurl(const struct cookie *c) { CURLcode ret; char *cookLine = netscapeCookieLine(c); debugPrint(3, "cookie for libcurl"); ret = curl_easy_setopt(http_curl_handle, CURLOPT_COOKIELIST, cookLine); nzFree(cookLine); return ret; } /* cookieForLibcurl */
char *prependString(char *s, const char *p) { int slen = strlen(s); int plen = strlen(p); char *t = allocString(slen + plen + 1); strcpy(t, p); strcpy(t + plen, s); nzFree(s); return t; } /* prependstring */
/* run a stored procedure with no % formatting */ static bool sql_procGo(const char *stmt) { bool rowfound; char *s = allocMem(20 + strlen(stmt)); strcpy(s, "execute procedure "); strcat(s, stmt); rowfound = execInternal(s, 3); /* if execInternal doesn't return, we have a memory leak */ nzFree(s); return rowfound; } /* sql_procGo */
char *tidyPreprocess(const char *h) { char *ns; /* the new string */ int l; char *inside, *expanded; const char *lg, *s = strstrCI(h, "<textarea"); /* most web pages don't have textareas */ if (!s) return NULL; ns = initString(&l); stringAndBytes(&ns, &l, h, s - h); h = s; while (true) { /* next textarea */ s = strstrCI(h, "<textarea"); if (!s) break; s = strchr(s, '>'); if (!s) break; ++s; stringAndBytes(&ns, &l, h, s - h); h = s; s = strstrCI(h, "</textarea"); if (!s) break; lg = strpbrk(h, "<>"); /* lg is at most s */ if (lg == s) continue; inside = pullString1(h, s); expanded = htmlEscapeTextarea(inside); stringAndString(&ns, &l, expanded); nzFree(inside); nzFree(expanded); h = s; } stringAndString(&ns, &l, h); return ns; } /* tidyPreprocess */
/* run a function with no args that returns a boolean */ bool run_function_bool(jsobjtype obj, const char *name) { run_function(obj, name, 0, NULL); if (!propval) return true; if (head.proptype == EJ_PROP_BOOL) { bool rc = (propval[0] == '1'); nzFree(propval); propval = 0; return rc; } if (head.proptype == EJ_PROP_INT) { int n = atoi(propval); nzFree(propval); propval = 0; return (n != 0); } /* wrong type, but at least it's something, just return true */ nzFree(propval); propval = 0; return true; } /* run_function_bool */
jsobjtype instantiate_array_element(jsobjtype array, int idx, const char *classname) { jsobjtype p = 0; if (whichproc == 'j') return instantiate_array_element_nat(array, idx, classname); set_array_element(array, idx, classname, EJ_PROP_INSTANCE); if (!propval) return p; sscanf(propval, "%p", &p); nzFree(propval); propval = 0; return p; } /* instantiate_array_element */
bool showTables(void) { char tabname[40]; char tabtype[40]; char tabowner[40]; SQLLEN tabnameOut, tabtypeOut, tabownerOut; char *buf; int buflen, cx; int truevalue = SQL_TRUE; /* SQLSetConnectAttr(hdbc, SQL_ATTR_METADATA_ID, &truevalue, SQL_IS_INTEGER); */ newStatement(); stmt_text = "get tables"; debugStatement(); rc = SQLTables(hstmt, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS, NULL, SQL_NTS); if(rc) goto abort; SQLBindCol(hstmt, 2, SQL_CHAR, (SQLPOINTER) tabowner, sizeof (tabowner), &tabownerOut); SQLBindCol(hstmt, 3, SQL_CHAR, (SQLPOINTER) tabname, sizeof (tabname), &tabnameOut); SQLBindCol(hstmt, 4, SQL_CHAR, (SQLPOINTER) tabtype, sizeof (tabtype), &tabtypeOut); buf = initString(&buflen); while(SQLFetch(hstmt) == SQL_SUCCESS) { char tabline[140]; sprintf(tabline, "%s.%s|%s\n", tabowner, tabname, tabtype); stringAndString(&buf, &buflen, tabline); } cx = sideBuffer(0, buf, buflen, 0, false); nzFree(buf); i_printf(MSG_ShowTables, cx); SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return true; abort: SQLFreeHandle(SQL_HANDLE_STMT, hstmt); return false; } /* showTables */
static void acceptCookie(struct cookie *c) { struct cookie *d; displacedCookie = false; foreach(d, cookies) { if (stringEqualCI(d->name, c->name) && stringEqualCI(d->domain, c->domain)) { displacedCookie = true; delFromList(d); freeCookie(d); nzFree(d); break; } } addToListBack(&cookies, c); } /* acceptCookie */
/* Javascript has changed an input field */ static void javaSetsTagVar(jsobjtype v, const char *newtext) { struct inputChange *ic; struct htmlTag *t = tagFromJavaVar(v); if (!t) return; if (t->itype == INP_HIDDEN || t->itype == INP_RADIO || t->itype == INP_FILE) return; if (t->itype == INP_TA) { debugPrint(3, "textarea.value is being updated"); return; } nzFree(t->value); t->value = cloneString(newtext); } /* javaSetsTagVar */
void scanMail(void) { int nmsgs, m; if (!isInteractive) i_printfExit(MSG_FetchNotBackgnd); if (!mailDir) i_printfExit(MSG_NoMailDir); if (chdir(mailDir)) i_printfExit(MSG_NoDirChange, mailDir); if (!umf) { umf = allocMem(strlen(mailUnread) + 12); sprintf(umf, "%s/", mailUnread); umf_end = umf + strlen(umf); } /* How many mail messages? */ unreadBase = 0; unreadStats(); nmsgs = unreadCount; if (!nmsgs) { i_puts(MSG_NoMail); exit(0); } i_printf(MSG_MessagesX, nmsgs); loadAddressBook(); for (m = 1; m <= nmsgs; ++m) { nzFree(lastMailText); lastMailText = 0; /* Now grab the entire message */ unreadStats(); sprintf(umf_end, "%d", unreadMin); if (!fileIntoMemory(umf, &mailstring, &mailstring_l)) showErrorAbort(); unreadBase = unreadMin; if (presentMail() == 1) unlink(umf); } /* loop over mail messages */ exit(0); } /* scanMail */
/* Pass the return value of the script back as a string. */ char *jsRunScriptResult(jsobjtype obj, const char *str, const char *filename, int lineno) { int rc; char *s; if (!allowJS || !cw->winobj || !obj) return 0; if (!str || !str[0]) return 0; debugPrint(5, "> script:"); debugPrint(6, "%s", str); head.cmd = EJ_CMD_SCRIPT; head.obj = obj; /* this, in js */ head.proplength = strlen(str); head.lineno = lineno; if (writeHeader()) return 0; /* and send the script to execute */ if (writeToJS(str, head.proplength)) return 0; jsSourceFile = filename; rc = readMessage(); jsSourceFile = 0; if (rc) return 0; ack5(); s = propval; propval = 0; if (head.n) { /* a real result */ if (!s) s = emptyString; } else { nzFree(s); s = 0; } return s; } /* jsRunScriptResult */
/* call javascript function with arguments, but all args must be objects */ static int run_function(jsobjtype obj, const char *name, int argc, const jsobjtype * argv) { int rc; propval = 0; /* should already be 0 */ if (!allowJS || !cw->winobj || !obj) return -1; debugPrint(5, "> call %s(%d)", name, argc); if (argc) { int i, l; char oval[20]; propval = initString(&l); for (i = 0; i < argc; ++i) { sprintf(oval, "%p|", argv[i]); stringAndString(&propval, &l, oval); } } head.cmd = EJ_CMD_CALL; head.n = strlen(name); head.obj = obj; head.proplength = 0; if (propval) head.proplength = strlen(propval); if (writeHeader()) return -1; if (writeToJS(name, head.n)) return -1; if (propval) { rc = writeToJS(propval, head.proplength); nzFree(propval); propval = 0; if (rc) return -1; } if (readMessage()) return -1; ack5(); return 0; } /* run_function */
/* Return 0 if there was no need to encode */ static char *isoEncode(char *start, char *end) { int nacount = 0, count = 0, len; char *s, *t; char c, code; for (s = start; s < end; ++s) { c = *s; if (c == 0) *s = ' '; if (isspaceByte(c)) *s = ' '; } for (s = start; s < end; ++s) { c = *s; ++count; if (!isprintByte(c) && c != ' ') ++nacount; } if (!nacount) return 0; if (nacount * 4 >= count && count > 8) { code = 'B'; s = base64Encode(start, end - start, false); goto package; } code = 'Q'; s = qpEncode(start); package: len = strlen(s); t = allocMem(len + 20); sprintf(t, "=?ISO-8859-1?%c?%s?=", code, s); nzFree(s); return t; } /* isoEncode */
/* If debug is at least 5, show a simple acknowledgement or error * from the js process. */ static void ack5(void) { char *a; int a_l; char buf[32]; if (debugLevel < 5) return; a = initString(&a_l); stringAndChar(&a, &a_l, '<'); if (head.highstat) { sprintf(buf, " error %d|%d", head.highstat, head.lowstat); stringAndString(&a, &a_l, buf); } stringAndChar(&a, &a_l, ' '); if (propval) stringAndString(&a, &a_l, debugString(propval)); else if (head.cmd == EJ_CMD_HASPROP) stringAndNum(&a, &a_l, head.proptype); else stringAndString(&a, &a_l, "ok"); debugPrint(5, "%s", a); nzFree(a); } /* ack5 */
void run_function_objargs(jsobjtype obj, const char *name, int nargs, ...) { /* lazy, limit of 20 args */ jsobjtype argv[20]; int i; va_list p; if (nargs > 20) { puts("more than 20 args to a javascript function"); return; } va_start(p, nargs); for (i = 0; i < nargs; ++i) argv[i] = va_arg(p, jsobjtype); va_end(p); run_function(obj, name, nargs, argv); /* return is thrown away; this is a void function */ nzFree(propval); propval = 0; } /* run_function_objargs */
/* set document.cookie to the cookies relevant to this url */ static void docCookie(jsobjtype d) { int cook_l; char *cook = initString(&cook_l); const char *url = cw->fileName; bool secure = false; const char *proto; char *s; if (url) { proto = getProtURL(url); if (proto && stringEqualCI(proto, "https")) secure = true; sendCookies(&cook, &cook_l, url, secure); if (memEqualCI(cook, "cookie: ", 8)) { /* should often happen */ strmove(cook, cook + 8); } if (s = strstr(cook, "\r\n")) *s = 0; } set_property_string(d, "cookie", cook); nzFree(cook); } /* docCookie */
/* like the above but throw away the result */ void jsRunScript(jsobjtype obj, const char *str, const char *filename, int lineno) { char *s = jsRunScriptResult(obj, str, filename, lineno); nzFree(s); } /* jsRunScript */
/* some compilers care whether it's void * or const void * */ void cnzFree(const void *v) { nzFree((void *)v); } /* cnzFree */
/* Returns number of messages fetched */ int fetchMail(int account) { const struct MACCOUNT *a = accounts + account - 1; const char *login = a->login; const char *pass = a->password; int nfetch = 0; /* number of messages actually fetched */ CURLcode res_curl = CURLE_OK; char *mailbox_url = get_mailbox_url(a); const char *url_for_error = mailbox_url; char *message_url = NULL; int message_count = 0, message_number; if (!mailDir) i_printfExit(MSG_NoMailDir); if (chdir(mailDir)) i_printfExit(MSG_NoDirChange, mailDir); if (!umf) { umf = allocMem(strlen(mailUnread) + 12); sprintf(umf, "%s/", mailUnread); umf_end = umf + strlen(umf); } unreadBase = 0; unreadStats(); mailstring = initString(&mailstring_l); CURL *mail_handle = newFetchmailHandle(mailbox_url, login, pass); res_curl = count_messages(mail_handle, mailbox_url, &message_count); if (res_curl != CURLE_OK) goto fetchmail_cleanup; for (message_number = 1; message_number <= message_count; message_number++) { if (asprintf(&message_url, "%s%u", mailbox_url, message_number) == -1) { /* Again, the byte count in the error message is a bit of a fib. */ i_printfExit(MSG_MemAllocError, strlen(mailbox_url) + 11); } nzFree(mailstring); mailstring = initString(&mailstring_l); res_curl = fetchOneMessage(mail_handle, message_url, message_number); if (res_curl != CURLE_OK) goto fetchmail_cleanup; nfetch++; res_curl = deleteOneMessage(mail_handle, message_url); if (res_curl != CURLE_OK) goto fetchmail_cleanup; nzFree(message_url); message_url = NULL; } fetchmail_cleanup: if (message_url) url_for_error = message_url; if (res_curl != CURLE_OK) { ebcurl_setError(res_curl, url_for_error); showError(); } curl_easy_cleanup(mail_handle); nzFree(message_url); nzFree(mailbox_url); nzFree(mailstring); mailstring = initString(&mailstring_l); return nfetch; } /* fetchMail */
/* Let's jump right into it - parse a cookie, as received from a website. */ bool receiveCookie(const char *url, const char *str) { struct cookie *c; const char *p, *q, *server; char *date, *s; debugPrint(3, "cookie %s", str); server = getHostURL(url); if (server == 0 || !*server) return false; /* Cookie starts with name=value. If we can't get that, go home. */ for (p = str; *p != ';' && *p; p++) ; for (q = str; *q != '='; q++) if (!*q || q >= p) return false; if (str == q) return false; c = allocZeroMem(sizeof(struct cookie)); c->tail = false; c->name = pullString1(str, q); ++q; if (p - q > 0) c->value = pullString1(q, p); else c->value = emptyString; c->server = cloneString(server); if (date = extractHeaderParam(str, "expires")) { c->expires = parseHeaderDate(date); nzFree(date); } else if (date = extractHeaderParam(str, "max-age")) { int n = stringIsNum(date); if (n >= 0) { time_t now = time(0); c->expires = now + n; } nzFree(date); } c->path = extractHeaderParam(str, "path"); if (!c->path) { /* The url indicates the path for this cookie, if a path is not explicitly given */ const char *dir, *dirend; getDirURL(url, &dir, &dirend); c->path = pullString1(dir, dirend); } else { if (!c->path[0] || c->path[strlen(c->path) - 1] != '/') c->path = appendString(c->path, "/"); if (c->path[0] != '/') c->path = prependString(c->path, "/"); } if (!(c->domain = extractHeaderParam(str, "domain"))) { c->domain = cloneString(server); } else { /* Is this safe for tail-matching? */ const char *domtemp = c->domain; if (domtemp[0] == '.') domtemp++; if (!domainSecurityCheck(server, domtemp)) { nzFree(c->domain); c->domain = cloneString(server); } else { /* It's safe to do tail-matching with this domain. */ c->tail = true; /* Guarantee that it does in fact start with dot, prepending if necessary.. */ if (c->domain[0] != '.') c->domain = prependString(c->domain, "."); } } if (s = extractHeaderParam(str, "secure")) { c->secure = true; nzFree(s); } cookieForLibcurl(c); freeCookie(c); nzFree(c); return true; } /* receiveCookie */
static bool sendMailSMTP(const struct MACCOUNT *account, const char *reply, const char **recipients, const char *message) { CURLcode res = CURLE_OK; bool smtp_success = false; char *smtp_url = buildSMTPURL(account); struct curl_slist *recipient_slist = buildRecipientSList(recipients); struct smtp_upload upload = { .data = message,.length = strlen(message),.pos = 0 }; CURL *handle = newSendmailHandle(account, smtp_url, reply, recipient_slist); if (!handle) goto smtp_cleanup; curl_easy_setopt(handle, CURLOPT_READFUNCTION, smtp_upload_callback); curl_easy_setopt(handle, CURLOPT_READDATA, &upload); curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L); res = curl_easy_perform(handle); if (res == CURLE_OK) smtp_success = true; smtp_cleanup: if (res != CURLE_OK) ebcurl_setError(res, smtp_url); if (handle) curl_easy_cleanup(handle); curl_slist_free_all(recipient_slist); nzFree(smtp_url); return smtp_success; } /* sendMailSMTP */ /* Send mail to the smtp server. */ bool sendMail(int account, const char **recipients, const char *body, int subjat, const char **attachments, const char *refline, int nalt, bool dosig) { char *from, *fromiso, *reply, *login, *smlogin, *pass; const struct MACCOUNT *a, *ao, *localMail; const char *s, *boundary; char reccc[MAXRECAT]; char *t; int nat, cx, i, j; char *out = 0; bool sendmail_success = false; bool mustmime = false; bool firstgreet = true; bool firstrec; const char *ct, *ce; char *encoded = 0; if (!validAccount(account)) return false; mailAccount = account; localMail = accounts + localAccount - 1; a = accounts + account - 1; from = a->from; reply = a->reply; ao = a->outssl ? a : localMail; doSignature = dosig; nat = 0; /* number of attachments */ if (attachments) { while (attachments[nat]) ++nat; } if (nat) mustmime = true; if (nalt && nalt < nat) { setError(MSG_AttAlternate); return false; } if (!loadAddressBook()) return false; /* set copy flags */ for (j = 0; s = recipients[j]; ++j) { char cc = 0; if (*s == '^' || *s == '?') cc = *s++; if (j == MAXRECAT) { setError(MSG_RecipMany, MAXRECAT); return false; } recipients[j] = s; reccc[j] = cc; } /* Look up aliases in the address book */ for (j = 0; s = recipients[j]; ++j) { if (strchr(s, '@')) continue; t = 0; for (i = 0; i < nads; ++i) { const char *a = addressList[i].name; if (*a == '-' || *a == '!') ++a; if (!stringEqual(s, a)) continue; t = addressList[i].email; debugPrint(3, " %s becomes %s", s, t); break; } if (t) { recipients[j] = t; continue; } if (!addressFile) { setError(MSG_ABMissing); return false; } setError(MSG_ABNoAlias2, s); return false; } /* recipients */ if (!j) { setError(MSG_RecipNone); return false; } /* verify attachments are readable */ for (j = 0; s = attachments[j]; ++j) { if (!ismc && (cx = stringIsNum(s)) >= 0) { if (!cxCompare(cx) || !cxActive(cx)) return false; if (!sessionList[cx].lw->dol) { setError(MSG_AttSessionEmpty, cx); return false; } } else { char ftype = fileTypeByName(s, false); if (!ftype) { setError(MSG_AttAccess, s); return false; } if (ftype != 'f') { setError(MSG_AttRegular, s); return false; } if (!fileSizeByName(s)) { setError(MSG_AttEmpty2, s); return false; } } } /* loop over attachments */ if (!encodeAttachment(body, subjat, false, &ct, &ce, &encoded)) return false; if (ce[0] == 'q') mustmime = true; boundary = makeBoundary(); /* Build the outgoing mail, as one string. */ out = initString(&j); firstrec = true; for (i = 0; s = recipients[i]; ++i) { if (reccc[i]) continue; stringAndString(&out, &j, firstrec ? "To:" : ",\r\n "); stringAndString(&out, &j, s); firstrec = false; } if (!firstrec) stringAndString(&out, &j, eol); firstrec = true; for (i = 0; s = recipients[i]; ++i) { if (reccc[i] != '^') continue; stringAndString(&out, &j, firstrec ? "CC:" : ",\r\n "); stringAndString(&out, &j, s); firstrec = false; } if (!firstrec) stringAndString(&out, &j, eol); firstrec = true; for (i = 0; s = recipients[i]; ++i) { if (reccc[i] != '?') continue; stringAndString(&out, &j, firstrec ? "BCC:" : ",\r\n "); stringAndString(&out, &j, s); firstrec = false; } if (!firstrec) stringAndString(&out, &j, eol); fromiso = isoEncode(from, from + strlen(from)); if (!fromiso) fromiso = from; sprintf(serverLine, "From: %s <%s>%s", fromiso, reply, eol); stringAndString(&out, &j, serverLine); sprintf(serverLine, "Reply-to: %s <%s>%s", fromiso, reply, eol); stringAndString(&out, &j, serverLine); if (fromiso != from) nzFree(fromiso); if (refline) { s = strchr(refline, '\n'); if (!s) /* should never happen */ s = refline + strlen(refline); stringAndBytes(&out, &j, refline, s - refline); stringAndString(&out, &j, eol); } sprintf(serverLine, "User-Agent: %s%s", currentAgent, eol); stringAndString(&out, &j, serverLine); if (subjectLine[0]) { sprintf(serverLine, "Subject: %s%s", subjectLine, eol); stringAndString(&out, &j, serverLine); } sprintf(serverLine, "Date: %s%sMessage-ID: <%s.%s>%sMime-Version: 1.0%s", mailTimeString(), eol, messageTimeID(), reply, eol, eol); stringAndString(&out, &j, serverLine); if (!mustmime) { /* no mime components required, we can just send the mail. */ sprintf(serverLine, "Content-Type: %s%s%sContent-Transfer-Encoding: %s%s%s", ct, charsetString(ct, ce), eol, ce, eol, eol); stringAndString(&out, &j, serverLine); } else { sprintf(serverLine, "Content-Type: multipart/%s; boundary=%s%sContent-Transfer-Encoding: 7bit%s%s", nalt ? "alternative" : "mixed", boundary, eol, eol, eol); stringAndString(&out, &j, serverLine); stringAndString(&out, &j, "This message is in MIME format. Since your mail reader does not understand\r\n\ this format, some or all of this message may not be legible.\r\n\r\n--"); stringAndString(&out, &j, boundary); sprintf(serverLine, "%sContent-Type: %s%s%sContent-Transfer-Encoding: %s%s%s", eol, ct, charsetString(ct, ce), eol, ce, eol, eol); stringAndString(&out, &j, serverLine); } /* Now send the body, line by line. */ appendAttachment(encoded, &out, &j); nzFree(encoded); encoded = 0; if (mustmime) { for (i = 0; s = attachments[i]; ++i) { if (!encodeAttachment (s, 0, false, &ct, &ce, &encoded)) return false; sprintf(serverLine, "%s--%s%sContent-Type: %s%s", eol, boundary, eol, ct, charsetString(ct, ce)); stringAndString(&out, &j, serverLine); /* If the filename has a quote in it, forget it. */ /* Also, suppress filename if this is an alternate presentation. */ if (!nalt && !strchr(s, '"') && (ismc || stringIsNum(s) < 0)) { sprintf(serverLine, "; name=\"%s\"", s); stringAndString(&out, &j, serverLine); } sprintf(serverLine, "%sContent-Transfer-Encoding: %s%s%s", eol, ce, eol, eol); stringAndString(&out, &j, serverLine); appendAttachment(encoded, &out, &j); nzFree(encoded); encoded = 0; } /* loop over attachments */ /* The last boundary */ sprintf(serverLine, "%s--%s--%s", eol, boundary, eol); stringAndString(&out, &j, serverLine); } /* mime format */ sendmail_success = sendMailSMTP(ao, reply, recipients, out); nzFree(out); return sendmail_success; } /* sendMail */
/* read and/or refresh the address book */ bool loadAddressBook(void) { char *buf, *bufend, *v, *last, *s, *t; bool cmt = false; char state = 0, c; int j, buflen, ln = 1; time_t mtime; if (!addressFile || (mtime = fileTimeByName(addressFile)) == -1 || mtime <= adbooktime) return true; debugPrint(3, "loading address book"); nzFree(addressList); addressList = 0; nads = 0; if (!fileIntoMemory(addressFile, &buf, &buflen)) return false; bufend = buf + buflen; for (s = t = last = buf; s < bufend; ++s) { c = *s; if (cmt) { if (c != '\n') continue; cmt = false; } if (c == ':') { /* delimiter */ if (state == 0) { setError(MSG_ABNoAlias, ln); freefail: nzFree(buf); nads = 0; return false; } while (t[-1] == ' ' || t[-1] == '\t') --t; if (state == 1) { *t++ = c; state = 2; continue; } c = '#'; /* extra fields are ignored */ } /* : */ if (c == '#') { cmt = true; continue; } if (c == '\n') { ++ln; if (state == 0) continue; if (state == 1) { setError(MSG_ABNoColon, ln - 1); goto freefail; } if (state == 3) { ++nads; while (isspaceByte(t[-1])) --t; *t = 0; v = strchr(last, ':'); if (v - last >= 16) { setError(MSG_ABAliasLong, ln - 1); goto freefail; } ++v; if (t - v >= 64) { setError(MSG_ABMailLong, ln - 1); goto freefail; } if (!strchr(v, '@')) { setError(MSG_ABNoAt, ln - 1); goto freefail; } if (strpbrk(v, " \t")) { setError(MSG_ABMailSpaces, ln - 1); goto freefail; } while (last < t) { if (!isprintByte(*last)) { setError(MSG_AbMailUnprintable, ln - 1); goto freefail; } ++last; } *t++ = c; } else t = last; /* back it up */ last = t; state = 0; continue; } /* nl */ if ((c == ' ' || c == '\t') && (state == 0 || state == 2)) continue; if (state == 0) state = 1; if (state == 2) state = 3; *t++ = c; } *t = 0; if (state) { setError(MSG_ABUnterminated); goto freefail; } if (nads) { addressList = allocMem(nads * sizeof(struct ALIAS)); j = 0; for (s = buf; *s; s = t + 1, ++j) { t = strchr(s, ':'); memcpy(addressList[j].name, s, t - s); addressList[j].name[t - s] = 0; s = t + 1; t = strchr(s, '\n'); memcpy(addressList[j].email, s, t - s); addressList[j].email[t - s] = 0; } } /* aliases are present */ nzFree(buf); adbooktime = mtime; return true; } /* loadAddressBook */
/* Read a file into memory, mime encode it, * and return the type of encoding and the encoded data. * Last three parameters are result parameters. * If ismail is nonzero, the file is the mail, not an attachment. * In fact ismail indicates the line that holds the subject. * If ismail is negative, then -ismail indicates the subject line, * and the string file is not the filename, but rather, the mail to send. */ bool encodeAttachment(const char *file, int ismail, bool webform, const char **type_p, const char **enc_p, char **data_p) { char *buf; char c; bool longline; char *s, *t, *v; char *ct, *ce; /* content type, content encoding */ int buflen, i, cx; int nacount, nullcount, nlcount; if (ismail < 0) { buf = cloneString(file); buflen = strlen(buf); ismail = -ismail; file = EMPTYSTRING; } else { if (!ismc && (cx = stringIsNum(file)) >= 0) { static char newfilename[16]; if (!unfoldBuffer(cx, false, &buf, &buflen)) return false; if (!buflen) { if (webform) { empty: buf = EMPTYSTRING; ct = "text/plain"; ce = "7bit"; goto success; } setError(MSG_BufferXEmpty, cx); goto freefail; } sprintf(newfilename, "<buffer %d>", cx); file = newfilename; if (sessionList[cx].lw->fileName) file = sessionList[cx].lw->fileName; } else { if (!fileIntoMemory(file, &buf, &buflen)) return false; if (!buflen) { if (webform) goto empty; setError(MSG_FileXEmpty, file); goto freefail; } } } /* ismail negative or normal */ if (ismail) { /* Put newline at the end. Yes, the buffer is allocated * with space for newline and null. */ if (buf[buflen - 1] != '\n') buf[buflen++] = '\n'; /* check for subject: line */ s = buf; i = ismail; while (--i) { while (*s != '\n') ++s; ++s; } while (*s == ' ' || *s == '\t') ++s; if (!memEqualCI(s, "subject:", 8)) { setError(MSG_SubjectStart); goto freefail; } s += 8; while (*s == ' ' || *s == '\t') ++s; t = s; while (*s != '\n') ++s; v = s; while (s > t && isspaceByte(s[-1])) --s; if (s - t >= sizeof(subjectLine)) { setError(MSG_SubjectLong, sizeof(subjectLine) - 1); goto freefail; } if (s > t) memcpy(subjectLine, t, s - t); subjectLine[s - t] = 0; if (subjectLine[0]) { char *subjiso = isoEncode(subjectLine, subjectLine + strlen(subjectLine)); if (subjiso) { if (strlen(subjiso) >= sizeof(subjectLine)) { nzFree(subjiso); setError(MSG_SubjectLong, sizeof(subjectLine) - 1); goto freefail; } strcpy(subjectLine, subjiso); nzFree(subjiso); } } debugPrint(6, "subject = %s", subjectLine); /* Blank lines after subject are optional, and ignored. */ for (t = buf + buflen; v < t; ++v) if (*v != '\r' && *v != '\n') break; buflen -= (v - buf); if (buflen) memmove(buf, v, buflen); buf[buflen] = 0; if (doSignature) { /* Append .signature file. */ /* Try account specific .signature file, then fall back to .signature */ sprintf(sigFileEnd, "%d", mailAccount); c = fileTypeByName(sigFile, false); if (!c) { *sigFileEnd = 0; c = fileTypeByName(sigFile, false); } if (c != 0) { int fd, n; if (c != 'f') { setError(MSG_SigRegular); goto freefail; } n = fileSizeByName(sigFile); if (n > 0) { buf = reallocMem(buf, buflen + n + 1); fd = open(sigFile, O_RDONLY); if (fd < 0) { setError(MSG_SigAccess); goto freefail; } read(fd, buf + buflen, n); close(fd); buflen += n; buf[buflen] = 0; } } } /* .signature */ } /* Infer content type from the filename */ ct = 0; s = strrchr(file, '.'); if (s && s[1]) { ++s; if (stringEqualCI(s, "ps")) ct = "application/PostScript"; if (stringEqualCI(s, "jpeg")) ct = "image/jpeg"; if (stringEqualCI(s, "gif")) ct = "image/gif"; if (stringEqualCI(s, "wav")) ct = "audio/basic"; if (stringEqualCI(s, "mpeg")) ct = "video/mpeg"; if (stringEqualCI(s, "rtf")) ct = "text/richtext"; if (stringEqualCI(s, "htm") || stringEqualCI(s, "html") || stringEqualCI(s, "shtm") || stringEqualCI(s, "shtml") || stringEqualCI(s, "asp")) ct = "text/html"; } /* Count the nonascii characters */ nacount = nullcount = nlcount = 0; longline = false; s = 0; for (i = 0; i < buflen; ++i) { c = buf[i]; if (c == '\0') ++nullcount; if (c < 0) ++nacount; if (c != '\n') continue; ++nlcount; t = buf + i; if (s && t - s > 120) longline = true; if (!s && i > 120) longline = true; s = t; } t = buf + i; if (s && t - s > 120) longline = true; if (!s && i > 120) longline = true; debugPrint(6, "attaching %s length %d nonascii %d nulls %d longline %d", file, buflen, nacount, nullcount, longline); nacount += nullcount; /* Set the type of attachment */ if (buflen > 20 && nacount * 5 > buflen) { if (!ct) ct = "application/octet-stream"; /* default type for binary */ } if (!ct) ct = "text/plain"; /* Criteria for base64 encode. * files uploaded from a web form need not be encoded, unless they contain * nulls, which is a quirk of my slapped together software. */ if (!webform && (buflen > 20 && nacount * 5 > buflen) || webform && nullcount) { if (ismail) { setError(MSG_MailBinary, file); goto freefail; } s = base64Encode(buf, buflen, true); nzFree(buf); buf = s; ce = "base64"; goto success; } if (!webform) { /* Switch to unix newlines - we'll switch back to dos later. */ v = buf + buflen; for (s = t = buf; s < v; ++s) { c = *s; if (c == '\r' && s < v - 1 && s[1] == '\n') continue; *t++ = c; } buflen = t - buf; /* Do we need to use quoted-printable? */ /* Perhaps this hshould read (nacount > 0) */ if (nacount * 20 > buflen || nullcount || longline) { char *newbuf; int l, colno = 0, space = 0; newbuf = initString(&l); v = buf + buflen; for (s = buf; s < v; ++s) { c = *s; /* do we have to =expand this character? */ if (c < '\n' && c != '\t' || c == '=' || c == '\xff' || (c == ' ' || c == '\t') && s < v - 1 && s[1] == '\n') { char expand[4]; sprintf(expand, "=%02X", (uchar) c); stringAndString(&newbuf, &l, expand); colno += 3; } else { stringAndChar(&newbuf, &l, c); ++colno; } if (c == '\n') { colno = space = 0; continue; } if (c == ' ' || c == '\t') space = l; if (colno < 72) continue; if (s == v - 1) continue; /* If newline's coming up anyways, don't force another one. */ if (s[1] == '\n') continue; i = l; if (!space || space == i) { stringAndString(&newbuf, &l, "=\n"); colno = space = 0; continue; } colno = i - space; stringAndString(&newbuf, &l, "**"); /* make room */ while (i > space) { newbuf[i + 1] = newbuf[i - 1]; --i; } newbuf[space] = '='; newbuf[space + 1] = '\n'; space = 0; } /* loop over characters */ nzFree(buf); buf = newbuf; ce = "quoted-printable"; goto success; } } buf[buflen] = 0; ce = (nacount ? "8bit" : "7bit"); success: debugPrint(6, "encoded %s %s length %d", ct, ce, strlen(buf)); *enc_p = ce; *type_p = ct; *data_p = buf; return true; freefail: nzFree(buf); return false; } /* encodeAttachment */
/* Return 0 for ok, 1 to delete the mail, -1 to stop. * stop is only meaningful for imap. */ static int presentMail(void) { int j, k; const char *redirect = NULL; /* send mail elsewhere */ char key = 0; const char *atname = NULL; /* name of file or attachment */ bool delflag = false; /* delete this mail */ bool scanat = false; /* scan for attachments */ int displine; int stashNumber = -1; /* clear things out from the last message */ if (lastMailInfo) freeMailInfo(lastMailInfo); lastMailInfo = 0; if (sessionList[1].lw) cxQuit(1, 2); cs = 0; cxSwitch(1, false); iuReformat(mailstring, mailstring_l, &mailu8, &mailu8_l); if (mailu8) { if (!addTextToBuffer((pst) mailu8, mailu8_l, 0, false)) showErrorAbort(); } else { if (!addTextToBuffer((pst) mailstring, mailstring_l, 0, false)) showErrorAbort(); } browseCurrentBuffer(); if (!passMail) { redirect = mailRedirect(lastMailInfo->to, lastMailInfo->from, lastMailInfo->reply, lastMailInfo->subject); } if (redirect) { if (!isimap) { delflag = true; key = 'w'; if (*redirect == '-') ++redirect, key = 'u'; if (stringEqual(redirect, "x")) i_puts(MSG_Junk); else printf("> %s\n", redirect); } else { if (*redirect == '-') ++redirect; if (stringEqual(redirect, "x")) redirect = NULL; } } /* display the next page of mail and get a command from the keyboard */ displine = 1; paging: if (!delflag) { /* show next page */ if (displine <= cw->dol) { for (j = 0; j < 20 && displine <= cw->dol; ++j, ++displine) { char *showline = (char *)fetchLine(displine, 1); k = pstLength((pst) showline); showline[--k] = 0; printf("%s\n", showline); nzFree(showline); } } } /* get key command from user */ key_command: if (delflag) goto writeMail; /* interactive prompt depends on whether there is more text or not */ printf("%c ", displine > cw->dol ? '?' : '*'); fflush(stdout); key = getLetter((isimap ? "q? nwWuUasd" : "q? nwud")); printf("\b\b\b"); fflush(stdout); switch (key) { case 'q': i_puts(MSG_Quit); exit(0); case 'n': i_puts(MSG_Next); goto afterinput; case 's': i_puts(MSG_Stop); goto afterinput; case 'd': i_puts(MSG_Delete); delflag = true; goto afterinput; case ' ': if (displine > cw->dol) i_puts(MSG_EndMessage); goto paging; case '?': i_puts(isimap ? MSG_ImapReadHelp : MSG_MailHelp); goto key_command; case 'a': key = 'w'; /* this will scan attachments */ scanat = true; case 'w': case 'W': case 'u': case 'U': break; default: i_puts(MSG_NYI); goto key_command; } /* switch */ /* At this point we're saving the mail somewhere. */ writeMail: if (!isimap || isupper(key)) delflag = true; atname = 0; if (!isimap) atname = redirect; if (scanat) goto attachOnly; saveMail: if (!atname) atname = getFileName(MSG_FileName, redirect, false, false); if (stringEqual(atname, "x")) goto afterinput; char exists = fileTypeByName(atname, false); int fsize; /* file size */ int fh = open(atname, O_WRONLY | O_TEXT | O_CREAT | O_APPEND, 0666); if (fh < 0) { i_printf(MSG_NoCreate, atname); goto saveMail; } if (exists) write(fh, "======================================================================\n", 71); if (key == 'u') { if (write(fh, mailstring, mailstring_l) < mailstring_l) { badsave: i_printf(MSG_NoWrite, atname); close(fh); goto saveMail; } close(fh); fsize = mailstring_l; } else { /* key = w, write the file - if pop then save the original unformatted */ if (!isimap && mailStash) { char *rmf; /* raw mail file */ int rmfh; /* file handle to same */ /* I want a fairly easy filename, in case I want to go look at the original. * Not a 30 character message ID that I am forced to cut&paste. * 4 or 5 digits would be nice. * So the filename looks like /home/foo/.Trash/rawmail/36921 * I pick the digits randomly. * Please don't accumulate 100,000 emails before you empty your trash. * It's good to have a cron job empty the trash early Sunday morning. */ k = strlen(mailStash); rmf = allocMem(k + 12); /* Try 20 times, then give up. */ for (j = 0; j < 20; ++j) { int rn = rand() % 100000; /* random number */ sprintf(rmf, "%s/%05d", mailStash, rn); if (fileTypeByName(rmf, false)) continue; /* dump the original mail into the file */ rmfh = open(rmf, O_WRONLY | O_TEXT | O_CREAT | O_APPEND, 0666); if (rmfh < 0) break; if (write(rmfh, mailstring, mailstring_l) < mailstring_l) { close(rmfh); unlink(rmf); break; } close(rmfh); /* written successfully, remember the stash number */ stashNumber = rn; break; } } fsize = 0; for (j = 1; j <= cw->dol; ++j) { char *showline = (char *)fetchLine(j, 1); int len = pstLength((pst) showline); if (write(fh, showline, len) < len) goto badsave; nzFree(showline); fsize += len; } /* loop over lines */ if (stashNumber >= 0) { char addstash[60]; sprintf(addstash, "\nUnformatted %05d\n", stashNumber); k = strlen(addstash); if (write(fh, addstash, k) < k) goto badsave; fsize += k; } close(fh); attachOnly: if (nattach) writeAttachments(lastMailInfo); else if (scanat) i_puts(MSG_NoAttachments); } /* unformat or format */ if (scanat) goto afterinput; /* print "mail saved" message */ i_printf(MSG_MailSaved, fsize); if (exists) i_printf(MSG_Appended); nl(); afterinput: nzFree(mailstring); mailstring = 0; nzFree(mailu8); mailu8 = 0; if (delflag) return 1; if (key == 's') return -1; return 0; } /* presentMail */
bool sendMailCurrent(int sm_account, bool dosig) { const char *reclist[MAXRECAT + 1]; char *recmem; const char *atlist[MAXRECAT + 1]; char *atmem; char *s, *t; char cxbuf[4]; int lr, la, ln; char *refline = 0; int nrec = 0, nat = 0, nalt = 0; int account = localAccount; int j; bool rc = false; bool subj = false; if (cw->browseMode) { setError(MSG_MailBrowse); return false; } if (cw->sqlMode) { setError(MSG_MailDB); return false; } if (cw->dirMode) { setError(MSG_MailDir); return false; } if (cw->binMode) { setError(MSG_MailBinary2); return false; } if (!cw->dol) { setError(MSG_MailEmpty); return false; } if (!validAccount(account)) return false; recmem = initString(&lr); atmem = initString(&la); /* Gather recipients and attachments, until we reach subject: */ for (ln = 1; ln <= cw->dol; ++ln) { char *line = (char *)fetchLine(ln, -1); if (memEqualCI(line, "to:", 3) || memEqualCI(line, "mailto:", 7) || memEqualCI(line, "cc:", 3) || memEqualCI(line, "bcc:", 4) || memEqualCI(line, "reply to:", 9) || memEqualCI(line, "reply to ", 9)) { char cc = 0; if (toupper(line[0]) == 'C') cc = '^'; if (toupper(line[0]) == 'B') cc = '?'; if (toupper(line[0]) == 'R') line += 9; else line = strchr(line, ':') + 1; while (*line == ' ' || *line == '\t') ++line; if (*line == '\n') { setError(MSG_RecipNone2, ln); goto done; } if (nrec == MAXRECAT) { setError(MSG_RecipMany, MAXRECAT); goto done; } ++nrec; for (t = line; *t != '\n'; ++t) ; if (cc) { if (!lr) { setError(MSG_MailFirstCC); goto done; } stringAndChar(&recmem, &lr, cc); } stringAndBytes(&recmem, &lr, line, t + 1 - line); continue; } if (memEqualCI(line, "attach:", 7) || memEqualCI(line, "alt:", 4)) { if (toupper(line[1]) == 'T') line += 7; else line += 4, ++nalt; while (*line == ' ' || *line == '\t') ++line; if (*line == '\n') { setError(MSG_AttLineX, ln); goto done; } if (nat == MAXRECAT) { setError(MSG_RecipMany, MAXRECAT); goto done; } ++nat; for (t = line; *t != '\n'; ++t) ; stringAndBytes(&atmem, &la, line, t + 1 - line); continue; } if (memEqualCI(line, "account:", 8)) { line += 8; while (*line == ' ' || *line == '\t') ++line; if (!isdigitByte(*line) || (account = strtol(line, &line, 10)) == 0 || account > maxAccount || *line != '\n') { setError(MSG_MailAccountBadLineX, ln); goto done; } continue; } if (memEqualCI(line, "references:", 11)) { if (!refline) refline = line; continue; } if (memEqualCI(line, "subject:", 8)) { while (*line == ' ' || *line == '\t') ++line; subj = true; } break; } /* loop over lines */ if (sm_account) account = sm_account; if (!subj) { setError(((ln > cw->dol) + MSG_MailFirstLine), ln); goto done; } if (nrec == 0) { setError(MSG_RecipNone3); goto done; } for (s = recmem, j = 0; *s; s = t + 1, ++j) { t = strchr(s, '\n'); *t = 0; reclist[j] = s; } reclist[j] = 0; for (s = atmem, j = 0; *s; s = t + 1, ++j) { t = strchr(s, '\n'); *t = 0; atlist[j] = s; } atlist[j] = 0; sprintf(cxbuf, "%d", context); rc = sendMail(account, reclist, cxbuf, ln, atlist, refline, nalt, dosig); done: nzFree(recmem); nzFree(atmem); if (!rc && intFlag) setError(MSG_Interrupted); if (rc) i_puts(MSG_OK); return rc; } /* sendMailCurrent */