/* $Id: mail.c,v 1.126 2007/09/19 09:05:40 nicm Exp $ */ /* * Copyright (c) 2006 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "fdm.h" void mail_free(struct mail *); int mail_open(struct mail *m, size_t size) { m->size = 0; m->space = IO_ROUND(size); m->body = 0; if ((m->base = shm_create(&m->shm, m->space)) == NULL) return (-1); SHM_REGISTER(&m->shm); m->off = 0; m->data = m->base + m->off; strb_create(&m->tags); ARRAY_INIT(&m->wrapped); m->wrapchar = '\0'; m->attach = NULL; m->attach_built = 0; return (0); } void mail_send(struct mail *m, struct msg *msg) { struct mail *mm = &msg->data.mail; memcpy(mm, m, sizeof *mm); ARRAY_INIT(&mm->wrapped); mm->wrapchar = '\0'; mm->attach = NULL; } int mail_receive(struct mail *m, struct msg *msg, int destroy) { struct mail *mm = &msg->data.mail; mm->idx = m->idx; mm->tags = m->tags; m->tags = NULL; mm->attach = m->attach; m->attach = NULL; mm->auxfree = m->auxfree; m->auxfree = NULL; mm->auxdata = m->auxdata; m->auxdata = NULL; if (destroy) mail_destroy(m); else mail_close(m); memcpy(m, mm, sizeof *m); if ((m->base = shm_reopen(&m->shm)) == NULL) return (-1); SHM_REGISTER(&m->shm); m->data = m->base + m->off; ARRAY_INIT(&m->wrapped); m->wrapchar = '\0'; return (0); } void mail_free(struct mail *m) { if (m->attach != NULL) attach_free(m->attach); if (m->tags != NULL) strb_destroy(&m->tags); ARRAY_FREE(&m->wrapped); m->wrapchar = '\0'; if (m->auxfree != NULL && m->auxdata != NULL) m->auxfree(m->auxdata); } void mail_close(struct mail *m) { mail_free(m); if (m->base != NULL) { SHM_DEREGISTER(&m->shm); shm_close(&m->shm); } } void mail_destroy(struct mail *m) { mail_free(m); if (m->base != NULL) { SHM_DEREGISTER(&m->shm); shm_destroy(&m->shm); } } int mail_resize(struct mail *m, size_t size) { if (SIZE_MAX - m->off < size) fatalx("size too large"); while (m->space <= (m->off + size)) { if ((m->base = shm_resize(&m->shm, 2, m->space)) == NULL) return (-1); m->space *= 2; } m->data = m->base + m->off; return (0); } /* Initialise for iterating over lines. */ void line_init(struct mail *m, char **line, size_t *len) { char *ptr; *line = m->data; ptr = memchr(m->data, '\n', m->size); if (ptr == NULL) *len = m->size; else *len = (ptr - *line) + 1; } /* Move to next line. */ void line_next(struct mail *m, char **line, size_t *len) { char *ptr; *line += *len; if (*line == m->data + m->size) { *line = NULL; return; } ptr = memchr(*line, '\n', (m->data + m->size) - *line); if (ptr == NULL) *len = (m->data + m->size) - *line; else *len = (ptr - *line) + 1; } /* Remove specified header. */ int remove_header(struct mail *m, const char *hdr) { char *ptr; size_t len; if ((ptr = find_header(m, hdr, &len, 0)) == NULL) return (-1); /* Remove the header. */ memmove(ptr, ptr + len, m->size - len - (ptr - m->data)); m->size -= len; m->body -= len; return (0); } /* Insert header, before specified header if not NULL, otherwise at end. */ int printflike3 insert_header(struct mail *m, const char *before, const char *fmt, ...) { va_list ap; char *hdr, *ptr; size_t hdrlen, len, off; u_int newlines; newlines = 1; if (before != NULL) { /* Insert before header. */ ptr = find_header(m, before, &len, 0); if (ptr == NULL) return (-1); off = ptr - m->data; } else { /* Insert at the end. */ if (m->body == 0) { /* * Creating the headers section. Insert at the start, * and add an extra newline. */ off = 0; newlines++; } else { /* * Body points just after the blank line. Insert before * the blank line. */ off = m->body - 1; } } /* Create the header. */ va_start(ap, fmt); hdrlen = xvasprintf(&hdr, fmt, ap); va_end(ap); /* Include the newlines. */ hdrlen += newlines; /* Make space for the header. */ if (mail_resize(m, m->size + hdrlen) != 0) { xfree(hdr); return (-1); } ptr = m->data + off; memmove(ptr + hdrlen, ptr, m->size - off); /* Copy the header. */ memcpy(ptr, hdr, hdrlen - newlines); memset(ptr + hdrlen - newlines, '\n', newlines); m->size += hdrlen; m->body += hdrlen; xfree(hdr); return (0); } /* * Find a header. If value is set, only the header value is returned, with EOL * stripped */ char * find_header(struct mail *m, const char *hdr, size_t *len, int value) { char *ptr; size_t hdrlen; hdrlen = strlen(hdr) + 1; /* include : */ if (m->body < hdrlen || m->size < hdrlen) return (NULL); line_init(m, &ptr, len); while (ptr != NULL) { if (ptr >= m->data + m->body) return (NULL); if (*len >= hdrlen && ptr[hdrlen - 1] == ':') { if (strncasecmp(ptr, hdr, hdrlen - 1) == 0) break; } line_next(m, &ptr, len); } if (ptr == NULL) return (NULL); /* If the entire header is wanted, return it. */ if (!value) return (ptr); /* Otherwise skip the header and following spaces. */ ptr += hdrlen; *len -= hdrlen; while (*len > 0 && isspace((u_char) *ptr)) { ptr++; (*len)--; } /* And trim newlines. */ while (*len > 0 && ptr[*len - 1] == '\n') (*len)--; if (len == 0) return (NULL); return (ptr); } /* Match a header. Same as find_header but uses fnmatch. */ char * match_header(struct mail *m, const char *patt, size_t *len, int value) { char *ptr, *last, *hdr; size_t hdrlen; line_init(m, &ptr, len); while (ptr != NULL) { if (ptr >= m->data + m->body) return (NULL); if ((last = memchr(ptr, ':', *len)) != NULL) { hdrlen = last - ptr; hdr = xmemstrdup(ptr, hdrlen); if (fnmatch(patt, hdr, FNM_CASEFOLD) == 0) break; xfree(hdr); } line_next(m, &ptr, len); } if (ptr == NULL) return (NULL); xfree(hdr); /* If the entire header is wanted, return it. */ if (!value) return (ptr); /* Include the : in the length. */ hdrlen++; /* Otherwise skip the header and following spaces. */ ptr += hdrlen; *len -= hdrlen; while (*len > 0 && isspace((u_char) *ptr)) { ptr++; (*len)--; } /* And trim newlines. */ while (*len > 0 && ptr[*len - 1] == '\n') (*len)--; if (len == 0) return (NULL); return (ptr); } /* * Find offset of body. The body is the offset of the first octet after the * separator (\n\n), or zero. */ size_t find_body(struct mail *m) { size_t len; char *ptr; line_init(m, &ptr, &len); while (ptr != NULL) { if (len == 1 && *ptr == '\n') { line_next(m, &ptr, &len); /* If no next line, body is end of mail. */ if (ptr == NULL) return (m->size); /* Otherwise, body is start of line after separator. */ return (ptr - m->data); } line_next(m, &ptr, &len); } return (0); } /* Count mail lines. */ void count_lines(struct mail *m, u_int *total, u_int *body) { size_t len; char *ptr; int flag; flag = 0; *total = *body = 0; line_init(m, &ptr, &len); while (ptr != NULL) { if (flag) (*body)++; if (len == 1 && *ptr == '\n') flag = 1; (*total)++; line_next(m, &ptr, &len); } } /* Append line to mail. Used during fetching. */ int append_line(struct mail *m, const char *line, size_t size) { if (mail_resize(m, m->size + size + 1) != 0) return (-1); if (size > 0) memcpy(m->data + m->size, line, size); m->data[m->size + size] = '\n'; m->size += size + 1; return (0); } /* Fill array of users from headers. */ struct users * find_users(struct mail *m) { struct passwd *pw; struct users *users; char *ptr, *last, *data, *hdr, *dom, *aptr, *dptr, *line; u_int i, j; size_t len, alen; users = xmalloc(sizeof *users); ARRAY_INIT(users); line = NULL; line_init(m, &ptr, &len); while (ptr != NULL) { if (ptr >= m->data + m->body) break; line = xmemstrdup(ptr, len); /* Find separator. */ data = strchr(line, ':'); if (data == NULL) goto next; *data++ = '\0'; while (isspace((u_char) *data)) data++; while ((last = strrchr(data, '\n')) != NULL) *last = '\0'; /* Is this in the list of headers? */ for (i = 0; i < ARRAY_LENGTH(conf.headers); i++) { hdr = ARRAY_ITEM(conf.headers, i); if (*hdr == '\0') continue; if (fnmatch(hdr, line, FNM_CASEFOLD) == 0) break; } if (i == ARRAY_LENGTH(conf.headers)) goto next; /* Yes, try to find addresses. */ while (*data != '\0') { aptr = find_address(data, strlen(data), &alen); if (aptr == NULL) break; data = aptr + alen; aptr = xmemstrdup(aptr, alen); dptr = memchr(aptr, '@', alen); *dptr++ = '\0'; for (j = 0; j < ARRAY_LENGTH(conf.domains); j++) { dom = ARRAY_ITEM(conf.domains, j); if (fnmatch(dom, dptr, FNM_CASEFOLD) != 0) continue; pw = getpwnam(aptr); if (pw != NULL) ARRAY_ADD(users, pw->pw_uid); endpwent(); break; } xfree(aptr); } next: if (line != NULL) xfree(line); line = NULL; line_next(m, &ptr, &len); } if (ARRAY_EMPTY(users)) { ARRAY_FREE(users); xfree(users); return (NULL); } return (users); } char * find_address(char *buf, size_t len, size_t *alen) { char *ptr, *hdr, *first, *last; /* * RFC2822 email addresses are stupidly complicated, so we just do a * naive match which is good enough for 99% of addresses used now. This * code is pretty inefficient. */ /* Duplicate the header as a string to work on it. */ if (len == 0) return (NULL); hdr = xmemstrdup(buf, len); /* First, replace any sections in "s with spaces. */ ptr = hdr; while (*ptr != '\0') { if (*ptr == '"') { while (*ptr != '"' && *ptr != '\0') *ptr++ = ' '; if (*ptr == '\0') break; } ptr++; } /* * Now, look for sections matching: * [< ][A-Za-z0-9._%+-]+@[A-Za-z0-9.\[\]-]+[> ,;]. */ #define isfirst(c) ((c) == '<' || (c) == ' ') #define islast(c) ((c) == '>' || (c) == ' ' || (c) == ',' || (c) == ';') #define isuser(c) (isalnum(c) || \ (c) == '.' || (c) == '_' || (c) == '%' || (c) == '+' || (c) == '-') #define isdomain(c) (isalnum(c) || \ (c) == '.' || (c) == '-' || (c) == '[' || (c) == ']') ptr = hdr + 1; for (;;) { /* Find an @. */ if ((ptr = strchr(ptr, '@')) == NULL) break; /* Find the end. */ last = ptr + 1; while (*last != '\0' && isdomain((u_char) *last)) last++; if (*last != '\0' && !islast((u_char) *last)) { ptr = last + 1; continue; } /* Find the start. */ first = ptr - 1; while (first != hdr && isuser((u_char) *first)) first--; if (first != hdr && !isfirst((u_char) *first)) { ptr = last + 1; continue; } /* If the last is > the first must be < and vice versa. */ if (*last == '>' && *first != '<') { ptr = last + 1; continue; } if (*first == '<' && *last != '>') { ptr = last + 1; continue; } /* If not right at the start, strip first character. */ if (first != hdr) first++; /* Free header copy. */ xfree(hdr); /* Have last and first, return the address. */ *alen = last - first; return (buf + (first - hdr)); } xfree(hdr); return (NULL); } void trim_from(struct mail *m) { char *ptr; size_t len; if (m->data == NULL || m->body == 0 || m->size < 5) return; if (strncmp(m->data, "From ", 5) != 0) return; line_init(m, &ptr, &len); m->size -= len; m->off += len; m->data = m->base + m->off; m->body -= len; } char * make_from(struct mail *m) { time_t t; char *s, *from = NULL; size_t fromlen = 0; from = find_header(m, "from", &fromlen, 1); if (from != NULL && fromlen > 0) from = find_address(from, fromlen, &fromlen); if (fromlen > INT_MAX) from = NULL; if (from == NULL) { from = conf.info.user; fromlen = strlen(from); } t = time(NULL); xasprintf(&s, "From %.*s %.24s", (int) fromlen, from, ctime(&t)); return (s); } /* * Sometimes mail has wrapped header lines, this undoubtedly looks neat but * makes them a pain to match using regexps. We build a list of the newlines * in all the wrapped headers in m->wrapped, and can then quickly unwrap them * for regexp matching and wrap them again for delivery. */ u_int fill_wrapped(struct mail *m) { char *ptr; size_t end, off; u_int n; if (!ARRAY_EMPTY(&m->wrapped)) fatalx("already wrapped"); ARRAY_INIT(&m->wrapped); m->wrapchar = '\0'; end = m->body; ptr = m->data; n = 0; for (;;) { ptr = memchr(ptr, '\n', m->size - (ptr - m->data)); if (ptr == NULL) break; ptr++; off = ptr - m->data; if (off >= end) break; /* Check if the line starts with whitespace. */ if (!isblank((u_char) *ptr)) continue; /* Save the position. */ ARRAY_ADD(&m->wrapped, off - 1); n++; } return (n); } void set_wrapped(struct mail *m, char ch) { u_int i; if (m->wrapchar == ch) return; m->wrapchar = ch; for (i = 0; i < ARRAY_LENGTH(&m->wrapped); i++) m->data[ARRAY_ITEM(&m->wrapped, i)] = ch; }