pax_global_header00006660000000000000000000000064132406120750014512gustar00rootroot0000000000000052 comment=2cd67e3fe1485908c1986fb64580906f98789da8 mblaze-0.3.2/000077500000000000000000000000001324061207500127665ustar00rootroot00000000000000mblaze-0.3.2/.mailmap000066400000000000000000000001161324061207500144050ustar00rootroot00000000000000Leah Neukirchen Christian Neukirchen mblaze-0.3.2/.travis.yml000066400000000000000000000004761324061207500151060ustar00rootroot00000000000000language: c os: - linux - osx compiler: - clang - gcc script: - case "$(uname)" in Linux) make CFLAGS="-g -O2 -Wall -Wno-switch -Wextra -D_FORTIFY_SOURCE=2" && make check || exit 1 ;; Darwin) make LDLIBS=-liconv && make check || exit 1 ;; esac notifications: email: false mblaze-0.3.2/COPYING000066400000000000000000000007021324061207500140200ustar00rootroot00000000000000mblaze is in the public domain. To the extent possible under law, Leah Neukirchen has waived all copyright and related or neighboring rights to this work. http://creativecommons.org/publicdomain/zero/1.0/ The files mystrverscmp.c and mymemmem.c and mytimegm.c are MIT-licensed and were written by Rich Felker. mpick.c is largely based on code by Christian Neukirchen and has been contributed by Duncan Overbruck mblaze-0.3.2/GNUmakefile000066400000000000000000000045561324061207500150520ustar00rootroot00000000000000CFLAGS?=-g -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 override CFLAGS:=-Wall -Wno-switch -Wextra $(CFLAGS) LDLIBS=-lrt OS := $(shell uname) ifeq ($(OS),OpenBSD) LOCALBASE=/usr/local override CFLAGS+=-I$(LOCALBASE)/include -pthread LDLIBS=-L$(LOCALBASE)/lib -liconv -pthread endif ifeq ($(OS),Darwin) LDLIBS=-liconv endif DESTDIR= PREFIX=/usr/local BINDIR=$(PREFIX)/bin MANDIR=$(PREFIX)/share/man ALL = maddr magrep mdate mdeliver mdirs mexport mflag mflow mgenmid mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread SCRIPT = mcolor mcom mless mmkdir mquote museragent all: $(ALL) museragent $(ALL) : % : %.o maddr magrep mdeliver mexport mflag mflow mgenmid mhdr mpick mscan msed mshow \ msort mthread : blaze822.o mymemmem.o mytimegm.o maddr magrep mexport mflag mgenmid mhdr mlist mpick mscan msed mseq mshow msort \ mthread : seq.o slurp.o maddr magrep mflow mhdr mpick mscan mshow : rfc2047.o magrep mflow mhdr mshow : rfc2045.o mshow : filter.o safe_u8putstr.o rfc2231.o pipeto.o mscan : pipeto.o msort : mystrverscmp.o mmime : slurp.o minc mlist : squeeze_slash.o museragent: FRC @printf '#!/bin/sh\nprintf "User-Agent: mblaze/%s (%s)\\n"\n' \ "$$({ git describe --always --dirty 2>/dev/null || cat VERSION; } | sed 's/^v//')" \ "$$(date +%Y-%m-%d)" >$@ @chmod +x $@ README: man/mblaze.7 mandoc -Tutf8 $< | col -bx >$@ clean: FRC -rm -f $(ALL) *.o museragent check: FRC all PATH=$$(pwd):$$PATH prove -v install: FRC all mkdir -p $(DESTDIR)$(BINDIR) \ $(DESTDIR)$(MANDIR)/man1 \ $(DESTDIR)$(MANDIR)/man5 \ $(DESTDIR)$(MANDIR)/man7 install -m0755 $(ALL) $(SCRIPT) $(DESTDIR)$(BINDIR) ln -sf mless $(DESTDIR)$(BINDIR)/mnext ln -sf mless $(DESTDIR)$(BINDIR)/mprev ln -sf mcom $(DESTDIR)$(BINDIR)/mbnc ln -sf mcom $(DESTDIR)$(BINDIR)/mfwd ln -sf mcom $(DESTDIR)$(BINDIR)/mrep install -m0644 man/*.1 $(DESTDIR)$(MANDIR)/man1 install -m0644 man/*.5 $(DESTDIR)$(MANDIR)/man5 install -m0644 man/*.7 $(DESTDIR)$(MANDIR)/man7 release: VERSION=$$(git describe --tags | sed 's/^v//;s/-[^.]*$$//') && \ git archive --prefix=mblaze-$$VERSION/ -o mblaze-$$VERSION.tar.gz HEAD sign: VERSION=$$(git describe --tags | sed 's/^v//;s/-[^.]*$$//') && \ gpg --armor --detach-sign mblaze-$$VERSION.tar.gz && \ signify -S -s ~/.signify/mblaze.sec -m mblaze-$$VERSION.tar.gz && \ sed -i '1cuntrusted comment: verify with mblaze.pub' mblaze-$$VERSION.tar.gz.sig FRC: mblaze-0.3.2/INSTALL.md000066400000000000000000000007201324061207500144150ustar00rootroot00000000000000# Compiling and installing mblaze You must use GNU make to build. Use `make all` to build, `make install` to install relative to `PREFIX` (`/usr/local` by default). The `DESTDIR` convention is respected. `mblaze` has been tested on - Linux 4.11 (glibc 2.25) - Linux 4.7 (glibc 2.24) - Linux 4.7 (musl 1.1.15) - Linux 3.2 (glibc 2.13) - FreeBSD 11.0 - OpenBSD 5.9 - OpenBSD 6.1 - Windows 10 (Version 1151) with Cygwin 2.3.1(0.291/5/3) - OS X Yosemite (10.10.5) mblaze-0.3.2/NEWS.md000066400000000000000000000033021324061207500140620ustar00rootroot00000000000000## 0.3.2 (2018-02-13) * magrep: add *:REGEX to search in any header * Fix of a buffer overflow in blaze822_multipart. * Small bug fixes. * Many documentation improvements by Larry Hynes. ## 0.3.1 (2018-01-30) * mless: support $NO_COLOR * mcolor: support $NO_COLOR * blaze822.h: ensure PATH_MAX is defined * Improved documentation. * Many fixes for address parser. ## 0.3 (2018-01-12) * New tool mflow to reformat format=flowed plain text mails. * New tool mbnc to bounce mails (send original mail to someone else). * mshow filters can output raw text now, e.g. for HTML rendering with colors. * mrep can quote mail that doesn't have a plain text part. * mcom runs mmime when deemed necessary. * mhdr can extract MIME parameters. * New contrib: mrecode * New contrib: mraw * mshow regards non-MIME mails as MIME mails with one part now. * mshow -F to disable MIME filters. * mpick supports negations now. * msed can remove headers depending on their value. * Improved UTF-8 parsing. * Improved documentation. * Numerous bug fixes and portability fixes. ## 0.2 (2017-07-17) * New sequence syntax `m:+n` for `n` messages after message `m`. * Threading shortcuts `=`, `_`, `^` for `.=`, `._`, `.^`. * Sequence related errors are now reported. * minc and mlist normalize slashes in paths. * mfwd now generates conforming message/rfc822 parts. * mthread can add optional folders (e.g. your outbox) to resolve message ids. * mcom now adds Date: just before sending or cancelling the mail. * VIOLATIONS.md documents how mblaze works with certain common mistakes. * Full documentation revamp by Larry Hynes. * Fix rare crash looking for mail body. * Numerous small bug and portability fixes. ## 0.1 (2017-06-24) * Initial release mblaze-0.3.2/README000066400000000000000000000133611324061207500136520ustar00rootroot00000000000000MBLAZE(7) Miscellaneous Information Manual MBLAZE(7) NAME mblaze – introduction to the mblaze message system DESCRIPTION The mblaze message system is a set of Unix utilities for processing and interacting with mail messages which are stored in maildir folders. Its design is roughly inspired by MH, the RAND Message Handling System, but it is a complete implementation from scratch. mblaze consists of these Unix utilities that each do one job: maddr(1) extract mail addresses from messages magrep(1) search messages matching a pattern mbnc(1) bounce messages mcom(1) compose and send messages mdeliver(1) deliver messages or import mbox file mdirs(1) list maildir folders, recursively mexport(1) export messages as mbox file mflag(1) manipulate maildir flags mflow(1) reflow format=flowed plain text messages mfwd(1) forward messages mgenmid(1) generate a Message-ID mhdr(1) print message headers minc(1) incorporate new messages mless(1) conveniently read messages in less(1) mlist(1) list and filter messages mmime(1) create MIME messages mmkdir(1) create new maildir folders mpick(1) advanced message filter mrep(1) reply to messages mscan(1) generate one-line message summaries msed(1) manipulate message headers mseq(1) manipulate message sequences mshow(1) render messages and extract MIME parts msort(1) sort messages mthread(1) arrange messages into discussions mblaze is a classic command line MUA and has no features for receiving or transferring messages; you can operate on messages in a local maildir spool, or fetch your messages using fdm(1), getmail(1), offlineimap(1), or similar utilities, and send it using dma(8), msmtp(1), sendmail(8), as provided by OpenSMTPD, Postfix, or similar. mblaze operates directly on maildir folders and doesn't use its own caches or databases. There is no setup needed for many uses. All utilities have been written with performance in mind. Enumeration of all messages in a maildir is avoided unless necessary, and then optimized to limit syscalls. Parsing message metadata is optimized to limit I/O requests. Initial operations on a large maildir may feel slow, but as soon as they are in the file system cache, everything is blazingly fast. The utilities are written to be memory efficient (i.e. not wasteful), but whole messages are assumed to fit into RAM easily (one at a time). mblaze has been written from scratch and is now well tested, but it is not 100% RFC-conforming (which is neither worth it, nor desirable). There may be issues with very old, nonconforming, messages. mblaze is written in portable C, using only POSIX functions (apart from a tiny Linux-only optimization), and has no external dependencies. It supports MIME and more than 7-bit messages (everything the host iconv(3) can decode). It assumes you work in a UTF-8 environment. mblaze works well with other Unix utilities such as mairix(1), mu(1), or offlineimap(1). EXAMPLES mblaze utilities are designed to be composed together in a pipe. They are suitable for interactive use and for scripting, and integrate well into a Unix workflow. For example, you could decide you want to look at all unseen messages in your INBOX, oldest first. mlist -s ~/Maildir/INBOX | msort -d | mscan To operate on a set of messages in multiple steps, you can save it as a sequence, e.g. add a call to ‘mseq -S’ to the above command: mlist -s ~/Maildir/INBOX | msort -d | mseq -S | mscan Now mscan will show message numbers and you could look at the first five messages at once, for example: mshow 1:5 Likewise, you could decide to incorporate (by moving from new to cur) all new messages in all folders, thread it and look at it interactively: mdirs ~/Maildir | xargs minc | mthread | mless Or you could list the attachments of the 20 largest messages in your INBOX: mlist ~/Maildir/INBOX | msort -S | tail -20 | mshow -t Or apply the patches from the current message: mshow -O. '*.diff' | patch As usual with pipes, the sky is the limit. CONCEPTS mblaze deals with messages (which are files), folders (which are maildir folders), sequences (which are newline-separated lists of messages, possibly saved on disk in ${MBLAZE:-$HOME/.mblaze}/seq), and the current message (kept as a symlink in ${MBLAZE:-$HOME/.mblaze}/cur). Messages in the saved sequence can be referred to using special syntax as explained in mmsg(7). Many utilities have a default behavior when used interactively from a terminal (e.g. operate on the current message or the current sequence). For scripting, you must make these arguments explicit. For configuration, see mblaze-profile(5). SEE ALSO mailx(1), mblaze-profile(5), nmh(7) AUTHORS Leah Neukirchen There is a mailing list available at mblaze@googlegroups.com (to subscribe, send a message to mblaze+subscribe@googlegroups.com) and an IRC channel #vuxu on irc.freenode.net. Please report security-related bugs directly to the author. LICENSE mblaze is in the public domain. To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. http://creativecommons.org/publicdomain/zero/1.0/ Void Linux January 6, 2018 Void Linux mblaze-0.3.2/VERSION000066400000000000000000000000041324061207500140300ustar00rootroot000000000000000.2 mblaze-0.3.2/VIOLATIONS.md000066400000000000000000000017761324061207500150120ustar00rootroot00000000000000# Standard-violations detected in the wild during development of mblaze This list is probably not complete. * RFC5322 assumes CRLF line endings throughout, but Maildir messages are generally using Unix line endings. mblaze accepts both, and only uses CRLF when required (e.g. for signing). * Backslashes in atoms (RFC 5322, 3.2.3) are parsed as if they were inside quoted strings. * Return-path is accepted without angle-addr (RFC5322, 3.6.7). * Encoded words within quoted strings (RFC2047, 5.3) are decoded for header printing. * Encoded words within MIME parameters (RFC2047, 5.3) are NOT decoded. * Empty encoded words are decoded as empty string (RFC2047, 2). * Split multi-octet characters between encoded words (RFC2047, 5.3) are reassembled if the encodings agree. * Date parsing is strict, obsolete timezone and two-digit years are not parsed (RFC5322, 4.3). * Mails without MIME-Version (RFC2045, 4) are still subject to MIME decoding if the Content-Transfer-Encoding header is present. mblaze-0.3.2/blaze822.c000066400000000000000000000316771324061207500145010ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" #include "blaze822_priv.h" #define bufsiz 4096 static long parse_posint(char **s, size_t minn, size_t maxn) { long n; char *end; errno = 0; n = strtol(*s, &end, 10); if (errno) return -1; if (n < (long)minn || n > (long)maxn) { errno = ERANGE; return -1; } *s = end; return n; } time_t blaze822_date(char *s) { struct tm tm; int c; #define i4(m) (((uint32_t) m[0]<<24 | m[1]<<16 | m[2]<<8 | m[3]) == \ ((uint32_t) s[0]<<24 | s[1]<<16 | s[2]<<8 | s[3] | 0x20202020) \ && (s += 4)) #define i3(m) (((uint32_t) m[0]<<24 | m[1]<<16 | m[2]<<8) == \ ((uint32_t) s[0]<<24 | s[1]<<16 | s[2]<<8 | 0x20202000) \ && (s += 3)) while (iswsp(*s)) s++; if (i4("mon,") || i4("tue,") || i4("wed,") || i4("thu,") || i4("fri,") || i4("sat,") || i4("sun,")) while (iswsp(*s)) s++; if ((c = parse_posint(&s, 1, 31)) < 0) goto fail; tm.tm_mday = c; while (iswsp(*s)) s++; if (i3("jan")) tm.tm_mon = 0; else if (i3("feb")) tm.tm_mon = 1; else if (i3("mar")) tm.tm_mon = 2; else if (i3("apr")) tm.tm_mon = 3; else if (i3("may")) tm.tm_mon = 4; else if (i3("jun")) tm.tm_mon = 5; else if (i3("jul")) tm.tm_mon = 6; else if (i3("aug")) tm.tm_mon = 7; else if (i3("sep")) tm.tm_mon = 8; else if (i3("oct")) tm.tm_mon = 9; else if (i3("nov")) tm.tm_mon = 10; else if (i3("dec")) tm.tm_mon = 11; else goto fail; #undef i3 #undef i4 while (iswsp(*s)) s++; if ((c = parse_posint(&s, 1000, 9999)) > 0) { tm.tm_year = c - 1900; } else if ((c = parse_posint(&s, 0, 49)) > 0) { tm.tm_year = c + 100; } else if ((c = parse_posint(&s, 50, 99)) > 0) { tm.tm_year = c; } else goto fail; while (iswsp(*s)) s++; if ((c = parse_posint(&s, 0, 24)) < 0) goto fail; tm.tm_hour = c; if (*s++ != ':') goto fail; if ((c = parse_posint(&s, 0, 59)) < 0) goto fail; tm.tm_min = c; if (*s++ == ':') { if ((c = parse_posint(&s, 0, 61)) < 0) goto fail; tm.tm_sec = c; } else { tm.tm_sec = 0; } while (iswsp(*s)) s++; if (*s == '+' || *s == '-') { int neg = (*s == '-'); s++; if ((c = parse_posint(&s, 0, 10000)) < 0) goto fail; if (neg) { tm.tm_hour += c / 100; tm.tm_min += c % 100; } else { tm.tm_hour -= c / 100; tm.tm_min -= c % 100; } } tm.tm_isdst = -1; time_t r = tm_to_secs(&tm); return r; fail: return -1; } static char * skip_comment(char *s) { if (*s != '(') return s; long d = 0; do { if (!*s) break; else if (*s == '(') d++; else if (*s == ')') d--; s++; } while (d > 0); return s; } // always 0 terminates // never writes more than dstmax to dst // returns how many bytes were appended static size_t safe_append(char *dst, size_t dstmax, char *strbeg, char *strend) { size_t dstlen = strlen(dst); if (dstmax - dstlen - 1 < strend - strbeg) strend = strbeg + (dstmax - dstlen - 1); memcpy(dst + dstlen, strbeg, strend - strbeg); dst[dstlen + (strend - strbeg)] = 0; return strend - strbeg; } static size_t safe_append_space(char *dst, size_t dstmax) { char sp[] = " "; char *se = sp + 1; return safe_append(dst, dstmax, sp, se); } char * blaze822_addr(char *s, char **dispo, char **addro) { static char disp[1024]; static char addr[1024]; memset(addr, 0, sizeof addr); memset(disp, 0, sizeof disp); char ttok[1024] = { 0 }; char *tc = ttok; char *te = ttok + sizeof ttok; int not_addr = 0; while (1) { if (!*s || iswsp(*s) || *s == ',' || *s == ';') { if (tc != ttok) { if (!*addr && !not_addr && memchr(ttok, '@', tc - ttok)) { safe_append(addr, sizeof addr, ttok, tc); } else { if (*disp) safe_append_space(disp, sizeof disp); safe_append(disp, sizeof disp, ttok, tc); } tc = ttok; *ttok = 0; not_addr = 0; } if (!*s) { if (!*addr && !*disp) { if (dispo) *dispo = 0; if (addro) *addro = 0; return 0; } break; } if (*s == ',' || *s == ';') { s++; if (*addr || *disp) break; } s++; } else if (*s == '<') { char tok[1024] = { 0 }; char *c = tok; char *e = tok + sizeof tok; s++; while (*s && c < e && *s != '>') { s = skip_comment(s); if (*s == '"') { // local part may be quoted, allow all s++; while (*s && c < e && *s != '"') { if (*s == '\\') s++; *c++ = *s++; } if (*s == '"') s++; } else if (*s == '<') { c = tok; s++; } else { if (iswsp(*s)) s++; else *c++ = *s++; } } if (*s == '>') s++; if (*addr) { if (*disp) safe_append_space(disp, sizeof disp); safe_append(disp, sizeof disp, addr, addr + strlen(addr)); } *addr = 0; safe_append(addr, sizeof addr, tok, c); } else if (*s == '"') { char tok[1024] = { 0 }; char *c = tok; char *e = tok + sizeof tok; s++; while (*s && c < e && *s != '"') { if (*s == '\\') s++; *c++ = *s++; } if (*s == '"') s++; if (memchr(tok, '@', c - tok)) not_addr = 1; // @ inside "" is never an addr if (tc != ttok) tc += safe_append_space(ttok, sizeof ttok); tc += safe_append(ttok, sizeof ttok, tok, c); } else if (*s == '(') { char *z = skip_comment(s); if (!*disp && *addr) // user@host (name) safe_append(disp, sizeof disp, s + 1, z - 1); else if (*disp) { // copy comment safe_append_space(disp, sizeof disp); safe_append(disp, sizeof disp, s, z); } s = z; } else if (*s == ':') { if (memchr(ttok, '[', tc - ttok)) { // in ipv6 address if (tc < te) *tc++ = *s++; } else { // ignore group name and start over s++; tc = ttok; memset(addr, 0, sizeof addr); memset(disp, 0, sizeof disp); *ttok = 0; not_addr = 0; } } else { if (*s == '\\' && *(s+1)) s++; if (tc < te) *tc++ = *s++; } } char *host = strrchr(addr, '@'); char *u; if (host && (u = strpbrk(addr, "()<>[]:;@\\,\" \t")) && u < host) { // need to "-quote local-part ssize_t hlen = strlen(host); char addr2[sizeof addr]; char *e = addr2 + sizeof addr2 - 1; char *t; u = addr; t = addr2; *t++ = '"'; while (u < host && e - t > 2) { if (*u == '"' || *u == '\\') *t++ = '\\'; *t++ = *u++; } *t++ = '"'; if (e - t > hlen + 1) { memcpy(t, host, hlen); *(t + hlen) = 0; memcpy(addr, addr2, sizeof addr); } } if (dispo) *dispo = *disp ? disp : 0; if (addro) *addro = *addr ? addr : 0; return s; } static void compress_hdr(char *s, char *end) { char *t, *h; if ((t = h = strchr(s, '\n'))) { while (h < end && *h) { if (*h == '\n') { *t++ = ' '; while (*h && isfws(*h)) h++; } *t++ = *h++; } // remove trailing whitespace while (s < t && isfws(t[-1])) *--t = 0; // zero fill gap while (t < h) *t++ = 0; } } static void unfold_hdr(char *buf, char *end) { char *s, *l; *end = 0; // sanitize all nul in message headers, srsly if (memchr(buf, 0, end-buf)) for (s = buf; s < end; s++) if (*s == 0) *s = ' '; // normalize crlf if (memchr(buf, '\r', end-buf)) for (s = buf; s < end; s++) if (*s == '\r') { if (*(s+1) == '\n') *s = '\n'; else *s = ' '; } l = buf; s = buf; while (s < end && *s != ':' && *s != '\n') { *s = lc(*s); s++; } while (s < end) { s = memchr(s+1, '\n', end-s); if (!s) break; while (s < end && *s == '\n') s++; if (!iswsp(*s)) { *(s-1) = 0; compress_hdr(l, s-1); l = s; while (s < end && *s != ':' && *s != '\n') { *s = lc(*s); s++; } } } compress_hdr(l, end); } struct message * blaze822(char *file) { int fd; ssize_t rd; char *buf; ssize_t bufalloc; ssize_t used; char *end; struct message *mesg = malloc(sizeof (struct message)); if (!mesg) return 0; fd = open(file, O_RDONLY); if (fd < 0) { free(mesg); return 0; } buf = 0; bufalloc = 0; used = 0; while (1) { int overlap = used > 3 ? 3 : 0; bufalloc += bufsiz; buf = realloc(buf, bufalloc); if (!buf) { free(mesg); close(fd); return 0; } rd = read(fd, buf+used, bufalloc-used); if (rd == 0) { end = buf+used; break; } if (rd < 0) { free(mesg); free(buf); close(fd); return 0; } if ((end = mymemmem(buf-overlap+used, rd+overlap, "\n\n", 2))) { end++; break; } if ((end = mymemmem(buf-overlap+used, rd+overlap, "\r\n\r\n", 4))) { end++; end++; break; } used += rd; } close(fd); *end = 0; // dereferencing *end is safe unfold_hdr(buf, end); mesg->msg = buf; mesg->end = end; mesg->body = mesg->bodyend = mesg->bodychunk = mesg->orig_header = 0; return mesg; } struct message * blaze822_mem(char *src, size_t len) { char *buf; char *end; struct message *mesg = malloc(sizeof (struct message)); if (!mesg) return 0; if ((end = mymemmem(src, len, "\n\n", 2))) { mesg->body = end+2; } else if ((end = mymemmem(src, len, "\r\n\r\n", 4))) { mesg->body = end+4; } else { end = src + len; mesg->body = end; mesg->bodyend = end; } if (mesg->body) mesg->bodyend = src + len; size_t hlen = end - src; buf = malloc(hlen+1); if (!buf) return 0; memcpy(buf, src, hlen); end = buf+hlen; *end = 0; // dereferencing *end is safe unfold_hdr(buf, end); mesg->msg = buf; mesg->end = end; mesg->bodychunk = 0; // src is not ours mesg->orig_header = src; return mesg; } void blaze822_free(struct message *mesg) { if (!mesg) return; if (mesg->bodychunk == mesg->msg) { munmap(mesg->bodychunk, mesg->bodyend - mesg->msg); } else { free(mesg->msg); free(mesg->bodychunk); } free(mesg); } char * blaze822_hdr_(struct message *mesg, const char *hdr, size_t hdrlen) { char *v; if (hdrlen == 0 || hdrlen-1 >= (size_t)(mesg->end - mesg->msg)) return 0; // header too small for the key, probably empty // special case: first header, no leading nul if (memcmp(mesg->msg, hdr+1, hdrlen-1) == 0) { v = mesg->msg; hdrlen--; } else { v = mymemmem(mesg->msg, mesg->end - mesg->msg, hdr, hdrlen); } if (!v) return 0; v += hdrlen; while (*v && iswsp(*v)) v++; return v; } char * blaze822_chdr(struct message *mesg, const char *chdr) { char hdr[256]; char *c; size_t l = snprintf(hdr, sizeof hdr, "%c%s:", 0, chdr); for (c = hdr+1; *c; c++) *c = lc(*c); return blaze822_hdr_(mesg, hdr, l); } struct message * blaze822_file(char *file) { char *buf = 0; ssize_t rd = 0, n; int fd = open(file, O_RDONLY); if (fd < 0) return 0; struct stat st; if (fstat(fd, &st) < 0) goto error; if (S_ISFIFO(st.st_mode)) { // unbounded read, grow buffer const ssize_t bufblk = 16384; ssize_t bufalloc = bufblk; buf = malloc(bufalloc); if (!buf) goto error; do { if (bufalloc < rd + bufblk) { bufalloc *= 2; buf = realloc(buf, bufalloc); if (!buf) goto error; } if ((n = read(fd, buf + rd, bufblk)) < 0) { if (errno == EINTR) { continue; } else { perror("read"); goto error; } } rd += n; } while (n > 0); } else { // file size known ssize_t s = st.st_size; buf = malloc(s+1); if (!buf) goto error; do { if ((n = read(fd, buf + rd, s - rd)) < 0) { if (errno == EINTR) { continue; } else { perror("read"); goto error; } } rd += n; } while (rd < s && n > 0); } close(fd); buf[rd] = 0; // XXX duplicate header in ram... struct message *mesg = blaze822_mem(buf, rd); if (mesg) mesg->bodychunk = buf; return mesg; error: close(fd); free(buf); return 0; } struct message * blaze822_mmap(char *file) { int fd = open(file, O_RDONLY); if (fd < 0) return 0; struct stat st; if (fstat(fd, &st) < 0) goto error; size_t len = st.st_size; struct message *mesg = malloc(sizeof (struct message)); if (!mesg) goto error; char *buf = mmap(0, len+1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); if (buf == MAP_FAILED) { perror("mmap"); goto error; } close(fd); char *end; if ((end = mymemmem(buf, len, "\n\n", 2))) { mesg->body = end+2; } else if ((end = mymemmem(buf, len, "\r\n\r\n", 4))) { mesg->body = end+4; } else { end = buf + len; mesg->body = end; } unfold_hdr(buf, end); mesg->msg = mesg->bodychunk = buf; mesg->end = end; mesg->bodyend = buf + len; mesg->orig_header = 0; return mesg; error: close(fd); return 0; } size_t blaze822_headerlen(struct message *mesg) { return mesg->end - mesg->msg; } char * blaze822_body(struct message *mesg) { return mesg->body; } char * blaze822_orig_header(struct message *mesg) { return mesg->orig_header; } size_t blaze822_bodylen(struct message *mesg) { if (!mesg->body || !mesg->bodyend) return 0; return mesg->bodyend - mesg->body; } char * blaze822_next_header(struct message *mesg, char *prev) { if (!prev) { prev = mesg->msg; } else { if (prev >= mesg->end) return 0; prev = prev + strlen(prev); } while (prev < mesg->end && *prev == 0) prev++; if (prev >= mesg->end) return 0; return prev; } mblaze-0.3.2/blaze822.h000066400000000000000000000055221324061207500144740ustar00rootroot00000000000000#include #include #include #include #if !defined(PATH_MAX) #define PATH_MAX 4096 #endif struct message; // blaze822.c struct message *blaze822(char *file); // just header struct message *blaze822_file(char *file); // header + body (read(2)) struct message *blaze822_mmap(char *file); // header + body (mmap(2)) struct message *blaze822_mem(char *buf, size_t len); // header + body char *blaze822_hdr_(struct message *mesg, const char *hdr, size_t len); #define blaze822_hdr(mesg, hdr) blaze822_hdr_(mesg, "\0" hdr ":", 2+strlen((hdr))) char *blaze822_chdr(struct message *mesg, const char *chdr); char *blaze822_next_header(struct message *mesg, char *prev); void blaze822_free(struct message *mesg); time_t blaze822_date(char *); char *blaze822_addr(char *, char **, char **); char *blaze822_body(struct message *mesg); size_t blaze822_bodylen(struct message *mesg); size_t blaze822_headerlen(struct message *mesg); char *blaze822_orig_header(struct message *mesg); // rfc2047.c int blaze822_decode_rfc2047(char *, char *, size_t, char *); int blaze822_decode_qp(char *start, char *stop, char **deco, size_t *decleno, int underscore); int blaze822_decode_b64(char *start, char *stop, char **deco, size_t *decleno); // rfc2045.c int blaze822_check_mime(struct message *msg); int blaze822_mime_body(struct message *msg, char **cto, char **bodyo, size_t *bodyleno, char **bodychunko); int blaze822_multipart(struct message *msg, struct message **imsg); int blaze822_mime_parameter(char *s, char *name, char **starto, char **stopo); typedef enum { MIME_CONTINUE, MIME_STOP, MIME_PRUNE } blaze822_mime_action; typedef blaze822_mime_action (*blaze822_mime_callback)(int, struct message *, char *, size_t); blaze822_mime_action blaze822_walk_mime(struct message *, int, blaze822_mime_callback); // rfc2231.c int blaze822_mime2231_parameter(char *, char *, char *, size_t, char *); // seq.c char *blaze822_seq_open(char *file); int blaze822_seq_load(char *map); long blaze822_seq_find(char *ref); char *blaze822_seq_cur(void); int blaze822_seq_setcur(char *s); struct blaze822_seq_iter { long lines; long cur; long start; long stop; long line; char *s; }; char *blaze822_seq_next(char *map, char *range, struct blaze822_seq_iter *iter); long blaze822_loop(int, char **, void (*)(char *)); long blaze822_loop1(char *arg, void (*cb)(char *)); char *blaze822_home_file(char *basename); // filter.c int filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno); // mygmtime.c time_t tm_to_secs(const struct tm *tm); // slurp.c int slurp(char *filename, char **bufo, off_t *leno); // safe_u8putstr.c #include void safe_u8putstr(char *s0, size_t l, FILE *stream); // pipeto.c pid_t pipeto(const char *cmdline); int pipeclose(pid_t pid); // squeeze_slash.c void squeeze_slash(char *); mblaze-0.3.2/blaze822_priv.h000066400000000000000000000007601324061207500155330ustar00rootroot00000000000000struct message { char *msg; char *end; char *body; char *bodyend; char *bodychunk; char *orig_header; }; // WSP = SP / HTAB #define iswsp(c) (((c) == ' ' || (c) == '\t')) #define isfws(c) (((unsigned char)(c) == ' ' || (unsigned char)(c) == '\t' || (unsigned char)(c) == '\n' || (unsigned char)(c) == '\r')) // ASCII lowercase without alpha check (wrong for "@[\]^_") #define lc(c) ((c) | 0x20) void *mymemmem(const void *h0, size_t k, const void *n0, size_t l); mblaze-0.3.2/contrib/000077500000000000000000000000001324061207500144265ustar00rootroot00000000000000mblaze-0.3.2/contrib/README.md000066400000000000000000000002741324061207500157100ustar00rootroot00000000000000# mblaze contrib This directory contains a few scripts and hacks that are not officially supported or subject to any robustness, portability or stability criteria. Use at your own risk. mblaze-0.3.2/contrib/malternative000077500000000000000000000005551324061207500170540ustar00rootroot00000000000000#!/bin/sh # malternative - multipart/alternative decoding policy { echo "Content-Type: $PIPE_CONTENTTYPE" echo cat } | mshow -t /dev/stdin | awk ' BEGIN { split("", ct) } /^ [0-9]/ { ct[++n] = $2 } function prefer(t) { for (i in ct) if (ct[i] == t) exit(64+i) } END { prefer("text/plain") prefer("text/html") exit 64+1 # default to first part }' mblaze-0.3.2/contrib/mblow000077500000000000000000000011111324061207500154660ustar00rootroot00000000000000#!/usr/bin/ruby # mblow - post an article via NNTP require 'socket' require 'optparse' params = ARGV.getopts("s:") port = 119 if params["s"] =~ /(.*):(.*)/ params["s"] = $1 port = Integer($2) end SERVER = params["s"] || ENV["NNTPSERVER"] || "news" nntp = TCPSocket.new SERVER, port msg = nntp.gets abort msg unless msg =~ /^200 / nntp.write "POST\r\n" msg = nntp.gets abort msg unless msg =~ /^340 / while line = gets line.chomp! line.sub!(/\A\./, '..') nntp.write(line + "\r\n") end nntp.write(".\r\n") msg = nntp.gets abort msg unless msg =~ /^240 / puts msg mblaze-0.3.2/contrib/mencrypt000077500000000000000000000011411324061207500162120ustar00rootroot00000000000000#!/bin/sh # mencrypt PLAINMSG - generate a PGP/MIME signed and encrypted message [ -f "$1" ] || exit 1 IFS=' ' FLAGS=$(mhdr -d -A -h from:to:cc:bcc: "$1" |sort -u |sed 's/^/--recipient=/') TMPD=$(mktemp -d -t mencrypt.XXXXXX) trap "rm -rf '$TMPD'" INT TERM EXIT awk '/^$/,0' "$1" | mmime | gpg --armor --encrypt --sign $FLAGS -o $TMPD/msg.asc || exit $? printf 'Version: 1\n' >$TMPD/version { sed '/^$/q' "$1" printf '#application/pgp-encrypted %s/version\n' "$TMPD" printf '#application/octet-stream %s/msg.asc\n' "$TMPD" } | mmime -t 'multipart/encrypted; protocol="application/pgp-encrypted"' mblaze-0.3.2/contrib/menter000077500000000000000000000004401324061207500156440ustar00rootroot00000000000000#!/bin/sh -e # menter [MSG] - run subshell in temporary directory with MSG extracted [ "$#" -eq 0 ] && set -- : dir=$(mktemp -d -t menter.XXXXXX) cd "$dir" mshow -t "$1" mshow -x "$1" 2>/dev/null ls -l ln -s "$(mseq "$1")" msg "${SHELL:-/bin/sh}" || true echo rm -r "$dir" rm -r "$dir" mblaze-0.3.2/contrib/mfillmid000077500000000000000000000005011324061207500161450ustar00rootroot00000000000000#!/bin/sh # mfillmid - fill in files for message-ids (via mairix) exec awk ' function q(a) { gsub("\\47", "\47\\\47\47", a); return "\47"a"\47" } /<..*>/ { match($0, "<..*>") mid = substr($0, RSTART+1, RLENGTH-2) if ("mairix -r m:" q(mid) | getline file) { print substr($0, 0, RSTART-1) file next } } { print }' mblaze-0.3.2/contrib/mgpg000077500000000000000000000005451324061207500153120ustar00rootroot00000000000000#!/bin/sh -e tmp=$(mktemp -t mgpg.XXXXXX) trap "rm -f '$tmp'" INT TERM EXIT { echo "Content-Type: $PIPE_CONTENTTYPE" echo cat } > "$tmp" n=$(mshow -t "$tmp" | awk -F: ' /: application\/pgp-encrypted/ {supported = 1} /: application\/octet-stream/ {if (supported) print $1}') if [ "$n" ]; then mshow -O "$tmp" "$n" | gpg -d 2>&1 exit 64 fi exit 63 mblaze-0.3.2/contrib/mhasatt000077500000000000000000000004371324061207500160210ustar00rootroot00000000000000#!/bin/sh # mhasatt [RANGE...] - print mails with real attachments mshow -t "${@:-:}" | awk ' /^[^ 0-9]/ && /\// { file = $0 } /^.*[0-9]*: (application\/pkcs7-signature)/ { next } /^.*[0-9]*: (application|image|video)\// || /^.*[0-9]*:.*name=/ { if (file) print file; file = 0 } ' mblaze-0.3.2/contrib/mmairix000077500000000000000000000001431324061207500160200ustar00rootroot00000000000000#!/bin/sh # mmairix QUERY - mblaze wrapper around mairix mairix -r "$@" | sed 's,//*,/,g' | mless mblaze-0.3.2/contrib/mpeek000077500000000000000000000002551324061207500154570ustar00rootroot00000000000000#!/bin/sh # mpeek - wrapper around mscan with a different seq export MAILSEQ=${MBLAZE:-$HOME/.mblaze}/peek.seq if [ -t 0 ]; then mseq "$@" else mseq -S | mscan "$@" fi mblaze-0.3.2/contrib/mraw000077500000000000000000000001311324061207500153150ustar00rootroot00000000000000#!/bin/sh # mraw [MSGS...] - display raw messages IFS=' ' exec cat -- $(mseq "${@:-.}") mblaze-0.3.2/contrib/mrecode000077500000000000000000000002411324061207500157670ustar00rootroot00000000000000#!/bin/sh # mrecode - recode stdin respecting PIPE_CHARSET into UTF-8 if [ -n "$PIPE_CHARSET" ]; then exec iconv -f "$PIPE_CHARSET" -t UTF-8 else exec cat fi mblaze-0.3.2/contrib/msendmail000077500000000000000000000020601324061207500163230ustar00rootroot00000000000000#!/bin/sh # msendmail TO... < msg - compose MIME mail noninteractively # -F from-name # -a attach-file # -b bcc # -c cc # -f from # -s subject # -m msg-paragraph IFS=' ' MBLAZE=${MBLAZE:-$HOME/.mblaze} from=$(mhdr -h local-mailbox "$MBLAZE/profile") subj= bcc= cc= msg= fromname= att= hdr() { [ -z "$2" ] && return printf '%s: ' "$1" shift printf '%s\n' "$@" | sed ':a;N;s/\n/, /;$!b a' } while getopts a:s:b:c:m:f:F: opt; do case "$opt" in s) subj=$OPTARG;; b) bcc="$bcc$IFS$OPTARG";; c) cc="$cc$IFS$OPTARG";; m) msg="$msg$OPTARG$IFS$IFS";; f) from=$OPTARG;; F) fromname=$OPTARG;; a) att="$att$IFS$OPTARG";; [?]) exit 1;; esac done shift $((OPTIND-1)) [ -n "$from" ] && [ -n "$fromname" ] && from="$fromname <$from>" { hdr To "$@" hdr Cc $cc hdr Bcc $bcc hdr Subject $subj hdr From $from hdr User-Agent "mblaze/beta (msendmail)" hdr Message-Id "$(mgenmid)" hdr Date "$(mdate)" printf '\n' if [ -n "$msg" ]; then printf '%s' "$msg" else cat fi for a in $att; do printf '#%s %s\n' "$(file -b --mime-type "$a")" "$a" done } | mmime mblaze-0.3.2/contrib/msign000077500000000000000000000010141324061207500154650ustar00rootroot00000000000000#!/bin/sh # msign PLAINMSG - generate a PGP/MIME signed message [ -f "$1" ] || exit 1 IFS=' ' TMPD=$(mktemp -d -t msign.XXXXXX) trap "rm -rf '$TMPD'" INT TERM EXIT awk '/^$/,0' "$1" | mmime | sed 's/$/ /' >"$TMPD"/content gpg --armor --detach-sign -o "$TMPD"/signature.asc "$TMPD"/content || exit $? { sed '/^$/q' "$1" printf '#mblaze/raw %s/content\n' "$TMPD" printf '#application/pgp-signature %s/signature.asc\n' "$TMPD" } | mmime -t 'multipart/signed; micalg="pgp-sha1"; protocol="application/pgp-signature"' mblaze-0.3.2/contrib/msuck000077500000000000000000000053221324061207500155000ustar00rootroot00000000000000#!/usr/bin/ruby # msuck - suck NNTP groups into Maildirs # # msuck [-s NNTPSERVER[:PORT]] [-d BASEDIR] [-l LIMIT] GROUPS... to fetch GROUPS # msuck [-s NNTPSERVER[:PORT]] -L to list all groups require 'socket' require 'fileutils' require 'optparse' $delivery = 0 HOST = Socket.gethostname def genname(id) $delivery += 1 t = Time.now "%d.M%06dP%05dQ%d.%s,N=%d" % [t.tv_sec, t.tv_usec, $$, $delivery, HOST, id] end params = ARGV.getopts("d:fl:s:L") dir = params["d"] || '.' LIMIT = if params["l"] Integer(params["l"]) else 10 end port = 119 if params["s"] =~ /(.*):(.*)/ params["s"] = $1 port = Integer($2) end SERVER = params["s"] || ENV["NNTPSERVER"] || "news" nntp = TCPSocket.new SERVER, port msg = nntp.gets abort msg unless msg =~ /^20[01] / if params["L"] # list all groups nntp.write("LIST NEWSGROUPS\r\n") msg = nntp.gets if msg !~ /^215 / abort msg end loop { msg = nntp.gets break if msg == ".\r\n" puts msg } exit end STDOUT.sync = true ARGV.each { |group| FileUtils.mkdir_p(File.join(dir, group, "cur")) FileUtils.mkdir_p(File.join(dir, group, "new")) FileUtils.mkdir_p(File.join(dir, group, "tmp")) nntp.write("GROUP #{group}\r\n") msg = nntp.gets unless msg =~ /^211 / STDERR.puts msg next end _, number, low, high, _ = msg.split(" ", 5) number = number.to_i low = low.to_i high = high.to_i low = high - LIMIT + 1 if number > LIMIT - 1 low = 1 if low <= 0 have = Dir.entries(File.join(dir, group, "cur")). map { |f| $1.to_i if f =~ /N=(\d+)/ }.compact ourhigh = have.max if ourhigh && low < ourhigh && !params["f"] low = ourhigh + 1 end next if low > high printf "%s %d-%d ", group, low, high nntp.write("STAT #{low}\r\n") msg = nntp.gets _, num, mid, _ = msg.split(" ", 4) loop { unless have.include? num.to_i nntp.write("ARTICLE\r\n") msg = nntp.gets if msg =~ /^220 / _, num, mid, _ = msg.split(" ", 4) text = ["X-Msuck: nntp://#{SERVER}/#{group}/#{num}\n"] loop { msg = nntp.gets break if msg == ".\r\n" msg.sub!(/\A\./, "") msg.sub!(/\r\n\z/, "\n") text << msg } text = text.join name = genname(num) File.write(File.join(dir, group, "tmp", name), text) File.rename(File.join(dir, group, "tmp", name), File.join(dir, group, "cur", name + ":2,")) print "." else STDERR.puts msg end else print "=" end nntp.write("NEXT\r\n") msg = nntp.gets if msg !~ /^223 / break end _, num, mid, _ = msg.split(" ", 4) } puts } mblaze-0.3.2/contrib/mtwoscan000077500000000000000000000001611324061207500162050ustar00rootroot00000000000000#!/bin/sh # mtwoscan - mscan with a two-line format exec mscan -f '%-4n%c%2i%s\n %2i%-3M %16D (%b) %f' "$@" mblaze-0.3.2/contrib/mverify000077500000000000000000000022271324061207500160400ustar00rootroot00000000000000#!/bin/sh # mverify MSG - verify a OpenPGP or SMIME message # Needs gpg (for OpenPGP) and openssl (for SMIME). [ "$#" -eq 0 ] && set -- . mshow -t "$1" | DOS2UNIX='/ $/!s/$/ /' awk -v "msg=$1" ' { match($0, "^ *"); indent = RLENGTH } $2 == "text/plain" { plain++ } $2 == "multipart/signed" { signed = 0+$1; si = indent; next } signed && !content && indent == si+2 { content = 0+$1; next } signed && content && !signature && indent == si+2 { signature = 0+$1; type = $2 } function q(a) { gsub("\\47", "\47\\\47\47", a); return "\47"a"\47" } END { if (type == "" && plain) { // guess plain text armored signature exit(system("mshow -r " q(msg) " | gpg --verify")); } else if (type == "") { print("No signature found.") exit(100) } else if (type == "application/pgp-signature") { exit(system("mshow -r -O " q(msg) " " q(content) \ " | sed $DOS2UNIX | " \ " { mshow -O " q(msg) " " q(signature) \ " | gpg --verify - /dev/fd/3; } 3<&0")) } else if (type == "application/pkcs7-signature") { exit(system("mshow -r -O " q(msg) " " q(signed) \ " | openssl smime -verify")) } else { print("Cannot verify signatures of type " type ".") exit(2) } } ' mblaze-0.3.2/contrib/mvi000077500000000000000000000135721324061207500151570ustar00rootroot00000000000000#!/bin/sh clear_buf() { while tput cup "${i:-0}" 0 && tput el \ && [ "$(( i+=1 ))" -le "$1" ] do :; done } term_init() { cols=$(tput cols) rows=$(tput lines) start_headers=$(( rows / 4 )) start_body=$(( start_headers + 1 )) end_body=$(( rows - start_body - 2 )) term_clear_headers=$(clear_buf "$start_headers"; tput cup 0 0) term_clear_body=$(tput cup "$(( start_body ))" 0; tput ed) term_move_status=$(tput cup "$(( rows ))" $(( cols - 5 )); tput el) term_move_cmd=$(tput cup "$(( rows ))" 0) term_init_cmd=$(tput cnorm; tput el) term_done_cmd=$(tput civis) } statusline() { printf "%s%s" "$term_move_status" "$@" } cmdline() { printf "%s%s:" "$term_move_cmd" "$term_init_cmd" stty "$stty_default" tput cnorm read -r cmd stty -echo -icanon case "$cmd" in "|"*) ;; "!"*) tput sgr0 && tput rmcup # restore to content before mvi eval "${cmd#!*}" # wait for enter, and delete message tput sc; printf "[enter to continue]" && read -r d; tput rc; tput el tput smcup # save new content ;; q) close ;; esac printf "%s" "$term_done_cmd" update_body=1 draw } update() { buf_row=1 [ "$buf_col" -le 0 ] && buf_col=1 mshow 2>/dev/null | mcolor \ | cut -c "$(( buf_col ))-$(( cols + buf_col - 1 ))" \ | trunc_lines >"$buf_path" buf_len=$(wc -l <"$buf_path") : $(( buf_len = buf_len + 1 )) : $(( buf_end = buf_len - end_body )) update_body=1 } trunc_lines() { t=$(tput el) while read -r l; do printf "%s%s\n" "$l" "$t" done } headers() { printf "%s" "$term_clear_headers" : $(( x = hdr_row - (start_headers / 2) )) : $(( y = hdr_row + (start_headers / 2) )) [ "$x" -gt 0 ] && x="+$x" [ "$y" -gt 0 ] && y="+$y" COLUMNS=$cols mscan ".$x:.$y" 2>/dev/null \ | awk ' function fg(c, s) { return sprintf("\033[38;5;%03dm%s\033[0m", c, s) } function so(s) { return sprintf("\033[1m%s\033[0m", s) } /^>/ { print so(fg(119, $0)); next } /^ *\\_/ { print fg(242, $0); next } { print } ' } body() { [ "$buf_row" -gt "$buf_end" ] && buf_row=$buf_end [ "$buf_row" -lt 1 ] && buf_row=1 : $(( x = buf_row )) : $(( y = end_body + buf_row )) [ "${update_body}" = "${x},${y}p" ] && return printf "%s" "$term_clear_body" update_body="${x},${y}p" sed -n "$update_body" "$buf_path" [ "$buf_len" -gt "$end_body" ] \ && statusline "$(( 100 * y / buf_len ))%" } draw() { headers body } init() { tput smcup # save position tput civis # cursor invisible term_init stty_default=$(stty -g) stty -echo -icanon update draw } close() { tput sgr0 # reset char attributes tput cnorm # normal cursor tput rmcup # restore position stty "$stty_default" # restore stty settings [ -e "$buf_path" ] && rm "$buf_path" exit "${1-0}" } motion() { [ -z "$mv" ] || [ -z "$in_new" ] && return mseq -C "$in_new"; mv= in_new= } readchar() { c=$(dd bs=1 count=1 2>/dev/null) printf '%d' "'$c" } in_read() { set -- $in_buf [ "$#" -eq 0 ] && in_key=$(readchar) && return in_key=$1; shift; in_buf=$@ } in_back() { set -- "$in_key" $in_buf in_buf=$@ } in_prefix() { n= while [ "$in_key" -le 57 ] && [ "$in_key" -ge 48 ]; do : $(( n = n * 10 + $(printf \\$(printf "%03o" "$in_key")) )) in_read done [ "$1" -eq "1" ] && in_cnt1=${n:-0} || in_cnt2=${n:-0} } in_motionln() { cnt=$(( (in_cnt1 < 1 ? 1 : in_cnt1) * (in_cnt2 < 1 ? 1 : in_cnt2) )) mv= case "$in_key" in # return + j 0|43|106) mv=".:.+$cnt"; in_new=".+$cnt" ;; # - k 45|107) mv=".-$cnt:."; in_new=".-$cnt" ;; # G 71) [ "$in_cnt1" -eq 0 ] && [ "$in_cnt2" -eq 0 ] \ && in_new="\$" \ && mv="$in_cur:$in_new" \ && return in_new="$cnt" [ "$cnt" -gt "$in_cur" ] \ && mv="$in_cur:$in_new" \ || mv="$in_new:$in_cur" ;; # P [ 80|91) in_new=$in_cur while [ $(( cnt-=1 )) -ge 0 ]; do j=$(mscan -n "$in_new=" | head -n1) mv="$j:$in_new $mv" in_new=$(( j - 1 )) done : $(( in_new+=1 )) # XXX: should [ ] move one more??? #[ "$in_cur" -eq "$in_new" ] && : $(( in_new-=1 )) #true ;; # N ] 78|93) # mv=".:$(mseq ".=" | tail -n1 | mscan -n)" in_new=$in_cur while [ $(( cnt-=1 )) -ge 0 ]; do j=$(mscan -n "$in_new=" | tail -n1) mv="$mv $in_new:$j" in_new=$(( j + 1 )) done : $(( in_new-=1 )) #[ "$in_cur" -eq "$in_new" ] && : $(( in_new+=1 )) #true ;; # { 123) in_new=$(mscan -n ".^" | head -n1); mv="$in_new:." ;; # } 125) in_new=$(mscan -n "._" | tail -n1); mv=".:$in_new" ;; *) false ;; esac } in_action() { c=$in_key in_read in_prefix 2 in_motionln || { [ "$in_key" -eq "$c" ] && mv="."; } [ -z "$mv" ] && return 1 case "$c" in # d 100) mflag -S "$mv" >/dev/null mseq -f : | mseq -S motion headers ;; # u 117) mflag -s "$mv" >/dev/null mseq -f : | mseq -S motion headers ;; esac } in_esc() { stty min 0 time 1 c=$(readchar) rv=0 case "$c" in 91) c=$(readchar) # page up/down [ "$c" -eq 53 ] && : $(( buf_row-=end_body )) [ "$c" -eq 54 ] && : $(( buf_row+=end_body )) c=$(readchar) body ;; *) c=$(readchar); c=$(readchar); rv=1 ;; esac stty min 1 time 0 return $rv } trap 'close 130;' INT TERM buf_path=$(mktemp /tmp/.mcurse_body.XXXXXX) buf_col=1 buf_row=1 buf_len= buf_end= hdr_row=0 init while :; do in_buf= in_key= in_cnt1=0 in_cnt2=0 in_cur=$(mscan -n .) in_new= printf "%s" "$term_move_cmd" in_read [ "$in_key" -eq 27 ] \ && in_esc \ && continue in_prefix 1 in_motionln \ && motion \ && update \ && draw \ && continue case "$in_key" in # d u 100|117) in_action ;; # D 68) in_key="100"; in_back; in_action ;; # input buffer to dd # U 85) in_key="117"; in_back; in_action ;; # input buffer to uu # e 101) ${EDITOR=ed} $(mseq .) && update && draw ;; # v 118) ${VISUAL=vi} $(mseq .) && update && draw ;; # L | C-l 76|12) tput clear && term_init && update && draw ;; # : 58) cmdline ;; # q 113) close ;; # c 99) hdr_row=0 && headers ;; # J 74) : $(( buf_row+=(in_cnt1 < 1 ? 1 : in_cnt1) )) && body ;; # K 75) : $(( buf_row-=(in_cnt1 < 1 ? 1 : in_cnt1) )) && body ;; esac done mblaze-0.3.2/filter.c000066400000000000000000000053071324061207500144240ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include int filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno) { char *output; ssize_t outlen; ssize_t outalloc = 4096; pid_t pid; sigset_t mask, orig_mask; int r; sigemptyset(&mask); sigaddset(&mask, SIGPIPE); sigprocmask(SIG_BLOCK, &mask, &orig_mask); outlen = 0; output = malloc(outalloc); if (!output) goto fail; int pipe0[2]; int pipe1[2]; if (pipe(pipe0) != 0 || pipe(pipe1) != 0) goto fail; int got = fcntl(pipe0[1], F_GETFL); if (got > 0) fcntl(pipe0[1], F_SETFL, got | O_NONBLOCK); char *argv[] = { "/bin/sh", "-c", cmd, (char *)0 }; if (!(pid = fork())) { dup2(pipe0[0], 0); close(pipe0[1]); close(pipe0[0]); dup2(pipe1[1], 1); close(pipe1[0]); close(pipe1[1]); execvp(argv[0], argv); exit(-1); } close(pipe0[0]); close(pipe1[1]); if (pid < 0) { close(pipe0[1]); close(pipe1[0]); goto fail; } struct pollfd fds[2]; fds[0].fd = pipe1[0]; fds[0].events = POLLIN | POLLHUP; fds[1].fd = pipe0[1]; fds[1].events = POLLOUT; while ((fds[0].fd >= 0 || fds[1].fd >= 0) && poll(fds, sizeof fds / sizeof fds[0], -1) >= 0) { if (fds[0].revents & POLLIN) { if (outlen + 512 > outalloc) { outalloc *= 2; if (outalloc < 0) exit(-1); output = realloc(output, outalloc); if (!output) exit(-1); } ssize_t ret = read(fds[0].fd, output + outlen, 512); if (ret > 0) outlen += ret; else close(fds[0].fd); } else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { fds[0].fd = -1; } if (fds[1].revents & POLLOUT) { ssize_t ret = write(fds[1].fd, input, inlen); if (ret > 0) { input += ret; inlen -= ret; } if (ret <= 0 && errno == EAGAIN) { /* ignore */ } else if (ret <= 0 || inlen == 0) close(fds[1].fd); } else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) { fds[1].fd = -1; } } // ok to fail when closed already close(pipe0[1]); close(pipe1[0]); int status; waitpid(pid, &status, 0); r = WEXITSTATUS(status); *outputo = output; *outleno = outlen; if (0) { fail: *outputo = 0; *outleno = 0; free(output); r = -1; } sigpending(&mask); if (sigismember(&mask, SIGPIPE)) { int sig; sigwait(&mask, &sig); } sigprocmask(SIG_SETMASK, &orig_mask, 0); return r; } #ifdef TEST int main() { char *input = "foo\nbar\nbaz"; int e; char *output; size_t outlen; e = filter(input, strlen(input), "rev;exit 2", &output, &outlen); fwrite(output, 1, outlen, stdout); printf("%ld -> %d\n", outlen, e); return 0; } #endif mblaze-0.3.2/filter.example000066400000000000000000000002661324061207500156340ustar00rootroot00000000000000text/plain: mflow text/html: lynx -dump -stdin -nomargins ${PIPE_CHARSET:+-assume_charset $PIPE_CHARSET} application/pdf: pdftotext - - | par application: file -b - image: file -b - mblaze-0.3.2/maddr.c000066400000000000000000000027511324061207500142260ustar00rootroot00000000000000#include #include #include #include #include #include #include "blaze822.h" static int aflag; static char defaulthflags[] = "from:sender:reply-to:to:cc:bcc:" "resent-from:resent-sender:resent-to:resent-cc:resent-bcc:"; static char *hflag = defaulthflags; void addr(char *file) { while (*file == ' ' || *file == '\t') file++; struct message *msg = blaze822(file); if (!msg) return; char *h = hflag; char *v; while (*h) { char *n = strchr(h, ':'); if (n) *n = 0; v = blaze822_chdr(msg, h); if (v) { char *disp, *addr; char vdec[16384]; blaze822_decode_rfc2047(vdec, v, sizeof vdec - 1, "UTF-8"); vdec[sizeof vdec - 1] = 0; v = vdec; while ((v = blaze822_addr(v, &disp, &addr))) { if (disp && addr && strcmp(disp, addr) == 0) disp = 0; if (disp && addr) { if (aflag) printf("%s\n", addr); else printf("%s <%s>\n", disp, addr); } else if (addr) { printf("%s\n", addr); } } } if (n) { *n = ':'; h = n + 1; } else { break; } } } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "ah:")) != -1) switch (c) { case 'a': aflag = 1; break; case 'h': hflag = optarg; break; default: fprintf(stderr, "Usage: maddr [-a] [-h headers] [msgs...]\n"); exit(1); } if (argc == optind && isatty(0)) blaze822_loop1(":", addr); else blaze822_loop(argc-optind, argv+optind, addr); return 0; } mblaze-0.3.2/magrep.c000066400000000000000000000105551324061207500144130ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "blaze822.h" static int aflag; static int cflag; static int dflag; static int iflag; static int oflag; static int pflag; static int qflag; static int vflag; static long mflag; static long matches; static regex_t pattern; static char *header; static char *curfile; int match(char *file, char *hdr, char *s) { if (oflag && !cflag && !qflag && !vflag) { regmatch_t pmatch; int len, matched; matched = 0; while (*s && regexec(&pattern, s, 1, &pmatch, 0) == 0) { s += pmatch.rm_so; if (!(len = pmatch.rm_eo-pmatch.rm_so)) { s += 1; continue; } if (pflag) printf("%s: %s: ", file, hdr); printf("%.*s\n", len, s); s += len; matched++; } return (matched && matches++); } else if (vflag ^ (regexec(&pattern, s, 0, 0, 0) == 0)) { if (qflag) exit(0); matches++; if (!cflag) { printf("%s", file); if (pflag && !vflag) printf(": %s: %s", hdr, s); putchar('\n'); } if (mflag && matches >= mflag) exit(0); return 1; } return 0; } blaze822_mime_action match_part(int depth, struct message *msg, char *body, size_t bodylen) { (void)depth; char *ct = blaze822_hdr(msg, "content-type"); blaze822_mime_action r = MIME_CONTINUE; if (!ct || strncmp(ct, "text/plain", 10) == 0) { char *charset = 0, *cs, *cse; if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) charset = strndup(cs, cse-cs); if (!charset || strcasecmp(charset, "utf-8") == 0 || strcasecmp(charset, "utf8") == 0 || strcasecmp(charset, "us-ascii") == 0) { if (pflag && !cflag && !oflag && !vflag) { char *s, *p; for (p = s = body; p < body+bodylen+1; p++) { if (*p == '\r' || *p == '\n') { *p = 0; if (p-s > 1) match(curfile, "/", s); s = p+1; } } } else if (match(curfile, "/", body)) { r = MIME_STOP; } } else { /* XXX decode here */ } free(charset); } return r; } void match_body(char *file) { char *filename; filename = curfile = file; while (*filename == ' ' || *filename == '\t') filename++; struct message *msg = blaze822_file(filename); if (!msg) return; blaze822_walk_mime(msg, 0, match_part); } void match_value(char *file, char *h, char *v) { if (dflag) { char d[4096]; blaze822_decode_rfc2047(d, v, sizeof d, "UTF-8"); match(file, h, d); } else if (aflag) { char *disp, *addr; while ((v = blaze822_addr(v, &disp, &addr))) { if (addr && match(file, h, addr)) break; } } else { match(file, h, v); } } void magrep(char *file) { if (!*header) { char *flags = strstr(file, ":2,"); if (flags) match(file, "flags", flags+3); return; } else if (strcmp(header, "/") == 0) { match_body(file); return; } char *filename = file; while (*filename == ' ' || *filename == '\t') filename++; struct message *msg = blaze822(filename); if (!msg) return; if (strcmp(header, "*") == 0) { char *hdr = 0; while ((hdr = blaze822_next_header(msg, hdr))) { char *v = strchr(hdr, ':'); if (v) { *v = 0; match_value(file, hdr, v + 1 + (v[1] == ' ')); *v = ':'; } } } else { char *v = blaze822_chdr(msg, header); if (v) match_value(file, header, v); } blaze822_free(msg); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "acdim:opqv")) != -1) switch (c) { case 'a': aflag = 1; break; case 'c': cflag = 1; break; case 'd': dflag = 1; break; case 'i': iflag = REG_ICASE; break; case 'm': mflag = atol(optarg); break; case 'o': oflag = 1; break; case 'p': pflag = 1; break; case 'q': qflag = 1; break; case 'v': vflag = 1; break; default: usage: fprintf(stderr, "Usage: magrep [-c|-o|-p|-q|-m max] [-v] [-i] [-a|-d] header:regex [msgs...]\n"); exit(2); } if (argc == optind) goto usage; header = argv[optind++]; char *rx = strchr(header, ':'); if (!rx) goto usage; *rx++ = 0; int r = regcomp(&pattern, rx, REG_EXTENDED | iflag); if (r != 0) { char buf[256]; regerror(r, &pattern, buf, sizeof buf); fprintf(stderr, "magrep: regex error: %s\n", buf); exit(2); } if (argc == optind && isatty(0)) blaze822_loop1(":", magrep); else blaze822_loop(argc-optind, argv+optind, magrep); if (cflag && !qflag && !mflag) printf("%ld\n", matches); return !matches; } mblaze-0.3.2/man/000077500000000000000000000000001324061207500135415ustar00rootroot00000000000000mblaze-0.3.2/man/maddr.1000066400000000000000000000022331324061207500147120ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MADDR 1 .Os .Sh NAME .Nm maddr .Nd extract mail addresses from messages .Sh SYNOPSIS .Nm .Op Fl a .Op Fl h Ar headers .Op Ar msgs\ ... .Sh DESCRIPTION .Nm prints, separated by newlines, all mail addresses found in the .Ar headers of the specified .Ar msgs . .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no .Ar msgs are specified and .Nm is used interactively, .Nm will operate on the current sequence by default. Otherwise, .Nm will read filenames from the standard input. .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Only print the addr-spec address, not the display name. .It Fl h Ar headers Only search the colon-separated list of .Ar headers for mail addresses. Default: .Sq Li from\&:sender\&:reply\&-to\&:to\&:cc\&:bcc\&: and their respective .Sq Li resent\&- variants, if any. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/magrep.1000066400000000000000000000042241324061207500151000ustar00rootroot00000000000000.Dd February 15, 2017 .Dt MAGREP 1 .Os .Sh NAME .Nm magrep .Nd search messages matching a pattern .Sh SYNOPSIS .Nm .Op Fl c | Fl q | Fl m Ar max .Op Fl v .Op Fl i .Op Fl a | Fl d .Ar header Ns Cm \&: Ns Ar regex .Op Ar msgs\ ... .Sh DESCRIPTION .Nm prints the names of files from the specified .Ar msgs if the value of .Ar header matches the POSIX Extended Regular Expression .Ar regex . .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If .Ar header is empty, .Nm matches against the maildir flags of .Ar msgs . .Pp If .Ar header is .Sq Cm \&* , .Nm searches for the pattern in any header. .Pp If .Ar header is .Sq Cm \&/ , .Nm searches any plain text parts of the .Ar msgs body. .Pp If no .Ar msgs are specified and .Nm is used interactively, the current sequence will be searched. .Pp The options are as follows: .Bl -tag -width Ds .It Fl a Search for .Ar regex in RFC 2822 address .Ar header parts only. .It Fl c Only print a count of matching messages. .It Fl d Decode the .Ar header according to RFC 2047 prior to searching. .It Fl i Match .Ar regex case insensitively. .It Fl m Ar max Do not show more than .Ar max matches. .It Fl o Print each match only, not the entire line. This option is ignored if .Fl c , .Fl q or .Fl v is specified. .It Fl p Print the filename, the header and the matching line for each of the matched .Ar msgs . If .Fl o is specified each match is printed, instead of the matching line. This option is ignored if .Fl c , .Fl q or .Fl v is specified. .It Fl q Quiet mode: do not print anything, quit as soon as possible. .It Fl v Invert the match; print (or count) all files where .Ar regex does not match. .El .Sh EXIT STATUS The .Nm utility exits 0 on success, 1 if no match was found and >1 if an error occurs. .Sh SEE ALSO .Xr grep 1 , .Xr mmsg 7 , .Xr regex 7 / .Xr re_format 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh TRIVIA .Nm is not called mgrep because many tools with this name already exist. .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mblaze-profile.5000066400000000000000000000034721324061207500165450ustar00rootroot00000000000000.Dd February 20, 2017 .Dt MBLAZE-PROFILE 5 .Os .Sh NAME .Nm mblaze-profile .Nd configuration of the mblaze message system .Sh DESCRIPTION The .Xr mblaze 7 message system can be configured with a .Pa profile file, which is located in the directory .Pa ~/.mblaze (or .Ev MBLAZE if set.) .Pp .Pa profile consists of lines, similar to RFC 2822 headers, in the form .Dl Ar key Ns \&: Ar value .Pp The key .Sq Cm \&# may be used to precede a comment: .Dl Li "#:" Ar comment .Pp Please note that empty lines will halt parsing. .Pp The following .Ar key Ns s are used by .Xr mblaze 7 : .Bl -tag -width Ds .It Li Local\&-Mailbox\&: Your primary mail address, used as the default value for .Li From\&: in .Xr mcom 1 , and in .Xr mscan 1 to recognize messages sent to you. .It Li Alternate\&-Mailboxes\&: A comma-separated list of mail addresses that also belong to you, for .Xr mscan 1 to recognize messages sent by or directly to you. .It Li FQDN\&: The fully qualified domain name used for .Li Message\&-Id\&: generation in .Xr mgenmid 1 . .It Li Outbox\&: If set, .Xr mcom 1 will create draft messages in this maildir, and save messages there after sending. .It Li Scan\&-Format\&: The default format string for .Xr mscan 1 . .It Li Sendmail\&: The program that .Xr mcom 1 will call to send mail. (Default: .Sq Li sendmail ) . .It Li Sendmail\&-Args\&: Flags to be passed to the .Li Sendmail\&: program. (Default: .Sq Fl t ) . .El .Sh ENVIRONMENT .Bl -tag -width Ds .It Ev MBLAZE Directory containing mblaze configuration files. (Default: .Pa $HOME/.mblaze ) .El .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm mblaze is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mblaze.7000066400000000000000000000126471324061207500151150ustar00rootroot00000000000000.Dd January 6, 2018 .Dt MBLAZE 7 .Os .Sh NAME .Nm mblaze .Nd introduction to the mblaze message system .Sh DESCRIPTION The .Nm message system is a set of Unix utilities for processing and interacting with mail messages which are stored in maildir folders. .Pp Its design is roughly inspired by MH, the RAND Message Handling System, but it is a complete implementation from scratch. .Pp .Nm consists of these Unix utilities that each do one job: .Pp .Bl -tag -width 11n -compact .It Xr maddr 1 extract mail addresses from messages .It Xr magrep 1 search messages matching a pattern .It Xr mbnc 1 bounce messages .It Xr mcom 1 compose and send messages .It Xr mdeliver 1 deliver messages or import mbox file .It Xr mdirs 1 list maildir folders, recursively .It Xr mexport 1 export messages as mbox file .It Xr mflag 1 manipulate maildir flags .It Xr mflow 1 reflow format=flowed plain text messages .It Xr mfwd 1 forward messages .It Xr mgenmid 1 generate a Message-ID .It Xr mhdr 1 print message headers .It Xr minc 1 incorporate new messages .It Xr mless 1 conveniently read messages in .Xr less 1 .It Xr mlist 1 list and filter messages .It Xr mmime 1 create MIME messages .It Xr mmkdir 1 create new maildir folders .It Xr mpick 1 advanced message filter .It Xr mrep 1 reply to messages .It Xr mscan 1 generate one-line message summaries .It Xr msed 1 manipulate message headers .It Xr mseq 1 manipulate message sequences .It Xr mshow 1 render messages and extract MIME parts .It Xr msort 1 sort messages .It Xr mthread 1 arrange messages into discussions .El .Pp .Nm is a classic command line MUA and has no features for receiving or transferring messages; you can operate on messages in a local maildir spool, or fetch your messages using .Xr fdm 1 , .Xr getmail 1 , .Xr offlineimap 1 , or similar utilities, and send it using .Xr dma 8 , .Xr msmtp 1 , .Xr sendmail 8 , as provided by OpenSMTPD, Postfix, or similar. .Pp .Nm operates directly on maildir folders and doesn't use its own caches or databases. There is no setup needed for many uses. All utilities have been written with performance in mind. Enumeration of all messages in a maildir is avoided unless necessary, and then optimized to limit syscalls. Parsing message metadata is optimized to limit I/O requests. Initial operations on a large maildir may feel slow, but as soon as they are in the file system cache, everything is blazingly fast. The utilities are written to be memory efficient .Pq i.e. not wasteful , but whole messages are assumed to fit into RAM easily .Pq one at a time . .Pp .Nm has been written from scratch and is now well tested, but it is not 100% RFC-conforming .Pq which is neither worth it, nor desirable . There may be issues with very old, nonconforming, messages. .Pp .Nm is written in portable C, using only POSIX functions .Pq apart from a tiny Linux-only optimization , and has no external dependencies. It supports MIME and more than 7-bit messages .Po everything the host .Xr iconv 3 can decode .Pc . It assumes you work in a UTF-8 environment. .Nm works well with other Unix utilities such as .Xr mairix 1 , .Xr mu 1 , or .Xr offlineimap 1 . .Sh EXAMPLES .Nm utilities are designed to be composed together in a pipe. They are suitable for interactive use and for scripting, and integrate well into a Unix workflow. .Pp For example, you could decide you want to look at all unseen messages in your INBOX, oldest first. .Dl mlist -s ~/Maildir/INBOX | msort -d | mscan .Pp To operate on a set of messages in multiple steps, you can save it as a sequence, e.g. add a call to .Ql mseq -S to the above command: .Dl mlist -s ~/Maildir/INBOX | msort -d | mseq -S | mscan .Pp Now mscan will show message numbers and you could look at the first five messages at once, for example: .Dl mshow 1:5 .Pp Likewise, you could decide to incorporate .Po by moving from .Pa new to .Pa cur .Pc all new messages in all folders, thread it and look at it interactively: .Dl mdirs ~/Maildir | xargs minc | mthread | mless .Pp Or you could list the attachments of the 20 largest messages in your INBOX: .Dl mlist ~/Maildir/INBOX | msort -S | tail -20 | mshow -t .Pp Or apply the patches from the current message: .Dl mshow -O . '*.diff' | patch .Pp As usual with pipes, the sky is the limit. .Sh CONCEPTS .Nm deals with messages .Pq which are files , folders .Pq which are maildir folders , sequences .Po which are newline-separated lists of messages, possibly saved on disk in .Pa ${MBLAZE:-$HOME/.mblaze}/seq .Pc , and the current message .Po kept as a symlink in .Pa ${MBLAZE:-$HOME/.mblaze}/cur .Pc . .Pp Messages in the saved sequence can be referred to using special syntax as explained in .Xr mmsg 7 . .Pp Many utilities have a default behavior when used interactively from a terminal .Pq e.g. operate on the current message or the current sequence . For scripting, you must make these arguments explicit. .Pp For configuration, see .Xr mblaze-profile 5 . .Sh SEE ALSO .Xr mailx 1 , .Xr mblaze-profile 5 , .Xr nmh 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Pp There is a mailing list available at .Mt mblaze@googlegroups.com .Po to subscribe, send a message to .Mt mblaze+subscribe@googlegroups.com .Pc and an IRC channel .Li #vuxu on irc.freenode.net. Please report security-related bugs directly to the author. .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mbnc.1000066400000000000000000000000201324061207500145320ustar00rootroot00000000000000.so man1/mcom.1 mblaze-0.3.2/man/mcom.1000066400000000000000000000044111324061207500145560ustar00rootroot00000000000000.Dd January 6, 2017 .Dt MCOM 1 .Os .Sh NAME .Nm mcom , .Nm mfwd , .Nm mbnc , .Nm mrep .Nd compose, reply, forward, bounce, send messages .Sh SYNOPSIS .Nm mcom .Op Ar recipients\ ... .Nm mcom .Fl r Op draft .Nm mrep .Ar msg .Nm mfwd .Op Fl r .Op Ar msgs\ ... .Nm mbnc .Ar msg .Sh DESCRIPTION .Nm mcom creates a new draft message and opens it in an editor. After editing, a loop is started where the user can send, re-edit or cancel the message. Use .Sq Nm Fl r to resume the editing of a draft; by default, the last modified draft will be edited. .Pp .Nm mrep creates the draft such that the message will be a reply to .Ar msg . .Pp .Nm mfwd creates the draft with a subject and body appropriate for forwarding the message. By default, messages are forwarded verbatim as MIME .Sq Li message/rfc822 attachments. Use .Fl r to forward as .Xr mshow 1 rendered plain text, using RFC 934 message encapsulation. .Pp .Nm mbnc creates the draft from the original .Ar msg .Pq including headers and adds a Resent-To header, to which the message will be bounced directly. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Sh MENU COMMANDS .Bl -tag -width 2n .It Ic s Send the message. The MIME version will be used when one has been created. .It Ic c Cancel sending and quit, after displaying the filename of the saved draft. .It Ic m Run .Xr mmime 1 on the draft, and print the result of .Ic mshow -t . .It Ic e Re-edit the draft. .It Ic d Delete the draft and quit. .El .Sh ENVIRONMENT .Bl -tag -width Ds .It Ev EDITOR Editor used to compose messages. .It Ev MBLAZE Directory containing mblaze configuration files. (Default: .Pa $HOME/.mblaze ) .El .Sh FILES .Bl -tag -width Ds .It Pa snd.* Draft messages, kept in current directory. This can be configured in .Xr mblaze-profile 5 . .It Pa ${MBLAZE:-$HOME/.mblaze}/headers Default headers for each message. .It Pa ${MBLAZE:-$HOME/.mblaze}/signature No or Pa ~/.signature Default signature. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmime 1 , .Xr mblaze-profile 5 , .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mdeliver.1000066400000000000000000000032001324061207500154250ustar00rootroot00000000000000.Dd July 26, 2016 .Dt MDELIVER 1 .Os .Sh NAME .Nm mdeliver .Nd deliver messages or import mbox file .Sh SYNOPSIS .Nm .Op Fl c .Op Fl v .Op Fl X Ar flags .Ar dir < .Ar message .Nm .Fl M .Op Fl c .Op Fl v .Op Fl X Ar flags .Ar dir < .Ar mbox .Sh DESCRIPTION .Nm adds a message given on the standard input as a new message in the maildir .Ar dir . .Pp When .Fl M is used, .Nm will regard the standard input as an MBOXRD mailbox, split it on .Dq Li "From " and deliver each message, decoding it according to the MBOXRD convention. .Nm will set the mtime according to the value of .Sq Li Date\&: and the maildir flags according to the value of .Sq Li Status\&: or .Sq Li X-Status\&: . .Pp The messages are delivered in a reliable way and use the default .Xr umask 2 . .Pp Please note that no syntactical checks are performed on the messages. .Pp The options are as follows: .Bl -tag -width Ds .It Fl M Deliver each message of an mbox. .It Fl c Deliver messages into .Pa cur/ , not .Pa new/ (the default). .It Fl v Print each new message filename after delivery. .It Fl X Ar flg Override the flags of the new message file to be .Ar flg . .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mexport 1 , .Xr maildir 5 , .Xr mbox 5 .Pp .Lk http://www.digitalpreservation.gov/formats/fdd/fdd000385.shtml "MBOXRD Email Format" .Pp .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mdirs.1000066400000000000000000000014511324061207500147420ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MDIRS 1 .Os .Sh NAME .Nm mdirs .Nd list maildir folders, recursively .Sh SYNOPSIS .Nm .Ar dirs\ ... .Sh DESCRIPTION .Nm recursively scans the given .Ar dirs for maildir .Pq and maildir++ folders and prints them, separated by newlines. .Pp To .Nm , a maildir folder is a directory containing the directories .Pa cur and .Pa new . By the maildir++ convention, nested maildir folder names must begin with .Sq Li \&. . .Pp There are currently no options. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr find 1 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mexport.1000066400000000000000000000023651324061207500153270ustar00rootroot00000000000000.Dd August 19, 2016 .Dt MEXPORT 1 .Os .Sh NAME .Nm mexport .Nd export messages as mbox file .Sh SYNOPSIS .Nm .Op Fl S .Ar msgs\ ... .Sh DESCRIPTION .Nm writes the specified .Ar msgs to the standard output as an MBOXRD file. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no .Ar msgs are specified, .Nm reads filenames from the standard input, or uses the messages in the current sequence if used interactively. .Pp .Nm uses the .Sq Li Return\&-Path\&: (or .Sq Li X\&-Envelope\&-To\&: ) and .Sq Li Date\&: headers from each message for the mbox .Sq Li "From " line. .Pp The options are as follows: .Bl -tag -width Ds .It Fl S Add .Sq Li Status\&: and .Sq Li X\&-Status\&: headers according to the .Ar msgs maildir flags. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mdeliver 1 , .Xr maildir 5 , .Xr mbox 5 .Pp .Lk http://www.digitalpreservation.gov/formats/fdd/fdd000385.shtml "MBOXRD Email Format" .Pp .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mflag.1000066400000000000000000000033711324061207500147150ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MFLAG 1 .Os .Sh NAME .Nm mflag .Nd manipulate maildir flags .Sh SYNOPSIS .Nm .Op Fl DFPRST .Op Fl X Ar str .br .Op Fl dfprst .Op Fl x Ar str .br .Op Fl v .Op Ar msgs\ ... .Sh DESCRIPTION .Nm changes the flags of the specified maildir .Ar msgs according to the options supplied. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp The options are as follows: .Bl -tag -width Ds .It Fl D Mark .Ar msgs as draft. .It Fl F Mark .Ar msgs as flagged. .It Fl P Mark .Ar msgs as passed .Pq resent/forwarded/bounced . .It Fl R Mark .Ar msgs as replied. .It Fl S Mark .Ar msgs as seen. .It Fl T Mark .Ar msgs as trashed. .It Fl X Ar str Mark .Ar msgs with the characters in .Ar str . .It Fl d Unmark .Ar msgs as draft. .It Fl f Unmark .Ar msgs as flagged. .It Fl p Unmark .Ar msgs as passed .Pq resent/forwarded/bounced . .It Fl r Unmark .Ar msgs as replied. .It Fl s Unmark .Ar msgs as seen. .It Fl t Unmark .Ar msgs as trashed. .It Fl x Ar str Unmark .Ar msgs with the characters in .Ar str . .Pq Remember to use uppercase characters. .It Fl v Read messages from the standard input .Pq or use the whole current sequence, if used interactively and print the transformed list of filenames. This can be used to keep the sequence intact in the case of renames. .El .Pp Note that the link to .Pa ${MBLAZE:-$HOME/.mblaze}/cur is updated in all cases, when affected. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmsg 7 .Pp .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mflow.1000066400000000000000000000024611324061207500147520ustar00rootroot00000000000000.Dd September 6, 2017 .Dt MFLOW 1 .Os .Sh NAME .Nm mflow .Nd reflow format=flowed plain text messages .Sh SYNOPSIS .Nm .Op Fl f .Op Fl q .Op Fl w Ar width \&< .Ar file .Sh DESCRIPTION .Nm reformats the standard input according to the rules of RFC 3676. .Ev PIPE_CONTENTTYPE is inspected, making this a suitable filter for .Sq text/plain messages for .Xr mshow 1 . Messages not specified as .Sq format=flowed are output unchanged. .Pp Text is reflowed (where allowed) to fit the width given in the environment variable .Ev COLUMNS , the terminal width, or 80 characters by default. .Pp If defined, the environment variable .Ev MAXCOLUMNS specifies the maximum line length. .Pp The options are as follows: .Bl -tag -width Ds .It Fl f Force wrapping of long lines. .It Fl q Prefix lines with .Sq Li \&> . Can be used multiple times. .It Fl w Ar width Set maximum line length to .Ar width . .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mshow 1 .Rs .%A R. Gellens .%D February 2004 .%R RFC 3676 .%T The Text/Plain Format and DelSp Parameters .Re .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mfwd.1000066400000000000000000000000201324061207500145500ustar00rootroot00000000000000.so man1/mcom.1 mblaze-0.3.2/man/mgenmid.1000066400000000000000000000021741324061207500152470ustar00rootroot00000000000000.Dd August 1, 2016 .Dt MGENMID 1 .Os .Sh NAME .Nm mgenmid .Nd generate a Message-ID .Sh SYNOPSIS .Nm .Sh DESCRIPTION .Nm generates and prints a unique Message-ID. The Message-ID consists of an encoded timestamp, a random value, and a fully qualified domain name. .Pp The fully qualified domain name is arrived at by: .Bl -enum .It Using .Sq Li FQDN\&: from .Pa "${MBLAZE:-$HOME/.mblaze}/profile" .Pq if set . .It Resolving the current hostname. .It Using the domain component of the mail address in .Sq Li Local\&-Mailbox\&: from .Pa "${MBLAZE:-$HOME/.mblaze}/profile" .Pq if set . .El .Pp If these steps don't result in a fully qualified domain name, .Nm fails. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mblaze-profile 5 .Rs .%A M. Curtin .%A J. Zawinski .%D July 1998 .%R draft-ietf-usefor-message-id-01.txt .%T Recommendations for generating Message IDs .Re .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mhdr.1000066400000000000000000000035701324061207500145620ustar00rootroot00000000000000.Dd August 17, 2016 .Dt MHDR 1 .Os .Sh NAME .Nm mhdr .Nd print message headers .Sh SYNOPSIS .Nm .Op Fl h Ar hdrs Op Fl p Ar parameter .Op Fl d .Op Fl H .Op Fl M .Op Fl A | Fl D .Op Ar msgs\ ... .Sh DESCRIPTION .Nm prints the headers of the specified .Ar msgs . .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no .Ar msgs are specified, .Nm will default to the current message. .Pp If no .Ar hdrs are specified, .Nm will print all headers. .Pp The options are as follows: .Bl -tag -width Ds .It Fl h Ar hdrs Only print the values of headers in the colon-separated list .Ar hdrs . .It Fl p Ar parameter Extract a particular RFC 2045 .Ar parameter from the specified .Ar hdrs . .It Fl d Decode the .Ar hdrs according to RFC 2047. .It Fl H Prefix output lines with the filename of the message, followed by a tab. .It Fl M Search for all occurrences of the .Ar hdrs .Pq default: only the first . .It Fl A Scan for RFC 5322 addresses in the .Ar hdrs and print them, one per line. .It Fl D Assume header contains RFC 5322 date and print as Unix timestamp. .El .Sh EXIT STATUS The .Nm utility exits 0 on success, 1 if no header was printed, and >1 if an error occurs. .Sh SEE ALSO .Xr mmsg 7 .Rs .%A N. Freed .%A N. Borenstein .%D November 1996 .%R RFC 2045 .%T Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies .Re .Rs .%A N. Freed .%A N. Borenstein .%B MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text .%R RFC 2047 .%D November 1996 .Re .Rs .%A P. Resnick (ed.) .%B Internet Message Format .%R RFC 5322 .%D October 2008 .Re .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/minc.1000066400000000000000000000014701324061207500145530ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MINC 1 .Os .Sh NAME .Nm minc .Nd incorporate new messages .Sh SYNOPSIS .Nm .Op Fl q .Ar dirs\ ... .Sh DESCRIPTION .Nm incorporates new messages in the maildir folders .Ar dirs by moving them from .Pa new to .Pa cur , and adjusting the filenames. .Pp By default, the new filenames are printed, separated by newlines. .Pp The options are as follows: .Bl -tag -width Ds .It Fl q Quiet: don't print new filenames. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mless.1000066400000000000000000000022011324061207500147410ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MLESS 1 .Os .Sh NAME .Nm mless .Nd interactive wrapper around mshow .Sh SYNOPSIS .Nm .Op Ar msg .Sh DESCRIPTION .Nm runs .Xr less 1 on the output of .Xr mshow 1 for the current sequence. Display starts with the current message, or .Ar msg if passed. See .Xr mmsg 7 for the message argument syntax. .Pp When no messages are passed and standard input is from a pipe, .Ql mseq -S will be called to set the current sequence and .Nm is run on the resulting sequence. The first "file" of the less instance will be the output of .Xr mscan 1 . This makes .Nm convenient for use at the end of a pipe. .Pp .Nm will start .Xr less 1 displaying the current message if possible. Use .Sq Ic ":n" and .Sq Ic ":p" to read the next (resp. previous) message. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr less 1 , .Xr mscan 1 , .Xr mseq 1 , .Xr mshow 1 , .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mlist.1000066400000000000000000000042071324061207500147560ustar00rootroot00000000000000.Dd December 13, 2016 .Dt MLIST 1 .Os .Sh NAME .Nm mlist .Nd list and filter messages .Sh SYNOPSIS .Nm .Op Fl DFPRST .Op Fl X Ar str .br .Op Fl dfprst .Op Fl x Ar str .br .Op Fl N | Fl n | Fl C | Fl c .br .Op Fl i .Op Ar dirs\ ... .Sh DESCRIPTION .Nm lists messages in the specified maildir folders .Ar dirs , separated by newlines. If used non-interactively with no specified folders, .Nm reads directory names from the standard input. .Pp The options are as follows: .Bl -tag -width Ds .It Fl D Only list messages marked as draft. .It Fl F Only list messages marked as flagged. .It Fl P Only list messages marked as passed .Pq resent/forwarded/bounced . .It Fl R Only list messages marked as replied-to. .It Fl S Only list messages marked as seen. .It Fl T Only list messages marked as trashed. .It Fl X Ar str Only list messages marked with the characters in .Ar str . .It Fl d Don't list messages marked as draft. .It Fl f Don't list messages marked as flagged. .It Fl p Don't list messages marked as passed .Pq resent/forwarded/bounced . .It Fl r Don't list messages marked as replied-to. .It Fl s Don't list messages marked as seen. .It Fl t Don't list messages marked as trashed. .It Fl x Ar str Don't list messages marked with the characters in .Ar str . .Pq Remember to use uppercase characters. .It Fl C Only list messages in .Pa cur . .It Fl N Only list messages in .Pa new . .It Fl c Don't list messages in .Pa cur . .It Fl n Don't list messages in .Pa new . .It Fl i Don't print filenames. Instead, print a one-line summary for each folder, showing the number of unseen, flagged and total messages, along with the folder name. If two or more folders are specified, a total for all folders will also be printed. .El .Pp Multiple options are regarded as a conjunction. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr find 1 .Pp .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mmime.1000066400000000000000000000042151324061207500147310ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MMIME 1 .Os .Sh NAME .Nm mmime .Nd create MIME messages .Sh SYNOPSIS .Nm .Op Fl c | Fl r .Op Fl t Ar content-type < .Ar message .Sh DESCRIPTION .Nm generates a .Sq Li multipart/mixed message from the standard input, extending, wrapping, and encoding the header as necessary, and replacing lines in the message body of the form .Pp .D1 Li # Ns Ar content Ns Li / Ns Ar type Ns Oo Ns Li # Ns Ar content-disposition Oc Pa path Ns Oo Li > Ns Ar filename Oc .Pp with a MIME part having Content-Type .Ar content/type , consisting of the contents of the file found at .Pa path . .Ar content-disposition is optional and defaults to .Sq attachment . .Ar filename is optional and defaults to the basename of .Ar path . .Pp The options are as follows: .Bl -tag -width Ds .It Fl c Check mode: don't output anything, exit with status 1 if MIME-encoding the message is required, or else exit with status 0. .It Fl r Raw mode: don't expand MIME parts in the body, generate a .Sq Li text/plain message. .It Fl t Ar content-type Override Content-Type for the toplevel part. Defaults to .Sq Li multipart/mixed . .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr makemime 1 , .Xr mhbuild 1 .Rs .%A N. Freed .%A N. Borenstein .%D November 1996 .%R RFC 2045 .%T Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies .Re .Rs .%A N. Freed .%A N. Borenstein .%D November 1996 .%R RFC 2046 .%T Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types .Re .Rs .%A K. Moore .%D November 1996 .%R RFC 2047 .%T MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text .Re .Rs .%A N. Freed .%A K. Moore .%D November 1997 .%R RFC 2231 .%T MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations .Re .Rs .%A P. Resnick (ed.) .%B Internet Message Format .%R RFC 5322 .%D October 2008 .Re .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mmkdir.1000066400000000000000000000015611324061207500151110ustar00rootroot00000000000000.Dd June 20, 2017 .Dt MMKDIR 1 .Os .Sh NAME .Nm mmkdir .Nd create new maildir folders .Sh SYNOPSIS .Nm .Ar dir\ ... .Sh DESCRIPTION .Nm creates new maildir folders. .Pp Parent directories are created as needed, and attempting to create an existing maildir is not an error. .Pp New maildir folders are created with mode 0700, i.e. readable and writable by the owner only. .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mkdir 1 .Pp .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh CAVEATS .Nm does not support maildir++, you need to transform nested maildir++ folder names yourself. .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mmsg.7000066400000000000000000000042511324061207500145760ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MMSG 7 .Os .Sh NAME .Nm mmsg .Nd mblaze message argument syntax .Sh DESCRIPTION This document outlines the message syntax used by many of the utilities in the .Xr mblaze 7 message system. .Pp In general, you can always specify a filename as a message, if it contains a .Sq Li \&/ character. .Po Use .Sq Li \&./ to prefix messages in the current directory. .Pc You can also specify a maildir folder, which will be expanded to all messages in the .Pa cur/ directory. .Pp Ranges have the format .Sq Ar start Ns Cm \&: Ns Ar stop , where .Ar start and .Ar stop are one-based indexes into the sequence. Negative numbers count from the end. If .Ar start is the empty string, .Li 1 will be used instead. If .Ar stop is the empty string, .Li \&-1 will be used instead. Thus, .Sq Cm \&: represents the whole sequence. If the range does not contain a .Sq Cm \&: , it is considered to be a single message, equivalent to the range .Sq Ar start Ns Cm \&: Ns Ar start of size one. The special notation .Sq Ar start Ns Cm \&:+ Ns Ar n , selects .Ar start and the next .Ar n messages. .Pp If the sequence is threaded, the following syntax may be used: .Sq Ar msg Ns Cm \&= refers to the whole thread that contains .Ar msg . .Sq Ar msg Ns Cm \&^ refers to the parent of the message .Ar msg and may be repeated to refer to grandparents. .Sq Ar msg Ns Cm \&_ refers to the subthread headed by .Ar msg .Po i.e. all messages below .Ar msg , with more indentation .Pc . .Pp The following special shortcuts may be used: .Bl -tag -width 3n .It Sq Li \&. refers to the current message. Additionally, the syntax .Sq Li \&.+ Ns Ar N and .Sq Li \&.- Ns Ar N can be used to refer to messages relative to the current message. .It Sq Li \&+ refers to the next message .Po like .Sq Li \&.+1 .Pc .It Sq Li \&- refers to the previous message .Po like .Sq Li \&.-1 .Pc .It Sq Li \&$ refers to the last message .Po like .Sq Li -1 .Pc .It Sq Li \&^ refers to the current parent message .Po like .Sq Li \&.^ .Pc .It Sq Li \&= refers to the current thread .Po like .Sq Li \&.= .Pc .It Sq Li \&_ refers to the current subthread .Po like .Sq Li \&._ .Pc .El .Sh SEE ALSO .Xr mblaze 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org mblaze-0.3.2/man/mpick.1000066400000000000000000000110341324061207500147250ustar00rootroot00000000000000.Dd July 27, 2016 .Dt MPICK 1 .Os .Sh NAME .Nm mpick .Nd advanced message filter .Sh SYNOPSIS .Nm .Op Fl T .Op Fl t Ar test .Op Ar msglist\ ... .Sh DESCRIPTION .Nm prints all matching messages. .Pp If used interactively, .Nm will default to the current sequence. Otherwise, .Nm will read filenames from the standard input. .Pp The options are as follows: .Bl -tag -width Ds .It Fl T Include whole thread. .It Fl t Ar test Only show messages matching the expression .Ar test , see .Sx TESTS . .El .Sh MSGLISTS .Nm message lists .Pq msglist are mostly compatible with .Xr mailx 1 . They are message specifications used as shortened .Sx TESTS , and can include: .Bl -tag -width Ds .It Ar n Message number .Ar n . .It Ar n Ns Cm \&: Ns Ar m , Ar n Ns Cm \&- Ns Ar m An inclusive range of message numbers between .Ar n and .Ar m . .It Ar address All messages from .Ar address . .It Cm \&/ Ns Ar string All messages with .Ar string in the subject line .Pq case ignored . .It Cm \&: Ns Ar c All messages of type .Ar c , where .Ar c shall be one of: .Bl -tag -width Ds .It Cm D Draft messages. .It Cm P Passed .Pq resent, forwarded or bounced messages. .It Cm R Replied messages. .It Cm F Flagged messages. .It Cm d , Cm T Deleted messages. .It Cm n New messages. .It Cm o Old messages. .It Cm r , Cm S Read messages. .It Cm u Unread messages. .El .El .Sh TESTS .Nm tests are given by the following EBNF: .Bd -literal ::= || -- disjunction | && -- conjunction | ! -- negation | ( ) | | | | | prune -- do not match further messages in thread | print -- always true value ::= child | draft | flagged | info | new | parent | passed | replied | seen | selected | trashed ::= atime | ctime | mtime | date ::= depth | kept | replies | index | size | total ::= <= | < | >= | > | == | = | != ::= "./path" -- mtime of relative path | "/path" -- mtime of absolute path | "YYYY-MM-DD HH:MM:SS" | "YYYY-MM-DD" -- at midnight | "HH:MM:SS" -- today | "HH:MM" -- today | "-[0-9]+d" -- n days ago at midnight | "-[0-9]+h" -- n hours before now | "-[0-9]+m" -- n minutes before now | "-[0-9]+s" -- n seconds before now | [0-9]+ -- absolute epoch time ::= [0-9]+ ( c -- *1 | b -- *512 | k -- *1024 | M -- *1024*1024 | G -- *1024*1024*1024 | T )? -- *1024*1024*1024*1024 | cur -- index of cur message ::= from | subject | to | -- header name ::= == | = | != -- string (in)equality | === | !=== -- case insensitive string (in)equality | ~~ | !~~ -- glob (fnmatch) | ~~~ | !~~~ -- case insensitive glob (fnmatch) | =~ | !=~ | !~ -- POSIX Extended Regular Expressions | =~~ | !=~~ -- case insensitive POSIX Extended Regular Expressions ::= " ([^"] | "")+ " -- use "" for a single " inside " | $[A-Za-z0-9_]+ -- environment variable .Ed .Sh EXIT STATUS .Ex -std .Sh EXAMPLES You can pick mails to move them into another .Dv maildir . .Pp .Dl mv $(mlist ./INBOX | mpick -t 'from =~ \&"@github\&"') ./github/cur .Pp Or you can use .Nm to pick mails from the current sequence. .Pp .Dl mpick -t 'subject =~~ \&"mblaze\&"' | mscan .Pp A more advanced .Nm expression to pick mails in a certain time span, which are flagged as replied or not seen. .Bd -literal -offset indent mpick -t 'date >= \&"2016-01-01\&" && date < \&"2017-01-01\&" && (replied || !seen)' .Ed .Pp And to find other mblaze users. .Pp .Dl mpick -t '"User-Agent" =~~ \&"mblaze\&"' | mscan .Sh SEE ALSO .Xr lr 1 , .Xr mailx 1 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .An Duncan Overbruck Aq Mt mail@duncano.de .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mrep.1000066400000000000000000000000201324061207500145560ustar00rootroot00000000000000.so man1/mcom.1 mblaze-0.3.2/man/mscan.1000066400000000000000000000077111324061207500147320ustar00rootroot00000000000000.Dd June 28, 2017 .Dt MSCAN 1 .Os .Sh NAME .Nm mscan .Nd generate one-line message summaries .Sh SYNOPSIS .Nm .Op Fl n .Op Fl f Ar format .Op Fl I .Ar msgs\ ... .Sh DESCRIPTION .Nm prints a one line summary for each message. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no .Ar msgs are specified, .Nm reads filenames from the standard input, or scans the mails in the current sequence if used interactively. .Pp By default, .Nm will spawn a pager on its output when used interactively. See .Sx ENVIRONMENT for details on how to control this behaviour. .Pp The default .Ar format is .Pp .Dl %c%u%r %-3n %10d %17f %t %2i%s .Pp that is, for each message, .Nm prints relevant flags, the sequence number .Pq if applicable , the date, the originator, and the subject of the message .Pq possibly indented . A default .Nm format may be specified in the user's .Xr mblaze-profile 5 . .Pp The options are as follows: .Bl -tag -width Ds .It Fl n Only print message numbers .Pq or filenames, if the message is not in the current sequence . .It Fl I Force ISO date output, even for .Sq Cm "%d" . .It Fl f Ar format Format according to the string .Ar format , inspired by .Xr printf 3 . .Pp The following formatting codes may be used .Po .Ar wd overrides the default width .Pc : .Bl -tag -width Ds .It Cm \en Newline. .It Cm \et Tab. .It Cm \&%% A plain .Sq Li \&% . .It Cm "%" Ns Oo Ar wd Oc Ns Cm "b" Human-readable size of the message .Pq in kilobytes . .It Cm "%c" A .Sq Li > on the current message, otherwise a blank. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "d" Adaptive date of the message. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "D" ISO date of the message .Pq year, month, day . When .Ar wd is greater or equal to 16, the hour-minute timestamp will also be shown. When .Ar wd is greater or equal to 19, seconds will also be shown. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "f" The .Sq Li From\&: .Po or .Sq Li To\&: , if the message is from us .Pc . .It Cm "%" Ns Oo Ar wd Oc Ns Cm "F" The maildir folder the message resides in. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "i" .Ar wd .Pq default: 1 spaces per indentation depth in the thread tree. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "I" The .Sq Li Message\&-ID\&: of the message. .It Cm "%M" The raw maildir flags of the message. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "n" The number of the message in the current sequence. .It Cm "%r" A .Sq Li \&- on a replied-to message, or a .Sq Li \&: on a forwarded message, or a blank. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "R" The filename of the message. .It Cm "%" Ns Oo Ar wd Oc Ns Cm "s" The subject of the message .Pq defaults to remaining width . .It Cm "%" Ns Oo Ar wd Oc Ns Cm "S" The subject of the message .Pq defaults to remaining width , with leading .Sq Li Re\&: , .Sq Li Fwd\&: etc. stripped. .It Cm "%t" A .Sq Li \&> if you are in .Sq Li To\&: , a .Sq Li \&+ if you are in .Sq Li Cc\&: , a .Sq Li \&: if you are in .Sq Li Resent\&-To\&: , or a blank. .It Cm "%u" An .Sq Li \&* on a flagged message, a .Sq Li \&. on an unseen message, an .Sq Li x on a trashed message, or a blank. .El .El .Sh MESSAGE FLAGS .Bl -tag -width 2n -compact .It Li \&> The current message .It Li \&. An unseen message .It Li x A trashed message .It Li \&* A flagged message .It Li \&- A replied-to message .El .Sh SUBJECT FLAGS .Bl -tag -width 2n -compact .It Li \&> You are in .Sq Li To\&: .It Li \&+ You are in .Sq Li Cc\:& .It Li \&: You are in .Sq Li Resent\&-To\&: .El .Sh ENVIRONMENT .Bl -tag -width MBLAZE_PAGER .It Ev MBLAZE_PAGER Any non-empty value of the environment variable .Ev MBLAZE_PAGER is used instead of the standard pagination program, specified in .Ev PAGER . When empty, or set to .Sq Ic cat , no pager is spawned. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mblaze-profile 5 , .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/msed.1000066400000000000000000000055371324061207500145650ustar00rootroot00000000000000.Dd August 1, 2016 .Dt MSED 1 .Os .Sh NAME .Nm msed .Nd manipulate message headers .Sh SYNOPSIS .Nm .Ar script .Op Ar msgs\ ... .Sh DESCRIPTION .Nm prints the messages .Ar msgs with message headers transformed by the commands in .Ar script . .Po See .Xr mmsg 7 for the message argument syntax. .Pc If no .Ar msgs are passed, .Nm will default to the current message. .Pp .Nm scripts are akin to a subset of .Xr sed 1 scripts, but optimized for modifying messages. Note that .Nm unfolds and normalizes message headers, so they may need to be passed through .Xr mmime 7 to ensure RFC 5322 conformance. The message body is not affected. .Pp .Nm supports the following commands. The separators .Em after the command letter may be substituted with an arbitrary symbol, just as in .Xr sed 1 . Multiple commands can be separated by .Sq Cm \&; . .Bl -tag -width Ds .It Cm \&/ Ns Ar header Ns Cm \&/ Ns Ic a Ns Cm \&/ Ns Ar value Ns Cm \&/ If the header .Sq Ar header Ns Cm \&: is not set in the message, add it with the given .Ar value . .It Cm \&/ Ns Ar headers Ns Cm \&/ Ns Ic c Ns Cm \&/ Ns Ar value Ns Cm \&/ Change colon-separated headers matching the regular expression .Ar headers , with implicit anchoring to the header name, to the value given in .Ar value . .It Cm \&/ Ns Ar headers Ns Cm \&/ Ns Ic d Delete colon-separated headers matching the regular expression .Ar headers , with implicit anchoring to the header name. Use explicit .Sq Li \&.* to match arbitrary strings at the beginning or end of the headers. .Pp For example, .Sq Li "/x-.*/d" will delete all headers starting with .Sq Li "X-" .Pq always case insensitive , and .Sq Li "/from:to:cc/d" will delete the headers .Sq Li From\&: , .Sq Li To\&: , and .Sq Li Cc\&: . .It Oo Cm \&/ Ns Ar headers Ns Cm \&/ Oc Ns Ic s Ns Cm \&/ Ns Ar regex Ns Cm \&/ Ns Ar replacement Ns Cm \&/ Ns Op Ar flags Substitute matches of the POSIX Basic Regular Expression .Ar regex in headers matching the POSIX Basic Regular Expression .Ar headers , with implicit anchoring to the header name .Pq or all headers, if omitted , with the string .Ar replacement , expanding .Sq Cm \&& to the matched string, and .Sq Cm \e Ns Ar N to the .Ar N Ns th sub-expression, where .Ar N is between 1 and 9. .Pp If .Ar flags contains the letter .Sq Cm d , the header is removed if .Ar regex matched. .Pp By default, only the first match is replaced, unless .Ar flags contains the letter .Sq Cm g . .Pp By default, .Ar regex is matched case sensitively, unless .Ar flags contains the letter .Sq Cm i . .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr sed 1 , .Xr mmsg 7 , .Xr regex 7 / .Xr re_format 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mseq.1000066400000000000000000000035551324061207500146000ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MSEQ 1 .Os .Sh NAME .Nm mseq .Nd manipulate message sequences .Sh SYNOPSIS .Nm .Op Fl fr .Op Fl c Ar msg .Ar msgs\ ... .Nm .Fl S .Op Fl fr < .Ar sequence .Nm .Fl A .Op Fl fr < .Ar sequence .Nm .Fl C Ar msg .Sh DESCRIPTION .Nm prints, fixes and sets the message sequence. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no .Ar msgs are passed and .Nm is used interactively, .Nm prints all messages in the sequence. .Pp With .Fl S or .Fl A , .Nm will replace .Pq resp. append the default sequence. If the standard output is not a terminal, the new sequence is also printed. .Pp The options are as follows: .Bl -tag -width Ds .It Fl c Ar msg Behave as if .Ar msg was the current message. .It Fl f Fix non-existing filenames by searching for a message with the same maildir id .Pq but different flags . .It Fl r Remove leading indentation from the filenames. .It Fl S Set the message sequence to the filenames passed on standard input. .It Fl A Like .Fl S , but append to the message sequence instead of replacing it. .It Fl C Ar msg Set the current message to .Ar msg and exit. .El .Sh ENVIRONMENT .Bl -tag -width Ds .It Ev MBLAZE Directory containing mblaze configuration. .Po Default: .Pa $HOME/.mblaze .Pc .It Ev MAILCUR Symbolic link referring to the current message. .Po Default: .Pa ${MBLAZE:-$HOME/.mblaze}/cur .Pc .It Ev MAILDOT When set to a filename, overrides the current message. .Po Prefer using .Fl c instead. .Pc .It Ev MAILSEQ File were the sequence is stored. .Po Default: .Pa ${MBLAZE:-$HOME/.mblaze}/seq .Pc .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mshow.1000066400000000000000000000072051324061207500147640ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MSHOW 1 .Os .Sh NAME .Nm mshow .Nd render messages and extract MIME parts .Sh SYNOPSIS .Nm .Op Fl h Ar headers .Op Fl A Ar mimetypes .Op Fl nqrFHLN .Op Ar msgs\ ... .Nm .Fl x Ar msg .Ar parts\ ... .Nm .Fl O Ar msg .Ar parts\ ... .Nm .Fl t .Ar msgs\ ... .Nm .Fl R .Ar msg .Sh DESCRIPTION .Nm renders the specified .Ar msgs to the standard output, by default. .Po See .Xr mmsg 7 for the message argument syntax. .Pc If used interactively and no .Ar msgs are specified, .Nm displays the current message using colorization and a pager. .Pp The options are as follows: .Bl -tag -width Ds .It Fl h Ar headers Display the headers in the colon-separated list .Ar headers , instead of the default headers .Sq Li from\&:subject\&:to\&:cc\&:date\&:reply\&-to\&: . .It Fl A Ar mimetypes Define .Sq Li "mixed/alternative" preference. .Ar mimetypes is a colon-separated list of MIME types which will be preferred, in the order given, when rendering .Sq Li "mixed/alternative" parts. If no MIME type matches, the first MIME part will be rendered. .Pp Defaults to .Sq Li "text/plain:text/html" . .It Fl n Don't update the current message link. .It Fl q Don't render the body, stop after header output. .It Fl r Don't render the body, print raw body. This may be dangerous to use on a tty. .It Fl F Don't apply filters to MIME parts. .It Fl H Don't decode the headers, print all raw headers. This may be dangerous to use on a tty. .It Fl L Don't filter the headers, print all decoded headers. .It Fl N Don't show MIME structure markers. .It Fl x Ar msg Switch to extraction mode: extract .Ar parts from the message .Ar msg into files. .Ar parts can be specified by number, filename or .Xr fnmatch 3 pattern. .It Fl O Ar msg Like .Fl x but write to standard output. This may be dangerous to use on a tty. When used together with .Fl r , the whole part is raw, that is, un-decoded and including MIME part headers. .It Fl t Switch to list mode: list all MIME parts of each .Ar msg . .It Fl R Ar msg Render the text parts from .Ar msg , suitable for use in a reply. .El .Sh FILTERS .Nm , by default, decodes all .Sq Li text/* , .Sq Li message/rfc822 and .Sq Li multipart/* parts, and re-encodes them into UTF-8 if necessary. .Pp Other filters can be specified in the file .Pa ${MBLAZE:-$HOME/.mblaze}/filter , in the format: .Pp .D1 Ar type/subtype Ns Li \&: Ar command or .D1 Ar type Ns Li \&: Ar command .Pp .Nm will then spawn a pipe to .Ar command , write the MIME part and display the output. The environment variable .Ev PIPE_CHARSET will be set to the charset declared in the MIME part, if known. .Pp Filters can communicate with .Nm using their exit status: .Bl -tag -compact -width 8n .It 0 The output is printed as plain text. .It 62 The output is printed raw, without escaping. .It 63 Behave as if the filter never ran. .It 64 The output is an RFC 5322 message that should be rendered again. .It 65 to 80 Render the .Va n Ns \&- Ns 64th part of this text/multipart part. .El All other exit statuses are regarded as errors. .Sh ENVIRONMENT .Bl -tag -width MBLAZE_NOCOLOR .It Ev MBLAZE_PAGER Any non-empty value of the environment variable .Ev MBLAZE_PAGER is used instead of the standard pagination program, specified in .Ev PAGER . When empty or set to .Sq Ic cat , no pager is spawned. .It Ev MBLAZE_NOCOLOR If non-empty, .Nm will not spawn a colorization filter. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/msort.1000066400000000000000000000024341324061207500147720ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MSORT 1 .Os .Sh NAME .Nm msort .Nd sort messages .Sh SYNOPSIS .Nm .Op Fl r .Op Fl f | Fl d | Fl s | Fl F | Fl M | Fl S | Fl U | Fl I .Op Ar msgs\ ... .Sh DESCRIPTION .Nm sorts messages according to various orders, and prints, separated by newlines, the resulting message names. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no messages are specified, .Nm will read filenames from the standard input, or use the default sequence if used interactively. .Pp The options are as follows: .Bl -tag -width Ds .It Fl r Reverse order. .It Fl f Sort by .Sq Li From\&: . .It Fl d Sort by date. .It Fl s Sort by .Sq Li Subject\&: (modulo various variants of .Sq Li Re\&: ) . .It Fl F Sort by filename, using proper order for numbers in filenames. .It Fl M Sort by message file modification time. .It Fl S Sort by message file size. .It Fl U Sort unread messages after read messages. .It Fl I Sort flagged messages before unflagged messages. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmsg 7 .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/man/mthread.1000066400000000000000000000026261324061207500152550ustar00rootroot00000000000000.Dd July 22, 2016 .Dt MTHREAD 1 .Os .Sh NAME .Nm mthread .Nd arrange messages into discussions .Sh SYNOPSIS .Nm .Op Fl v .Op Fl S Ar msg .Op Ar msgs\ ... .Sh DESCRIPTION .Nm groups messages together in parent/child relationships, based on which messages are replies to which others. .Po See .Xr mmsg 7 for the message argument syntax. .Pc .Pp If no messages are specified, .Nm will read filenames from the standard input, or use the default sequence if used interactively. .Pp .Nm prints the threaded messages separated by newlines and indented according to their depth in the message tree. Unresolved Message-IDs are printed as-is. .Pp The options are as follows: .Bl -tag -width Ds .It Fl v Do not prune unresolved Message-IDs at the top-level. .It Fl S Ar msg Treat .Ar msg as optional message(s) that will be added to threads only if they are referenced. Threads where all messages are optional are suppressed. You can use .Fl S to add an outbox folder, for example, completing threads where your replies were missing. .El .Sh EXIT STATUS .Ex -std .Sh SEE ALSO .Xr mmsg 7 .Pp .Lk https://www.jwz.org/doc/threading.html "Message threading" .Sh AUTHORS .An Leah Neukirchen Aq Mt leah@vuxu.org .Sh LICENSE .Nm is in the public domain. .Pp To the extent possible under law, the creator of this work has waived all copyright and related or neighboring rights to this work. .Pp .Lk http://creativecommons.org/publicdomain/zero/1.0/ mblaze-0.3.2/mbnc000077700000000000000000000000001324061207500145012mcomustar00rootroot00000000000000mblaze-0.3.2/mcolor000077500000000000000000000021061324061207500142060ustar00rootroot00000000000000#!/usr/bin/awk -f # mcolor - colorize rendered mail function co(n, c) { e = ENVIRON["MCOLOR_" n]; return e ? e : c } function fg(c, s) { return sprintf("\033[38;5;%03dm%s\033[0m", c, s) } function so(s) { return sprintf("\033[1m%s\033[0m", s) } BEGIN { hdr = 1; if ("NO_COLOR" in ENVIRON || match(ENVIRON["TERM"], "^(dumb|network|9term)")) no_color = 1 } no_color { print; next } /\r$/ { sub(/\r$/, "") } /^\014$/ { nextmail = 1; next } /^$/ { hdr = 0 } /^-- $/ { ftr = 1 } /^--- .* ---/ { print fg(co("SEP",242), $0); ftr = 0; sig = 0; next } /^-----BEGIN .* SIGNATURE-----/ { sig = 1 } nextmail && /^From:/ { hdr = 1 } hdr && /^From:/ { print so(fg(co("FROM",119), $0)); next } hdr { print fg(co("HEADER",120), $0); next } ftr { print fg(co("FOOTER",244), $0); next } /^-----BEGIN .* MESSAGE-----/ || /^-----END .* SIGNATURE-----/ { print fg(co("SIG",244), $0); sig = 0; next } sig { print fg(co("SIG",244), $0); next } /^> *> *>/ { print fg(co("QQQUOTE",152), $0); next } /^> *>/ { print fg(co("QQUOTE",149), $0); next } /^>/ { print fg(co("QUOTE",151), $0); next } { nextmail = 0; print } mblaze-0.3.2/mcom000077500000000000000000000144061324061207500136540ustar00rootroot00000000000000#!/bin/sh # mcom [TO] - compose mail commajoin() { awk 'NR==1 {l=$0; next} {l=l", "$0} END {print l}' } notmine() { mine="$(maddr -a -h local-mailbox:alternate-mailboxes: $MBLAZE/profile)" grep -Fv -e "$mine" } reffmt() { sed 's/^[^<]*//g;s/[^>]*$//g;s/>[^<]*\ /dev/null | sed 's/^/Message-Id: /' } stampdate() { if ! mhdr -h date "$1" >/dev/null; then tmp=$(mktemp -t mcom.XXXXXX) { printf 'Date: ' mdate cat "$1" } >"$tmp" && mv "$tmp" "$1" fi } stripempty() { tmp=$(mktemp -t mcom.XXXXXX) msed 's/^[ \t]*$//d' "$1" >"$tmp" mv "$tmp" "$1" } needs_multipart() { mhdr -h attach "$1" >/dev/null || grep -q '^#[^ ]*/[^ ]* ' "$1" } do_mime() { if needs_multipart "$draft"; then ( IFS=' ' msed '/attach/d' $draft for f in $(mhdr -M -h attach $draft); do printf '#%s %s\n' \ "$(file -Lbi $f | sed 's/ //g')" \ "$f" done ) | mmime >$draftmime else mmime -r <"$draft" >"$draftmime" fi } MBLAZE=${MBLAZE:-$HOME/.mblaze} sendmail=$(mhdr -h sendmail "$MBLAZE/profile") sendmail_args=$(mhdr -h sendmail-args "$MBLAZE/profile") sendmail="${sendmail:-sendmail} ${sendmail_args:--t}" resume= case "$0" in *mcom*) if [ "$1" = -r ]; then shift resume=1 if [ "$#" -gt 0 ]; then echo "used draft $1" draft="$1" shift fi fi ;; esac outbox=$(mhdr -h outbox "$MBLAZE/profile") if [ -z "$outbox" ]; then if [ -z "$resume" ]; then i=0 while [ -f "snd.$i" ]; do i=$((i+1)) done draft="./snd.$i" elif [ -z "$draft" ]; then draft=$(ls -1t ./snd.*[0-9] | sed 1q) fi draftmime="./snd.$i.mime" else if [ -z "$resume" ]; then draft="$(true | mdeliver -v -c -XD "$outbox")" if [ -z "$draft" ]; then printf '%s\n' "$0: failed to create draft in outbox $outbox." 1>&2 exit 1 fi elif [ -z "$draft" ]; then draft=$(mlist -D "$outbox" | msort -r -M | sed 1q) fi draftmime="$(printf '%s\n' "$draft" | sed 's,\(.*\)/cur/,\1/tmp/mime-,')" fi [ -z "$resume" ] && { case "$0" in *mcom*) printf 'To: ' printf '%s\n' "$@" | commajoin printf '%s: \n' Cc Bcc Subject from=$(mhdr -h local-mailbox "$MBLAZE/profile") [ "$from" ] && printf 'From: %s\n' "$from" cat "$MBLAZE/headers" 2>/dev/null msgid museragent printf '\n\n' ;; *mfwd*) raw= [ "$1" = -r ] && raw=1 && shift [ "$#" -eq 0 ] && set -- . printf '%s: \n' To Cc Bcc COLUMNS=10000 mscan -f 'Subject: [%f] %s' "$@" | sed 1q from=$(mhdr -h local-mailbox "$MBLAZE/profile") [ "$from" ] && printf 'From: %s\n' "$from" cat "$MBLAZE/headers" 2>/dev/null msgid museragent printf '\n\n' if [ -z "$raw" ]; then mseq -r "$@" | sed 's:^:#message/rfc822#inline :; s:$:>:' else ( SEP=----- IFS=' ' for f in $(mseq -r "$@"); do printf '%s Forwarded message from %s %s\n\n' \ $SEP "$(mhdr -d -h from "$f")" $SEP DISPLAY= mshow -n -N "$f" /dev/null mid=$(mhdr -h message-id "$1") if [ "$mid" ]; then printf 'References:' { mhdr -h references "$1" printf '%s\n' "$mid" } | reffmt printf 'In-Reply-To: %s\n' "$mid" fi msgid museragent printf '\n' mquote "$1" printf '\n' ;; esac case "$0" in *mbnc*) ;; *) if [ -f "$MBLAZE/signature" ]; then SIGNATURE="$MBLAZE/signature" elif [ -f ~/.signature ]; then SIGNATURE="$HOME/.signature" fi if [ -n "$SIGNATURE" ]; then printf '%s\n' '-- ' cat "$SIGNATURE" fi esac } >$draft automime= c=e while :; do case "$c" in s|send) case "$(mhdr -h newsgroups "$draft")" in *gmane.*) sendmail="mblow -s news.gmane.org";; *.*) sendmail="mblow";; esac resent="$(maddr -h resent-to "$draft")" case "$resent" in ?*) sendmail=$(mhdr -h sendmail "$MBLAZE/profile") sendmail="${sendmail:-sendmail} -- $resent" ;; esac if [ -e $draftmime ]; then if [ $draft -ot $draftmime ] || [ "$automime" -eq 1 ]; then stampdate $draftmime if $sendmail <$draftmime; then if [ "$outbox" ]; then mv $draftmime $draft mflag -d $draft else rm $draft $draftmime fi exit 0 else printf '%s\n' "mcom: $sendmail failed, kept draft $draft" exit 2 fi else printf 'mcom: re-run mmime first.\n' c= fi else if mmime -c <$draft; then stampdate $draft if $sendmail <$draft; then if [ "$outbox" ]; then mflag -d $draft else rm $draft fi exit 0 else printf '%s\n' "mcom: $sendmail failed, kept draft $draft" exit 2 fi else printf '%s\n' "mcom: message needs to be MIME-encoded first." c= fi fi ;; c|cancel) stampdate $draft printf '%s\n' "mcom: cancelled draft $draft" exit 1 ;; m|mime) do_mime mshow -t $draftmime c= ;; e|edit) c= if ! ${EDITOR:-vi} $draft; then c=c fi stripempty $draft if mmime -c <$draft; then automime= else automime=1 do_mime fi ;; d|delete) rm -i $draft if ! [ -f $draft ]; then rm -f $draftmime printf '%s\n' "mcom: deleted draft $draft" exit 0 fi c= ;; sign) msign $draft >$draftmime mshow -t $draftmime c= ;; encrypt) mencrypt $draft >$draftmime mshow -t $draftmime c= ;; show) if [ -e $draftmime ]; then mshow "$draftmime" else mshow "$draft" fi c= ;; *) printf 'What now? (%s[s]end, [c]ancel, [d]elete, [e]dit, [m]ime, sign, encrypt) ' "${automime:+mime and }" read -r c ;; esac done mblaze-0.3.2/mdate.c000066400000000000000000000003471324061207500142300ustar00rootroot00000000000000#include #include int main() { char buf[64]; time_t now = time(0); ssize_t l = strftime(buf, sizeof buf, "%a, %d %b %Y %T %z\n", localtime(&now)); if (write(1, buf, l) == l) return 0; return 1; } mblaze-0.3.2/mdeliver.c000066400000000000000000000103441324061207500147430ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" /* design rationale: - only MBOX-RD because it's the only reasonable lossless encoding, and decodes MBOX-O fine - date from Date: since From lines are usually crap - proper maildir delivery because it's not that hard - no creation of maildirs, should be a separate tool */ static int cflag; static int Mflag; static int vflag; static char *Xflag; char *targetdir; long delivery; char host[64]; void gethost() { gethostname(host, sizeof host); // termination not posix guaranteed host[sizeof host - 1] = 0; // replace / and : with - char *s; for (s = host; *s; s++) if (*s == '/' || *s == ':') *s = '-'; } int deliver(FILE *infile) { int outfd; FILE *outfile; char dst[PATH_MAX]; char tmp[PATH_MAX]; char id[128]; struct timeval tv; char *line = 0; size_t linelen = 0; if (Mflag) { // skip to first "From " line while (1) { errno = 0; ssize_t rd = getdelim(&line, &linelen, '\n', infile); if (rd == -1) { if (errno == 0) // invalid mbox file errno = EINVAL; return -1; } if (strncmp("From ", line, 5) == 0) break; } } while (!feof(infile)) { delivery++; tryagain: gettimeofday(&tv, 0); snprintf(id, sizeof id, "%ld.M%06ldP%ldQ%ld.%s", (long)tv.tv_sec, (long)tv.tv_usec, (long)getpid(), delivery, host); snprintf(tmp, sizeof tmp, "%s/tmp/%s", targetdir, id); outfd = open(tmp, O_CREAT | O_WRONLY | O_EXCL, 0666); if (outfd < 0) { if (errno == EEXIST) goto tryagain; return -1; } outfile = fdopen(outfd, "w"); char statusflags[5] = { 0 }; int in_header = 1; int is_old = 0; while (1) { errno = 0; ssize_t rd = getdelim(&line, &linelen, '\n', infile); if (rd == -1) { if (errno != 0) return -1; break; } char *line_start = line; if (line[0] == '\n' && !line[1]) in_header = 0; if (Mflag && strncmp("From ", line, 5) == 0) break; if (Mflag && in_header && (strncasecmp("status:", line, 6) == 0 || strncasecmp("x-status:", line, 8) == 0)) { char *v = strchr(line, ':'); if (strchr(v, 'F')) statusflags[0] = 'F'; if (strchr(v, 'A')) statusflags[1] = 'R'; if (strchr(v, 'R')) statusflags[2] = 'S'; if (strchr(v, 'D')) statusflags[3] = 'T'; if (strchr(v, 'O')) is_old = 1; continue; // drop header } if (Mflag) { // MBOXRD: strip first > from >>..>>From char *s = line; while (*s == '>') s++; if (strncmp("From ", s, 5) == 0) { line_start++; rd--; } } if (fwrite(line_start, 1, rd, outfile) != (size_t)rd) return -1; } if (fflush(outfile) == EOF) return -1; if (fsync(outfd) < 0) return -1; if (fclose(outfile) == EOF) return -1; // compress flags int i, j; for (i = sizeof statusflags - 1; i >= 0; i--) if (!statusflags[i]) for (j = i+1; j < (int)sizeof statusflags; j++) statusflags[j-1] = statusflags[j]; if (Mflag) { struct message *msg = blaze822_file(tmp); time_t date = -1; char *v; if (msg && (v = blaze822_hdr(msg, "date"))) { date = blaze822_date(v); if (date != -1) { const struct timeval times[2] = { { tv.tv_sec, tv.tv_usec }, { date, 0 } }; utimes(tmp, times); } } } snprintf(dst, sizeof dst, "%s/%s/%s:2,%s", targetdir, (cflag || is_old) ? "cur" : "new", id, Xflag ? Xflag : statusflags); if (rename(tmp, dst) != 0) return -1; if (vflag) printf("%s\n", dst); } return 0; } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "cMvX:")) != -1) switch (c) { case 'c': cflag = 1; break; case 'M': Mflag = 1; break; case 'v': vflag = 1; break; case 'X': Xflag = optarg; break; default: fprintf(stderr, "Usage: mdeliver [-c] [-v] [-X flags] dir < message\n" " mdeliver -M [-c] [-v] [-X flags] dir < mbox\n" ); exit(1); } if (argc != optind+1) { fprintf(stderr, "usage: mdeliver DIR\n"); return 1; } targetdir = argv[optind]; gethost(); if (deliver(stdin) < 0) { perror("mdeliver"); return 2; } return 0; } mblaze-0.3.2/mdirs.c000066400000000000000000000024551324061207500142560ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "blaze822.h" void pwd() { char cwd[PATH_MAX]; if (getcwd(cwd, sizeof cwd)) puts(cwd); } void mdirs(char *fpath) { DIR *dir; struct dirent *d; struct stat st; dir = opendir(fpath); if (!dir) return; if (chdir(fpath) < 0) { closedir(dir); return; } int dotonly = 0; if (stat("cur", &st) == 0 && S_ISDIR(st.st_mode) && stat("new", &st) == 0 && S_ISDIR(st.st_mode)) { pwd(); dotonly = 1; // Maildir++ } while ((d = readdir(dir))) { #if defined(DT_DIR) && defined(DT_UNKNOWN) if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN) continue; #endif if (d->d_name[0] == '.' && d->d_name[1] == 0) continue; if (d->d_name[0] == '.' && d->d_name[1] == '.' && d->d_name[2] == 0) continue; if (dotonly && d->d_name[0] != '.') continue; mdirs(d->d_name); } if (chdir("..") < 0) exit(-1); closedir(dir); } int main(int argc, char *argv[]) { int c, i; while ((c = getopt(argc, argv, "")) != -1) switch (c) { default: usage: fprintf(stderr, "Usage: mdirs dirs...\n"); exit(1); } if (argc == optind) goto usage; for (i = 0; i < argc; i++) mdirs(argv[i]); return 0; } mblaze-0.3.2/mexport.c000066400000000000000000000053551324061207500146400ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "blaze822.h" static int Sflag; static int status; void export(char *file) { struct message *msg; while (*file == ' ' || *file == '\t') file++; FILE *infile = fopen(file, "r"); if (!infile) { status = 1; fprintf(stderr, "mexport: error opening '%s': %s\n", file, strerror(errno)); return; } char from[1024] = "nobody"; time_t date = -1; if (fseek(infile, 0L, SEEK_SET) && errno == ESPIPE) { date = time(0); memcpy(from, "stdin", 6); } else { msg = blaze822(file); if (!msg) return; char *v; if ((v = blaze822_hdr(msg, "return-path")) || (v = blaze822_hdr(msg, "x-envelope-from"))) { char *s = strchr(v, '<'); if (s) { char *e = strchr(s, '>'); if (e) { s++; snprintf(from, sizeof from, "%.*s", (int)(e-s), s); } } else { // return-path without <> snprintf(from, sizeof from, "%s", v); } } if ((v = blaze822_hdr(msg, "date"))) { date = blaze822_date(v); } blaze822_free(msg); } char *line = 0; size_t linelen = 0; printf("From %s %s", from, ctime(&date)); int in_header = 1; int final_nl = 0; while (1) { errno = 0; ssize_t rd = getdelim(&line, &linelen, '\n', infile); if (rd == -1) { if (errno == 0) break; fprintf(stderr, "mexport: error reading '%s': %s\n", file, strerror(errno)); status = 1; return; } if (in_header && line[0] == '\n' && !line[1]) { if (Sflag) { char *flags = strstr(file, ":2,"); if (!flags) flags = ""; fputs("Status: ", stdout); if (strchr(flags, 'S')) putchar('R'); char *ee = strrchr(file, '/'); if (!ee || !(ee >= file + 3 && ee[-3] == 'n' && ee[-2] == 'e' && ee[-1] == 'w')) putchar('O'); putchar('\n'); fputs("X-Status: ", stdout); if (strchr(flags, 'R')) putchar('A'); if (strchr(flags, 'T')) putchar('D'); if (strchr(flags, 'F')) putchar('F'); putchar('\n'); } in_header = 0; } // MBOXRD: add first > to >>..>>From char *s = line; while (*s == '>') s++; if (strncmp("From ", s, 5) == 0) putchar('>'); fputs(line, stdout); final_nl = (line[rd-1] == '\n'); } // ensure trailing newline if (!final_nl) putchar('\n'); fclose(infile); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "S")) != -1) switch (c) { case 'S': Sflag = 1; break; default: fprintf(stderr, "Usage: mexport [-S] [msgs...]\n"); exit(2); } status = 0; if (argc == optind && isatty(0)) blaze822_loop1(":", export); else blaze822_loop(argc-optind, argv+optind, export); return status; } mblaze-0.3.2/mflag.c000066400000000000000000000060421324061207500142220ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" #define uc(c) ((c) & 0xdf) static int8_t flags[255]; static int vflag = 0; char **args; ssize_t argsalloc = 256; int idx = 0; char *curfile; void add(char *file) { if (idx >= argsalloc) { argsalloc *= 2; if (argsalloc < 0) exit(-1); args = realloc(args, sizeof (char *) * argsalloc); } if (!args) exit(-1); while (*file == ' ' || *file == '\t') file++; args[idx] = strdup(file); idx++; } void flag(char *file) { int indent = 0; while (file[indent] == ' ' || file[indent] == '\t') indent++; char *f = strstr(file, ":2,"); if (!f) goto skip; if (args) { int i; for (i = 0; i < idx; i++) if (strcmp(file+indent, args[i]) == 0) goto doit; goto skip; } doit: ; int8_t myflags[255] = { 0 }; char *s; for (s = f+3; *s; s++) myflags[(unsigned int)*s] = 1; int changed = 0; unsigned int i; for (i = 0; i < sizeof myflags; i++) { int z = myflags[i]; myflags[i] += flags[i]; if ((z <= 0 && myflags[i] > 0) || (z > 0 && myflags[i] <= 0)) changed = 1; } if (changed) { char dst[PATH_MAX]; char *s = file; char *t = dst; while (s < f+3 && t < dst + sizeof dst - 1) *t++ = *s++; for (i = 0; i < sizeof myflags && t < dst + sizeof dst - 1; i++) if (myflags[i] > 0) *t++ = i; *t = 0; if (rename(file+indent, dst+indent) < 0) { fprintf(stderr, "mflag: can't rename '%s' to '%s': %s\n", file+indent, dst+indent, strerror(errno)); goto skip; } if (curfile && strcmp(file+indent, curfile) == 0) blaze822_seq_setcur(dst+indent); printf("%s\n", dst); return; } skip: if (vflag) printf("%s\n", file); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "PRSTDFprstdfX:x:v")) != -1) switch (c) { case 'P': case 'R': case 'S': case 'T': case 'D': case 'F': flags[(unsigned int)c] = 1; break; case 'p': case 'r': case 's': case 't': case 'd': case 'f': flags[(unsigned int)uc(c)] = -1; break; case 'X': while (*optarg) flags[(unsigned int)*optarg++] = 1; break; case 'x': while (*optarg) flags[(unsigned int)*optarg++] = -1; break; case 'v': vflag = 1; break; default: fprintf(stderr, "Usage: mflag [-DFPRST] [-X str]\n" " [-dfprst] [-x str]\n" " [-v] [msgs...]\n" ); exit(1); } curfile = blaze822_seq_cur(); if (vflag) { if (argc == optind && !isatty(0)) { blaze822_loop(0, 0, flag); // read from stdin return 0; } args = calloc(sizeof (char *), argsalloc); if (!args) exit(-1); if (argc == optind) blaze822_loop1(".", add); else blaze822_loop(argc-optind, argv+optind, add); if (isatty(0)) blaze822_loop1(":", flag); else blaze822_loop(0, 0, flag); return 0; } if (argc == optind && isatty(0)) blaze822_loop1(".", flag); else blaze822_loop(argc-optind, argv+optind, flag); return 0; } mblaze-0.3.2/mflow.c000066400000000000000000000073231324061207500142630ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "blaze822.h" int column = 0; int maxcolumn = 80; void chgquote(int quotes) { static int oquotes; if (quotes != oquotes) { if (column) putchar('\n'); column = 0; oquotes = quotes; } } void fixed(int quotes, char *line, size_t linelen) { chgquote(quotes); if (column && linelen > (size_t)(maxcolumn - column)) { putchar('\n'); column = 0; } if (column == 0) { for (; column < quotes; column++) putchar('>'); if (quotes) putchar(' '); } fwrite(line, 1, linelen, stdout); putchar('\n'); column = 0; } void flowed(int quotes, char *line, ssize_t linelen) { chgquote(quotes); int done = 0; while (!done) { if (column == 0) { for (; column < quotes; column++) putchar('>'); column++; if (quotes) putchar(' '); } char *eow; if (*line == ' ') eow = memchr(line + 1, ' ', linelen - 1); else eow = memchr(line, ' ', linelen); if (!eow) { eow = line + linelen; done = 1; } if (column + (eow - line) > maxcolumn && eow - line < maxcolumn && column - quotes > 1) { putchar('\n'); column = 0; done = 0; if (*line == ' ') { line++; linelen--; } } else { fwrite(line, 1, eow - line, stdout); column += eow - line; linelen -= eow - line; line = eow; } } } int main(int argc, char *argv[]) { char *linebuf = 0; char *line; size_t linelen = 0; int outer_quotes = 0; int quotes; int reflow = 1; // re-evaluated on $PIPE_CONTENTTYPE int force = 0; int delsp = 0; char *ct = getenv("PIPE_CONTENTTYPE"); if (ct) { char *s, *se; reflow = 0; if (blaze822_mime_parameter(ct, "format", &s, &se) && s) reflow = (strncasecmp(s, "flowed", 6) == 0); if (blaze822_mime_parameter(ct, "delsp", &s, &se) && s) delsp = (strncasecmp(s, "yes", 3) == 0); } char *cols = getenv("COLUMNS"); if (cols && isdigit(*cols)) { maxcolumn = atoi(cols); } else { struct winsize w; int fd = open("/dev/tty", O_RDONLY | O_NOCTTY); if (fd >= 0) { if (ioctl(fd, TIOCGWINSZ, &w) == 0) maxcolumn = w.ws_col; close(fd); } } char *maxcols = getenv("MAXCOLUMNS"); if (maxcols && isdigit(*maxcols)) { int m = atoi(maxcols); if (maxcolumn > m) maxcolumn = m; } int c; while ((c = getopt(argc, argv, "fqw:")) != -1) switch (c) { case 'f': force = 1; break; case 'q': outer_quotes++; break; case 'w': maxcolumn = atoi(optarg); break; default: fprintf(stderr, "Usage: mflow [-f] [-q] [-w MAXCOLUMNS]\n"); exit(2); } while (1) { errno = 0; ssize_t rd = getdelim(&linebuf, &linelen, '\n', stdin); if (rd == -1) { if (errno == 0) break; fprintf(stderr, "mflow: error reading: %s\n", strerror(errno)); exit(1); } line = linebuf; if (!reflow && !force) { fwrite(line, 1, rd, stdout); continue; } if (rd > 0 && line[rd-1] == '\n') line[--rd] = 0; if (rd > 0 && line[rd-1] == '\r') line[--rd] = 0; quotes = outer_quotes; while (*line == '>') { // measure quote depth line++; quotes++; rd--; } if (reflow && *line == ' ') { // space stuffing line++; rd--; } if (strcmp(line, "-- ") == 0) { // usenet signature convention if (column) fixed(quotes, "", 0); // flush paragraph fixed(quotes, line, rd); continue; } if (reflow && rd > 0 && line[rd-1] == ' ') { // flowed line if (delsp) line[--rd] = 0; flowed(quotes, line, rd); } else { if (force && rd > maxcolumn) { flowed(quotes, line, rd); fixed(quotes, "", 0); } else { fixed(quotes, line, rd); } } } if (reflow && column != 0) putchar('\n'); } mblaze-0.3.2/mgenmid.c000066400000000000000000000050271324061207500145560ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" void printb36(uint64_t x) { static char const base36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char outbuf[16]; char *o = outbuf + sizeof outbuf; *--o = 0; do { *--o = base36[x % 36]; } while (x /= 36); fputs(o, stdout); } int main() { char hostbuf[1024]; char *host = 0; char *f = blaze822_home_file("profile"); struct message *config = blaze822(f); if (config) // try FQDN: first host = blaze822_hdr(config, "fqdn"); if (!host && gethostname(hostbuf, sizeof hostbuf) == 0) { // termination not posix guaranteed hostbuf[sizeof hostbuf - 1] = 0; struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME }; struct addrinfo *info; if (getaddrinfo(hostbuf, 0, &hints, &info) == 0) { // sanity checks: no (null), at least one dot, // doesn't start with localhost. if (info && info->ai_canonname && strchr(info->ai_canonname, '.') && strncmp(info->ai_canonname, "localhost.", 10) != 0) host = info->ai_canonname; } } if (!host && config) { // get address part of Local-Mailbox: char *disp, *addr; char *from = blaze822_hdr(config, "local-mailbox"); while (from && (from = blaze822_addr(from, &disp, &addr))) if (addr) { host = strchr(addr, '@'); if (host) { host++; break; } } } if (!host) { fprintf(stderr, "mgenmid: failed to find a FQDN for the Message-ID.\n" " Define 'FQDN:' or 'Local-Mailbox:' in" " ${MBLAZE:-$HOME/.mblaze}/profile\n" " or add a FQDN to /etc/hosts.\n"); exit(1); } struct timeval tp; gettimeofday(&tp, (struct timezone *)0); uint64_t rnd; int rndfd = open("/dev/urandom", O_RDONLY); if (rndfd >= 0) { unsigned char rndb[8]; if (read(rndfd, rndb, sizeof rndb) != sizeof rndb) goto fallback; close(rndfd); int i; for (i = 0, rnd = 0; i < 8; i++) rnd = rnd*256 + rndb[i]; } else { fallback: srand48(tp.tv_sec ^ tp.tv_usec ^ getpid()); rnd = ((uint64_t)lrand48() << 32) + lrand48(); } rnd |= (1ULL << 63); // set highest bit to force full width putchar('<'); printb36(((uint64_t)tp.tv_sec * 1000000LL + tp.tv_usec)); putchar('.'); printb36(rnd); putchar('@'); fputs(host, stdout); putchar('>'); putchar('\n'); return 0; } mblaze-0.3.2/mhdr.c000066400000000000000000000065771324061207500141030ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "blaze822.h" static char *hflag; static char *pflag; static int Aflag; static int Dflag; static int Hflag; static int Mflag; static int dflag; static char *curfile; static int status; static void printhdr(char *hdr) { int uc = 1; if (Hflag) printf("%s\t", curfile); while (*hdr && *hdr != ':') { putc(uc ? toupper(*hdr) : *hdr, stdout); uc = (*hdr == '-'); hdr++; } fputs(hdr, stdout); fputc('\n', stdout); status = 0; } void headerall(struct message *msg) { char *h = 0; while ((h = blaze822_next_header(msg, h))) { if (dflag) { char d[4096]; blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8"); printhdr(d); } else { printhdr(h); } } blaze822_free(msg); } void print_addresses(char *s) { char *disp, *addr; char sdec[4096]; if (dflag) { blaze822_decode_rfc2047(sdec, s, sizeof sdec, "UTF-8"); sdec[sizeof sdec - 1] = 0; s = sdec; } while ((s = blaze822_addr(s, &disp, &addr))) { if (Hflag && addr) printf("%s\t", curfile); if (disp && addr) printf("%s <%s>\n", disp, addr); else if (addr) printf("%s\n", addr); } } void print_date(char *s) { time_t t = blaze822_date(s); if (t == -1) return; printf("%ld\n", (long)t); } void print_decode_header(char *s) { char d[4096]; blaze822_decode_rfc2047(d, s, sizeof d, "UTF-8"); printf("%s\n", d); } void print_header(char *v) { if (pflag) { char *s, *se; if (blaze822_mime_parameter(v, pflag, &s, &se)) { *se = 0; v = s; } else { return; } } status = 0; if (Hflag && !Aflag) printf("%s\t", curfile); if (Aflag) print_addresses(v); else if (Dflag) print_date(v); else if (dflag) print_decode_header(v); else printf("%s\n", v); } void headermany(struct message *msg) { char *hdr = 0; while ((hdr = blaze822_next_header(msg, hdr))) { char *h = hflag; while (*h) { char *n = strchr(h, ':'); if (n) *n = 0; size_t l = strlen(h); if (strncmp(hdr, h, l) == 0 && hdr[l] == ':') { hdr += l + 1; while (*hdr == ' ' || *hdr == '\t') hdr++; print_header(hdr); } if (n) { *n = ':'; h = n + 1; } else { break; } } } blaze822_free(msg); } void header(char *file) { struct message *msg; while (*file == ' ' || *file == '\t') file++; curfile = file; msg = blaze822(file); if (!msg) return; if (!hflag) { headerall(msg); return; } if (Mflag) { headermany(msg); return; } char *h = hflag; while (*h) { char *n = strchr(h, ':'); if (n) *n = 0; char *v = blaze822_chdr(msg, h); if (v) print_header(v); if (n) { *n = ':'; h = n + 1; } else { break; } } blaze822_free(msg); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "h:p:ADHMd")) != -1) switch (c) { case 'h': hflag = optarg; break; case 'p': pflag = optarg; break; case 'A': Aflag = 1; break; case 'D': Dflag = 1; break; case 'H': Hflag = 1; break; case 'M': Mflag = 1; break; case 'd': dflag = 1; break; default: fprintf(stderr, "Usage: mhdr [-h header [-p parameter]] [-d] [-H] [-M] [-A|-D] [msgs...]\n"); exit(2); } status = 1; if (argc == optind && isatty(0)) blaze822_loop1(".", header); else blaze822_loop(argc-optind, argv+optind, header); return status; } mblaze-0.3.2/minc.c000066400000000000000000000027101324061207500140600ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "blaze822.h" static int qflag; static int status; void inc(char *dir) { DIR *fd; struct dirent *d; char src[PATH_MAX]; char dst[PATH_MAX]; squeeze_slash(dir); snprintf(src, sizeof src, "%s/new", dir); fd = opendir(src); if (!fd) { fprintf(stderr, "minc: can't open maildir '%s': %s\n", src, strerror(errno)); status = 2; return; } while ((d = readdir(fd))) { #if defined(DT_REG) && defined(DT_UNKNOWN) if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN) continue; #endif if (d->d_name[0] == '.') continue; snprintf(src, sizeof src, "%s/new/%s", dir, d->d_name); snprintf(dst, sizeof dst, "%s/cur/%s%s", dir, d->d_name, strstr(d->d_name, ":2,") ? "" : ":2,"); if (rename(src, dst) < 0) { fprintf(stderr, "minc: can't rename '%s' to '%s': %s\n", src, dst, strerror(errno)); status = 3; continue; } if (!qflag) printf("%s\n", dst); } closedir(fd); } int main(int argc, char *argv[]) { int c, i; while ((c = getopt(argc, argv, "q")) != -1) switch (c) { case 'q': qflag = 1; break; default: usage: fprintf(stderr, "Usage: minc [-q] dirs...\n"); exit(1); } if (optind == argc) goto usage; status = 0; for (i = optind; i < argc; i++) inc(argv[i]); return status; } mblaze-0.3.2/mless000077500000000000000000000037141324061207500140440ustar00rootroot00000000000000#!/bin/sh # mless [MSG] - less(1)-wrapper around mshow colorscan() { awk ' function co(n, c) { e = ENVIRON["MCOLOR_" n]; return e ? e : c } function fg(c, s) { return sprintf("\033[38;5;%03dm%s\033[0m", c, s) } function so(s) { return sprintf("\033[1m%s\033[0m", s) } /^>/ { print so(fg(co("CUR",119), $0)); next } /^ *\\_/ { print fg(co("MISS",242), $0); next } { print }' } if [ -n "${NO_COLOR+set}" ]; then colorscan() { cat -; } fi if [ "$1" = --filter ]; then if [ "$2" = //scan ]; then mscan : 2>/dev/null | colorscan exit $? fi mseq -C "$2" mscan .-2:.+3 2>/dev/null | colorscan echo if ! [ -f "$(mseq -r "$2")" ]; then mseq "$2" exit fi if [ $MLESS_RAW -eq 0 ]; then if [ $MLESS_HTML -eq 1 ]; then mshow -A text/html "$2" else mshow "$2" fi | mcolor else mseq -r $2 echo cat "$(mseq -r $2)" fi exit $? fi if [ "$#" -eq 0 ] && ! [ -t 0 ]; then mseq -S >/dev/null set -- : fi if ! [ -t 1 ]; then exec mseq : fi case "$0" in *mnext) set -- +;; *mprev) set -- -;; *) [ "$#" -eq 1 ] && set -- ${1:-.};; esac if [ "$#" -ge 1 ]; then mseq -C "$1" fi nl=" " export MLESS_RAW=0 export MLESS_HTML=0 while :; do if [ -f $MBLAZE/mless ]; then export LESSKEY=$MBLAZE/mless elif [ -f $HOME/.mblaze/mless ]; then export LESSKEY=$HOME/.mblaze/mless elif [ -f $HOME/.mless ]; then export LESSKEY=$HOME/.mless fi LESSOPEN="|$0 --filter %s" \ less -Ps"mless %f?m (message %i of %m).." -R \ "+:e $(mscan -n .)$nl" //scan $(mscan -n :) case "$?" in 0|1) exit $?;; 36) # $ goto end mseq -C '$' 2>/dev/null ;; 78) # N go to next unseen message nu=$(magrep -v -m1 :S .:) && mseq -C "$nu" ;; 107) # k next thread mseq -C "$(mseq .+1: | sed -n '/^[^ <]/{p;q;}')" ;; 100) # d mark read mflag -S . mseq -f : | mseq -S mseq -C + ;; 82) # R toggle raw mode MLESS_RAW=$((1-$MLESS_RAW)) ;; 72) # H toggle HTML mode MLESS_HTML=$((1-$MLESS_HTML)) ;; 94) # ^ goto parent mseq -C '.^' 2>/dev/null ;; esac done mblaze-0.3.2/mlesskey.example000066400000000000000000000003621324061207500162000ustar00rootroot00000000000000# mless(1) keybindings # to update: lesskey -o ~/.mless ~/.mlesskey Q quit \1 :cq quit \1 [ prev-file ] next-file { noaction E1\n } quit $ $ quit $ S noaction E//scan\n ` noaction E\#\n H quit H N quit N R quit R k quit k d quit d \^ quit \^ mblaze-0.3.2/mlist.c000066400000000000000000000115201324061207500142610ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "blaze822.h" #define lc(c) ((c) | 0x20) #define uc(c) ((c) & 0xdf) /* PRSTDF prstdf -N new -n not new -C cur -c not cur -m age -r recursive? */ static int8_t flags[255]; static int flagsum; static int flagset; static int Nflag; static int Cflag; static int iflag; static long icount; static long iunseen; static long iflagged; static long imatched; static long tdirs; static long tunseen; static long tflagged; static long tcount; void list(char *prefix, char *file) { if (flagset) { int sum = 0; char *f = strstr(file, ":2,"); if (!f) return; icount++; tcount++; f += 3; while (*f) { if (flags[(unsigned int)*f] == -1) return; if (flags[(unsigned int)*f] == 1) sum++; f++; } if (sum != flagsum) return; } if (iflag) { char *f = strstr(file, ":2,"); if (!f) return; imatched++; if (!flagset) icount++, tcount++; if (!strchr(f, 'S')) iunseen++, tunseen++; if (strchr(f, 'F')) iflagged++, tflagged++; return; } if (prefix) { fputs(prefix, stdout); putc('/', stdout); } puts(file); } #ifdef __linux__ // faster implementation of readdir using a bigger getdents buffer #include struct linux_dirent64 { ino64_t d_ino; /* 64-bit inode number */ off64_t d_off; /* 64-bit offset to next structure */ unsigned short d_reclen; /* Size of this dirent */ unsigned char d_type; /* File type */ char d_name[]; /* Filename (null-terminated) */ }; #define BUF_SIZE 1024000 char buf[BUF_SIZE]; void listdir(char *dir) { int fd; ssize_t nread; ssize_t bpos; struct linux_dirent64 *d; fd = open(dir, O_RDONLY | O_DIRECTORY); if (fd == -1) { perror("open"); return; } while (1) { nread = syscall(SYS_getdents64, fd, buf, BUF_SIZE); if (nread == -1) { perror("getdents64"); break; } if (nread == 0) break; for (bpos = 0; bpos < nread; bpos += d->d_reclen) { d = (struct linux_dirent64 *)(buf + bpos); if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN) continue; if (d->d_name[0] == '.') continue; list(dir, d->d_name); } } close(fd); } #else void listdir(char *dir) { DIR *fd; struct dirent *d; fd = opendir(dir); if (!fd) return; while ((d = readdir(fd))) { if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN) continue; if (d->d_name[0] == '.') continue; list(dir, d->d_name); } closedir(fd); } #endif void listarg(char *arg) { squeeze_slash(arg); struct stat st; if (stat(arg, &st) < 0) return; if (S_ISDIR(st.st_mode)) { char subdir[PATH_MAX]; struct stat st2; int maildir = 0; long gcount = icount; long gunseen = iunseen; long gflagged = iflagged; long gmatched = imatched; icount = 0; iunseen = 0; iflagged = 0; snprintf(subdir, sizeof subdir, "%s/cur", arg); if (stat(subdir, &st2) == 0) { maildir = 1; if (Cflag >= 0 && Nflag <= 0) listdir(subdir); } snprintf(subdir, sizeof subdir, "%s/new", arg); if (stat(subdir, &st2) == 0) { maildir = 1; if (Nflag >= 0 && Cflag <= 0) listdir(subdir); } if (!maildir) listdir(arg); if (iflag && (imatched || (maildir && !flagset))) { tdirs++; printf("%6ld unseen %3ld flagged %6ld msg %s\n", iunseen, iflagged, icount, arg); } icount = gcount; iunseen = gunseen; iflagged = gflagged; imatched = gmatched; } else if (S_ISREG(st.st_mode)) { list(0, arg); } } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "PRSTDFprstdfX:x:NnCci")) != -1) switch (c) { case 'P': case 'R': case 'S': case 'T': case 'D': case 'F': flags[(unsigned int)c] = 1; break; case 'p': case 'r': case 's': case 't': case 'd': case 'f': flags[(unsigned int)uc(c)] = -1; break; case 'X': while (*optarg) flags[(unsigned int)*optarg++] = 1; break; case 'x': while (*optarg) flags[(unsigned int)*optarg++] = -1; break; case 'N': Nflag = 1; break; case 'n': Nflag = -1; break; case 'C': Cflag = 1; break; case 'c': Cflag = -1; break; case 'i': iflag = 1; break; default: usage: fprintf(stderr, "Usage: mlist [-DFPRST] [-X str]\n" " [-dfprst] [-x str]\n" " [-N | -n | -C | -c]\n" " [-i] [dirs...]\n" ); exit(1); } int i; for (i = 0, flagsum = 0, flagset = 0; (size_t)i < sizeof flags; i++) { if (flags[i] != 0) flagset++; if (flags[i] == 1) flagsum++; } if (optind == argc) { if (isatty(0)) goto usage; blaze822_loop(0, 0, listarg); } else { for (i = optind; i < argc; i++) listarg(argv[i]); } if (iflag && tdirs > 1) printf("%6ld unseen %3ld flagged %6ld msg\n", tunseen, tflagged, tcount); return 0; } mblaze-0.3.2/mmime.c000066400000000000000000000241731324061207500142450ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" static int cflag; static int rflag; static char *tflag = "multipart/mixed"; int gen_b64(uint8_t *s, off_t size) { static char *b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; off_t i; int l; uint32_t v; for (i = 0, l = 0; i+2 < size; i += 3) { v = (s[i] << 16) | (s[i+1] << 8) | s[i+2]; putc_unlocked(b64[(v & 0xfc0000) >> 18], stdout); putc_unlocked(b64[(v & 0x03f000) >> 12], stdout); putc_unlocked(b64[(v & 0x000fc0) >> 6], stdout); putc_unlocked(b64[(v & 0x3f)], stdout); l += 4; if (l > 72) { l = 0; printf("\n"); } } if (size - i == 2) { // 2 bytes left, XXX= v = (s[size - 2] << 16) | (s[size - 1] << 8); putc_unlocked(b64[(v & 0xfc0000) >> 18], stdout); putc_unlocked(b64[(v & 0x03f000) >> 12], stdout); putc_unlocked(b64[(v & 0x000fc0) >> 6], stdout); putc_unlocked('=', stdout); } else if (size - i == 1) { // 1 byte left, XX== v = s[size - 1] << 16; putc_unlocked(b64[(v & 0xfc0000) >> 18], stdout); putc_unlocked(b64[(v & 0x03f000) >> 12], stdout); putc_unlocked('=', stdout); putc_unlocked('=', stdout); } printf("\n"); return 0; } size_t gen_qp(uint8_t *s, off_t size, size_t maxlinelen, size_t linelen) { off_t i; int header = linelen > 0; char prev = 0; for (i = 0; i < size; i++) { // inspect utf8 sequence to not wrap in between multibyte int mb; if ((s[i] & 0x80) == 0) mb = 3; else if ((s[i] & 0xc0) == 0x80) mb = 3; else if ((s[i] & 0xe0) == 0xc0) mb = 6; else if ((s[i] & 0xf0) == 0xe0) mb = 9; else if ((s[i] & 0xf8) == 0xf0) mb = 12; else mb = 3; if (linelen >= maxlinelen-mb-!!header) { linelen = 0; prev = '\n'; if (header) { printf("?=\n =?UTF-8?Q?"); linelen += 11; } else { puts("="); } } if ((s[i] > 126) || (s[i] == '=') || (linelen == 0 && (strncmp((char *)s, "From ", 5) == 0 || (s[i] == '.' && i+1 < size && (s[i+1] == '\n' || s[i+1] == '\r'))))) { printf("=%02X", s[i]); linelen += 3; prev = s[i]; } else if (header && (s[i] == '\n' || s[i] == '\t' || s[i] == '_')) { printf("=%02X", s[i]); linelen += 3; prev = '_'; } else if (header && s[i] == ' ') { putc_unlocked('_', stdout); linelen++; prev = '_'; } else if (s[i] < 33 && s[i] != '\n') { if ((s[i] == ' ' || s[i] == '\t') && i+1 < size && (s[i+1] != '\n' && s[i+1] != '\r')) { putc_unlocked(s[i], stdout); linelen += 1; prev = s[i]; } else { printf("=%02X", s[i]); linelen += 3; prev = '_'; } } else if (s[i] == '\n') { if (prev == ' ' || prev == '\t') puts("="); putc_unlocked('\n', stdout); linelen = 0; prev = 0; } else { putc_unlocked(s[i], stdout); linelen++; prev = s[i]; } } if (linelen > 0 && !header) puts("="); return linelen; } static const char * basenam(const char *s) { char *r = strrchr(s, '/'); return r ? r + 1 : s; } static void gen_attachment(const char *filename, char *content_disposition) { const char *s = filename; int quote = 0; if (!*filename) { printf("Content-Disposition: %s\n", content_disposition); return; } for (s = (char *)filename; *s; s++) { if (*s < 32 || *s == '"' || *s >= 127 || s - filename > 35) goto rfc2231; if (strchr(" ()<>@,;:\\/[]?=", *s)) quote = 1; } // filename SHOULD be an atom if possible printf("Content-Disposition: %s; filename=%s%s%s\n", content_disposition, quote ? "\"" : "", filename, quote ? "\"" : ""); return; rfc2231: printf("Content-Disposition: %s", content_disposition); int i = 0; int d = 0; s = filename; while (*s) { i = printf(";\n filename*%d*=", d); if (d++ == 0) { printf("UTF-8''"); i += 7; } while (*s && i < 78 - 3) { if (*s <= 32 || *s == '"' || *s > 126) i += printf("%%%02x", (uint8_t)*s++); else i += printf("%c", (uint8_t)*s++); } } printf("\n"); } int gen_file(char *file, char *ct) { uint8_t *content; off_t size; char *cd = "attachment"; char *s = strchr(ct, '#'); if (s) { *s = 0; cd = s + 1; } const char *filename = basenam(file); s = strchr(file, '>'); if (s) { *s = 0; filename = s + 1; } int r = slurp(file, (char **)&content, &size); if (r != 0) { fprintf(stderr, "mmime: error attaching file '%s': %s\n", file, strerror(r)); return -1; } if (strcmp(ct, "mblaze/raw") == 0) goto raw; off_t bithigh = 0; off_t bitlow = 0; off_t linelen = 0; off_t maxlinelen = 0; off_t i; for (i = 0; i < size; i++) { if (content[i] == '\n') { if (maxlinelen < linelen) maxlinelen = linelen; linelen = 0; } else { linelen++; } if (content[i] != '\t' && content[i] != '\n' && content[i] < 32) bitlow++; if (content[i] > 127) bithigh++; } gen_attachment(filename, cd); if (strcmp(ct, "message/rfc822") == 0) { printf("Content-Type: %s\n", ct); printf("Content-Transfer-Encoding: %dbit\n\n", (bitlow > 0 || bithigh > 0) ? 8 : 7); fwrite(content, 1, size, stdout); return 0; } if (bitlow == 0 && bithigh == 0 && maxlinelen <= 78 && content[size-1] == '\n') { if (!ct) ct = "text/plain"; printf("Content-Type: %s\n", ct); printf("Content-Transfer-Encoding: 7bit\n\n"); raw: fwrite(content, 1, size, stdout); return 0; } else if (bitlow == 0 && bithigh == 0) { if (!ct) ct = "text/plain"; printf("Content-Type: %s\n", ct); printf("Content-Transfer-Encoding: quoted-printable\n\n"); gen_qp(content, size, 78, 0); return 0; } else if (bitlow > size/10 || bithigh > size/4) { if (!ct) ct = "application/binary"; printf("Content-Type: %s\n", ct); printf("Content-Transfer-Encoding: base64\n\n"); return gen_b64(content, size); } else { if (!ct) ct = "text/plain"; printf("Content-Type: %s\n", ct); printf("Content-Transfer-Encoding: quoted-printable\n\n"); gen_qp(content, size, 78, 0); return 0; } } void print_header(char *line) { char *s, *e; size_t l = strlen(line); if (line[l-1] == '\n') line[l-1] = 0; /* iterate word-wise, encode words when needed. */ s = line; if (!(*s == ' ' || *s == '\t')) { // raw header name while (*s && *s != ':') putc_unlocked(*s++, stdout); if (*s == ':') putc_unlocked(*s++, stdout); } int prevq = 0; // was the previous word encoded as qp? size_t linelen = s - line; while (*s) { size_t highbit = 0; e = s; while (*e && *e == ' ') e++; for (; *e && *e != ' '; e++) { if ((uint8_t)*e >= 127) highbit++; } if (!highbit) { if (e-s >= 998) goto force_qp; if (e-s >= 78 - linelen) { // wrap in advance before long word printf("\n"); linelen = 0; } if (linelen <= 1 && s[0] == ' ' && s[1] == ' ') { // space at beginning of line goto force_qp; } if (*s != ' ') { printf(" "); linelen++; } fwrite(s, 1, e-s, stdout); linelen += e-s; prevq = 0; } else { force_qp: if (!prevq && *s == ' ') s++; if (linelen >= 78 - 13 - 4 || (e-s < (78 - 13)/3 && e-s >= (78 - linelen - 13)/3)) { // wrap in advance printf("\n"); linelen = 0; } printf(" =?UTF-8?Q?"); linelen += 11; linelen = gen_qp((uint8_t *)s, e-s, 78, linelen); printf("?="); linelen += 2; prevq = 1; } s = e; } printf("\n"); } int gen_build() { char sep[100]; snprintf(sep, sizeof sep, "----_=_%08lx%08lx%08lx_=_", lrand48(), lrand48(), lrand48()); char *line = 0; size_t linelen = 0; int inheader = 1; int intext = 0; while (1) { ssize_t read = getdelim(&line, &linelen, '\n', stdin); if (read == -1) { if (feof(stdin)) break; else exit(1); } if (inheader) { if (line[0] == '\n') { inheader = 0; printf("MIME-Version: 1.0\n"); if (rflag) { printf("Content-Type: text/plain; charset=UTF-8\n"); printf("Content-Transfer-Encoding: quoted-printable\n\n"); } else { printf("Content-Type: %s; boundary=\"%s\"\n", tflag, sep); printf("\n"); printf("This is a multipart message in MIME format.\n"); } } else { print_header(line); } continue; } if (!rflag && line[0] == '#') { char *f = strchr(line, ' '); if (f) { char of = *f; *f = 0; if (strchr(line, '/')) { printf("\n--%s\n", sep); if (line[read-1] == '\n') line[read-1] = 0; gen_file(f+1, (char *)line+1); intext = 0; continue; } *f = of; } } if (!rflag && !intext) { printf("\n--%s\n", sep); printf("Content-Type: text/plain; charset=UTF-8\n"); printf("Content-Disposition: inline\n"); printf("Content-Transfer-Encoding: quoted-printable\n\n"); intext = 1; } gen_qp((uint8_t *)line, strlen(line), 78, 0); } if (!rflag && !inheader) printf("\n--%s--\n", sep); free(line); return 0; } int check() { off_t bithigh = 0; off_t bitlow = 0; off_t linelen = 0; off_t maxheadlinelen = 0; off_t maxbodylinelen = 0; int c; int l = -1; while ((c = getchar()) != EOF) { if (c == '\n') { if (maxheadlinelen < linelen) maxheadlinelen = linelen; linelen = 0; if (l == '\n') break; } else { linelen++; } if (c != '\t' && c != '\n' && c < 32) bitlow++; if (c > 127) bithigh++; l = c; } while ((c = getchar()) != EOF) { if (c == '\n') { if (maxbodylinelen < linelen) maxbodylinelen = linelen; linelen = 0; } else { linelen++; } if (c != '\t' && c != '\n' && c < 32) bitlow++; if (c > 127) bithigh++; l = c; } if (bitlow == 0 && bithigh == 0 && maxheadlinelen < 998 && maxbodylinelen <= 78 && l == '\n') return 0; else return 1; } int main(int argc, char *argv[]) { srand48(time(0) ^ getpid()); int c; while ((c = getopt(argc, argv, "crt:")) != -1) switch (c) { case 'r': rflag = 1; break; case 'c': cflag = 1; break; case 't': tflag = optarg; break; default: usage: fprintf(stderr, "Usage: mmime [-c|-r] [-t CONTENT-TYPE] < message\n"); exit(1); } if (argc != optind) goto usage; if (cflag) return check(); return gen_build(); } mblaze-0.3.2/mmkdir000077500000000000000000000002041324061207500141730ustar00rootroot00000000000000#!/bin/sh # mmkdir DIRS... - create new maildirs r=0 for dir; do mkdir -p -m 0700 $dir/cur $dir/new $dir/tmp || r=1 done exit $r mblaze-0.3.2/mnext000077700000000000000000000000001324061207500151052mlessustar00rootroot00000000000000mblaze-0.3.2/mpick.c000066400000000000000000000520631324061207500142430ustar00rootroot00000000000000// FNM_CASEFOLD #define _GNU_SOURCE #include // strptime #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 #endif #ifdef __has_include #if __has_include() #include #else #define noreturn /**/ #endif #else #define noreturn /**/ #endif /* For Solaris. */ #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) #define FNM_CASEFOLD FNM_IGNORECASE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" enum op { EXPR_OR = 1, EXPR_AND, EXPR_NOT, EXPR_LT, EXPR_LE, EXPR_EQ, EXPR_NEQ, EXPR_GE, EXPR_GT, EXPR_STREQ, EXPR_STREQI, EXPR_GLOB, EXPR_GLOBI, EXPR_REGEX, EXPR_REGEXI, EXPR_PRUNE, EXPR_PRINT, EXPR_TYPE, EXPR_ALLSET, EXPR_ANYSET, }; enum prop { PROP_ATIME = 1, PROP_CTIME, PROP_DEPTH, PROP_KEPT, PROP_MTIME, PROP_PATH, PROP_REPLIES, PROP_SIZE, PROP_TOTAL, PROP_FROM, PROP_TO, PROP_INDEX, PROP_DATE, PROP_FLAG, }; enum flags { FLAG_PASSED = 1, FLAG_REPLIED = 2, FLAG_SEEN = 4, FLAG_TRASHED = 8, FLAG_DRAFT = 16, FLAG_FLAGGED = 32, /* custom */ FLAG_NEW = 64, FLAG_CUR = 128, FLAG_PARENT = 256, FLAG_CHILD = 512, FLAG_INFO = 1024, }; enum var { VAR_CUR = 1, }; struct expr { enum op op; union { enum prop prop; struct expr *expr; char *string; int64_t num; regex_t *regex; enum var var; } a, b; int extra; }; struct mailinfo { char *fpath; struct stat *sb; struct message *msg; time_t date; int depth; int index; int replies; int matched; int prune; int flags; off_t total; }; struct mlist { struct mailinfo *m; struct mlist *parent; struct mlist *next; }; struct thread { int matched; struct mlist childs[100]; struct mlist *cur; }; static struct thread *thr; static char *argv0; static int Tflag; static int need_thr; static long kept; static long num; static struct expr *expr; static long cur_idx; static char *cur; static char *pos; static time_t now; static int prune; static void ws() { while (isspace((unsigned char)*pos)) pos++; } static int token(char *token) { if (strncmp(pos, token, strlen(token)) == 0) { pos += strlen(token); ws(); return 1; } else { return 0; } } noreturn static void parse_error(char *msg, ...) { va_list ap; va_start(ap, msg); fprintf(stderr, "%s: parse error: ", argv0); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); exit(2); } static struct expr * mkexpr(enum op op) { struct expr *e = malloc(sizeof (struct expr)); if (!e) parse_error("out of memory"); e->op = op; e->extra = 0; return e; } static struct expr * chain(struct expr *e1, enum op op, struct expr *e2) { struct expr *i, *j, *e; if (!e1) return e2; if (!e2) return e1; for (j = 0, i = e1; i->op == op; j = i, i = i->b.expr) ; if (!j) { e = mkexpr(op); e->a.expr = e1; e->b.expr = e2; return e; } else { e = mkexpr(op); e->a.expr = i; e->b.expr = e2; j->b.expr = e; return e1; } } static enum op parse_op() { if (token("<=")) return EXPR_LE; else if (token("<")) return EXPR_LT; else if (token(">=")) return EXPR_GE; else if (token(">")) return EXPR_GT; else if (token("==") || token("=")) return EXPR_EQ; else if (token("!=")) return EXPR_NEQ; return 0; } static struct expr *parse_cmp(); static struct expr *parse_or(); static struct expr * parse_inner() { if (token("prune")) { struct expr *e = mkexpr(EXPR_PRUNE); return e; } else if (token("print")) { struct expr *e = mkexpr(EXPR_PRINT); return e; } else if (token("!")) { struct expr *e = parse_cmp(); struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; } else if (token("(")) { struct expr *e = parse_or(); if (token(")")) return e; parse_error("missing ) at '%.15s'", pos); return 0; } else { parse_error("unknown expression at '%.15s'", pos); return 0; } } static int parse_string(char **s) { char *buf = 0; size_t bufsiz = 0; size_t len = 0; if (*pos == '"') { pos++; while (*pos && (*pos != '"' || (*pos == '"' && *(pos+1) == '"'))) { if (len >= bufsiz) { bufsiz = 2*bufsiz + 16; buf = realloc(buf, bufsiz); if (!buf) parse_error("string too long"); } if (*pos == '"') pos++; buf[len++] = *pos++; } if (!*pos) parse_error("unterminated string"); if (buf) buf[len] = 0; pos++; ws(); *s = buf ? buf : ""; return 1; } else if (*pos == '$') { char t; char *e = ++pos; while (isalnum((unsigned char)*pos) || *pos == '_') pos++; if (e == pos) parse_error("invalid environment variable name"); t = *pos; *pos = 0; *s = getenv(e); if (!*s) *s = ""; *pos = t; ws(); return 1; } return 0; } static struct expr * parse_strcmp() { enum prop prop; enum op op; int negate; char *h; h = 0; prop = 0; negate = 0; if (token("from")) prop = PROP_FROM; else if (token("to")) prop = PROP_TO; else if (token("subject")) h = "subject"; else if (!parse_string(&h)) return parse_inner(); if (token("~~~")) op = EXPR_GLOBI; else if (token("~~")) op = EXPR_GLOB; else if (token("=~~")) op = EXPR_REGEXI; else if (token("=~")) op = EXPR_REGEX; else if (token("===")) op = EXPR_STREQI; else if (token("==")) op = EXPR_STREQ; else if (token("=")) op = EXPR_STREQ; else if (token("!~~~")) negate = 1, op = EXPR_GLOBI; else if (token("!~~")) negate = 1, op = EXPR_GLOB; else if (token("!=~~")) negate = 1, op = EXPR_REGEXI; else if (token("!=~")) negate = 1, op = EXPR_REGEX; else if (token("!===")) negate = 1, op = EXPR_STREQI; else if (token("!==") || token("!=")) negate = 1, op = EXPR_STREQ; else parse_error("invalid string operator at '%.15s'", pos); char *s; if (!parse_string(&s)) { parse_error("invalid string at '%.15s'", pos); return 0; } int r = 0; struct expr *e = mkexpr(op); if (prop) e->a.prop = prop; else e->a.string = h; if (prop == PROP_FROM || prop == PROP_TO) { char *disp, *addr; blaze822_addr(s, &disp, &addr); if (!disp && !addr) parse_error("invalid address at '%.15s'", pos); s = strdup((disp) ? disp : addr); e->extra = (disp) ? 0 : 1; } if (op == EXPR_REGEX) { e->b.regex = malloc(sizeof (regex_t)); r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB); } else if (op == EXPR_REGEXI) { e->b.regex = malloc(sizeof (regex_t)); r = regcomp(e->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); } else { e->b.string = s; } if (r != 0) { char msg[256]; regerror(r, e->b.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", s, msg); exit(2); } if (negate) { struct expr *not = mkexpr(EXPR_NOT); not->a.expr = e; return not; } return e; } static int64_t parse_num(int64_t *r) { char *s = pos; if (isdigit((unsigned char)*pos)) { int64_t n; for (n = 0; isdigit((unsigned char)*pos) && n <= INT64_MAX / 10 - 10; pos++) n = 10 * n + (*pos - '0'); if (isdigit((unsigned char)*pos)) parse_error("number too big: %s", s); if (token("c")) ; else if (token("b")) n *= 512LL; else if (token("k")) n *= 1024LL; else if (token("M")) n *= 1024LL * 1024; else if (token("G")) n *= 1024LL * 1024 * 1024; else if (token("T")) n *= 1024LL * 1024 * 1024 * 1024; ws(); *r = n; return 1; } else { return 0; } } static struct expr * parse_flag() { enum flags flag; if (token("passed")) { flag = FLAG_PASSED; } else if (token("replied")) { flag = FLAG_REPLIED; } else if (token("seen")) { flag = FLAG_SEEN; } else if (token("trashed")) { flag = FLAG_TRASHED; } else if (token("draft")) { flag = FLAG_DRAFT; } else if (token("flagged")) { flag = FLAG_FLAGGED; } else if (token("new")) { flag = FLAG_NEW; } else if (token("cur")) { flag = FLAG_CUR; } else if (token("info")) { flag = FLAG_INFO; } else if (token("parent")) { flag = FLAG_PARENT; need_thr = 1; } else if (token("child")) { flag = FLAG_CHILD; need_thr = 1; } else return parse_strcmp(); struct expr *e = mkexpr(EXPR_ANYSET); e->a.prop = PROP_FLAG; e->b.num = flag; return e; } static struct expr * parse_cmp() { enum prop prop; enum op op; if (token("depth")) prop = PROP_DEPTH; else if (token("kept")) prop = PROP_KEPT; else if (token("index")) prop = PROP_INDEX; else if (token("replies")) { prop = PROP_REPLIES; need_thr = 1; } else if (token("size")) prop = PROP_SIZE; else if (token("total")) prop = PROP_TOTAL; else return parse_flag(); if (!(op = parse_op())) parse_error("invalid comparison at '%.15s'", pos); int64_t n; if (parse_num(&n)) { struct expr *e = mkexpr(op); e->a.prop = prop; e->b.num = n; return e; } else if (token("cur")) { struct expr *e = mkexpr(op); e->a.prop = prop; e->b.var = VAR_CUR; e->extra = 1; return e; } return 0; } static int parse_dur(int64_t *n) { char *s, *r; if (!parse_string(&s)) return 0; if (*s == '/' || *s == '.') { struct stat st; if (stat(s, &st) < 0) parse_error("can't stat file '%s': %s", s, strerror(errno)); *n = st.st_mtime; return 1; } struct tm tm = { 0 }; r = strptime(s, "%Y-%m-%d %H:%M:%S", &tm); if (r && !*r) { *n = mktime(&tm); return 1; } r = strptime(s, "%Y-%m-%d", &tm); if (r && !*r) { tm.tm_hour = tm.tm_min = tm.tm_sec = 0; *n = mktime(&tm); return 1; } r = strptime(s, "%H:%M:%S", &tm); if (r && !*r) { struct tm *tmnow = localtime(&now); tm.tm_year = tmnow->tm_year; tm.tm_mon = tmnow->tm_mon; tm.tm_mday = tmnow->tm_mday; *n = mktime(&tm); return 1; } r = strptime(s, "%H:%M", &tm); if (r && !*r) { struct tm *tmnow = localtime(&now); tm.tm_year = tmnow->tm_year; tm.tm_mon = tmnow->tm_mon; tm.tm_mday = tmnow->tm_mday; tm.tm_sec = 0; *n = mktime(&tm); return 1; } if (*s == '-') { s++; errno = 0; int64_t d; d = strtol(s, &r, 10); if (errno == 0 && r[0] == 'd' && !r[1]) { struct tm *tmnow = localtime(&now); tmnow->tm_mday -= d; tmnow->tm_hour = tmnow->tm_min = tmnow->tm_sec = 0; *n = mktime(tmnow); return 1; } if (errno == 0 && r[0] == 'h' && !r[1]) { *n = now - (d*60*60); return 1; } if (errno == 0 && r[0] == 'm' && !r[1]) { *n = now - (d*60); return 1; } if (errno == 0 && r[0] == 's' && !r[1]) { *n = now - d; return 1; } parse_error("invalid relative time format '%s'", s-1); } parse_error("invalid time format '%s'", s); return 0; } static struct expr * parse_timecmp() { enum prop prop; enum op op; if (token("atime")) prop = PROP_ATIME; else if (token("ctime")) prop = PROP_CTIME; else if (token("mtime")) prop = PROP_MTIME; else if (token("date")) prop = PROP_DATE; else return parse_cmp(); op = parse_op(); if (!op) parse_error("invalid comparison at '%.15s'", pos); int64_t n; if (parse_num(&n) || parse_dur(&n)) { struct expr *e = mkexpr(op); e->a.prop = prop; e->b.num = n; return e; } return 0; } static struct expr * parse_and() { struct expr *e1 = parse_timecmp(); struct expr *r = e1; while (token("&&")) { struct expr *e2 = parse_timecmp(); r = chain(r, EXPR_AND, e2); } return r; } static struct expr * parse_or() { struct expr *e1 = parse_and(); struct expr *r = e1; while (token("||")) { struct expr *e2 = parse_and(); r = chain(r, EXPR_OR, e2); } return r; } static struct expr * parse_expr(char *s) { pos = s; struct expr *e = parse_or(); if (*pos) parse_error("trailing garbage at '%.15s'", pos); return e; } static struct expr * parse_msglist(char *s) { int64_t n, m; int r; struct expr *e1, *e2; char *d; switch (*s) { case '/': s++; e1 = mkexpr(EXPR_REGEXI); e1->a.string = "subject"; e1->b.regex = malloc(sizeof (regex_t)); r = regcomp(e1->b.regex, s, REG_EXTENDED | REG_NOSUB | REG_ICASE); if (r != 0) { char msg[256]; regerror(r, e1->b.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", s, msg); } return e1; case ':': if (strlen(s) <= 1) parse_error("missing type at '%.15s'", s); enum flags flag; n = 0; switch (*++s) { case 'P': flag = FLAG_PASSED; break; case 'F': flag = FLAG_FLAGGED; break; case 'D': flag = FLAG_DRAFT; break; case 'd': /* FALL THROUGH */ case 'T': flag = FLAG_TRASHED; break; case 'u': n = 1; /* FALL THROUGH */ case 'r': /* FALL THROUGH */ case 'S': flag = FLAG_SEEN; break; case 'o': n = 1; /* FALL THROUGH */ case 'n': flag = FLAG_NEW; break; case 'R': flag = FLAG_REPLIED; break; default: parse_error("unknown type at '%.15s'", s); } e1 = mkexpr(EXPR_ANYSET); e1->a.prop = PROP_FLAG; e1->b.num = flag; if (!n) return e1; e2 = mkexpr(EXPR_NOT); e2->a.expr = e1; return e2; default: pos = s; if (((d = strchr(s, ':')) || (d = strchr(s, '-'))) && parse_num(&n) && (pos = d + 1) && parse_num(&m)) { /* index >= n */ e1 = mkexpr(EXPR_GE); e1->a.prop = PROP_INDEX; e1->b.num = n; /* index <= m */ e2 = mkexpr(EXPR_LE); e2->a.prop = PROP_INDEX; e2->b.num = m; /* e1 && e2 */ return chain(e1, EXPR_AND, e2); } else if (parse_num(&n)) { e1 = mkexpr(EXPR_EQ); e1->a.prop = PROP_INDEX; e1->b.num = n; return e1; } else { char *disp, *addr; blaze822_addr(s, &disp, &addr); if (!disp && !addr) parse_error("invalid address at '%.15s'", pos); d = strdup((disp) ? disp : addr); e1 = mkexpr(EXPR_REGEXI); e1->a.prop = PROP_FROM; e1->b.regex = malloc(sizeof (regex_t)); e1->extra = (disp) ? 0 : 1; r = regcomp(e1->b.regex, d, REG_EXTENDED | REG_NOSUB | REG_ICASE); if (r != 0) { char msg[256]; regerror(r, e1->b.regex, msg, sizeof msg); parse_error("invalid regex '%s': %s", d, msg); } return e1; } } return 0; } time_t msg_date(struct mailinfo *m) { if (m->date) return m->date; if (!m->msg) m->msg = blaze822(m->fpath); char *b; if (m->msg && (b = blaze822_hdr(m->msg, "date"))) return (m->date = blaze822_date(b)); return -1; } char * msg_hdr(struct mailinfo *m, const char *h) { if (!m->msg) m->msg = blaze822(m->fpath); char *b; if (!m->msg || !(b = blaze822_chdr(m->msg, h))) goto err; char buf[100]; blaze822_decode_rfc2047(buf, b, sizeof buf - 1, "UTF-8"); if (!*buf) goto err; return strdup(buf); err: return ""; } char * msg_addr(struct mailinfo *m, char *h, int t) { if (!m->msg) m->msg = blaze822(m->fpath); char *b; if (m->msg == 0 || (b = blaze822_chdr(m->msg, h)) == 0) return ""; char *disp, *addr; blaze822_addr(b, &disp, &addr); if (t) { if (!addr) return ""; return addr; } else { if (!disp) return ""; return disp; } } int eval(struct expr *e, struct mailinfo *m) { switch (e->op) { case EXPR_OR: return eval(e->a.expr, m) || eval(e->b.expr, m); case EXPR_AND: return eval(e->a.expr, m) && eval(e->b.expr, m); case EXPR_NOT: return !eval(e->a.expr, m); return 1; case EXPR_PRUNE: prune = 1; return 1; case EXPR_PRINT: return 1; case EXPR_LT: case EXPR_LE: case EXPR_EQ: case EXPR_NEQ: case EXPR_GE: case EXPR_GT: case EXPR_ALLSET: case EXPR_ANYSET: { long v = 0, n; if (!m->sb && ( e->a.prop == PROP_ATIME || e->a.prop == PROP_CTIME || e->a.prop == PROP_MTIME || e->a.prop == PROP_SIZE) && (m->sb = calloc(1, sizeof *m->sb)) && stat(m->fpath, m->sb) != 0) { fprintf(stderr, "stat"); exit(2); } n = e->b.num; if (e->extra) switch (e->b.var) { case VAR_CUR: if (!cur_idx) n = (e->op == EXPR_LT || e->op == EXPR_LE) ? LONG_MAX : -1; else n = cur_idx; break; } switch (e->a.prop) { case PROP_ATIME: if (m->sb) v = m->sb->st_atime; break; case PROP_CTIME: if (m->sb) v = m->sb->st_ctime; break; case PROP_MTIME: if (m->sb) v = m->sb->st_mtime; break; case PROP_KEPT: v = kept; break; case PROP_REPLIES: v = m->replies; break; case PROP_SIZE: if (m->sb) v = m->sb->st_size; break; case PROP_DATE: v = msg_date(m); break; case PROP_FLAG: v = m->flags; break; case PROP_INDEX: v = m->index; break; case PROP_DEPTH: v = m->depth; break; default: parse_error("unknown property"); } switch (e->op) { case EXPR_LT: return v < n; case EXPR_LE: return v <= n; case EXPR_EQ: return v == n; case EXPR_NEQ: return v != n; case EXPR_GE: return v >= n; case EXPR_GT: return v > n; case EXPR_ALLSET: return (v & n) == n; case EXPR_ANYSET: return (v & n) > 0; default: parse_error("invalid operator"); } } case EXPR_STREQ: case EXPR_STREQI: case EXPR_GLOB: case EXPR_GLOBI: case EXPR_REGEX: case EXPR_REGEXI: { const char *s = ""; switch (e->a.prop) { case PROP_PATH: s = m->fpath; break; case PROP_FROM: s = msg_addr(m, "from", e->extra); break; case PROP_TO: s = msg_addr(m, "to", e->extra); break; default: s = msg_hdr(m, e->a.string); break; } switch (e->op) { case EXPR_STREQ: return strcmp(e->b.string, s) == 0; case EXPR_STREQI: return strcasecmp(e->b.string, s) == 0; case EXPR_GLOB: return fnmatch(e->b.string, s, 0) == 0; case EXPR_GLOBI: return fnmatch(e->b.string, s, FNM_CASEFOLD) == 0; case EXPR_REGEX: case EXPR_REGEXI: return regexec(e->b.regex, s, 0, 0, 0) == 0; } } } return 0; } struct mailinfo * mailfile(char *file) { static int init; if (!init) { // delay loading of the seqmap until we need to scan the first // file, in case someone in the pipe updated the map before char *seqmap = blaze822_seq_open(0); blaze822_seq_load(seqmap); cur = blaze822_seq_cur(); init = 1; } struct mailinfo *m; m = calloc(1, sizeof *m); if (!m) { fprintf(stderr, "calloc"); exit(2); } m->fpath = file; m->index = num++; m->flags = 0; m->replies = 0; m->depth = 0; m->sb = 0; m->msg = 0; while (*m->fpath == ' ' || *m->fpath == '\t') { m->depth++; m->fpath++; } char *e = m->fpath + strlen(m->fpath) - 1; while (m->fpath < e && (*e == ' ' || *e == '\t')) *e-- = 0; if (m->fpath[0] == '<') { m->flags |= FLAG_SEEN | FLAG_INFO; return m; } if ((e = strrchr(m->fpath, '/') - 1) && (e - m->fpath) >= 2 && *e-- == 'w' && *e-- == 'e' && *e-- == 'n') m->flags |= FLAG_NEW; if (cur && strcmp(cur, m->fpath) == 0) { m->flags |= FLAG_CUR; cur_idx = m->index; } char *f = strstr(m->fpath, ":2,"); if (f) { if (strchr(f, 'P')) m->flags |= FLAG_PASSED; if (strchr(f, 'R')) m->flags |= FLAG_REPLIED; if (strchr(f, 'S')) m->flags |= FLAG_SEEN; if (strchr(f, 'T')) m->flags |= FLAG_TRASHED; if (strchr(f, 'D')) m->flags |= FLAG_DRAFT; if (strchr(f, 'F')) m->flags |= FLAG_FLAGGED; } return m; } void do_thr() { struct mlist *ml; if (!thr) return; for (ml = thr->childs; ml; ml = ml->next) { if (!ml->m) continue; if ((ml->m->prune = prune) || (Tflag && thr->matched)) continue; if (expr && eval(expr, ml->m)) { ml->m->matched = 1; thr->matched++; } } prune = 0; for (ml = thr->childs; ml; ml = ml->next) { if (!ml->m) break; if (((Tflag && thr->matched) || ml->m->matched) && !ml->m->prune) { int i; for (i = 0; i < ml->m->depth; i++) putchar(' '); fputs(ml->m->fpath, stdout); putchar('\n'); kept++; } /* free collected mails */ if (ml->m->msg) blaze822_free(ml->m->msg); if (ml->m->sb) free(ml->m->sb); free(ml->m->fpath); free(ml->m); } free(thr); thr = 0; } void collect(char *file) { struct mailinfo *m; struct mlist *ml; if ((m = mailfile(file)) == 0) return; if (m->depth == 0) { if (thr) do_thr(); /* new thread */ thr = calloc(1, sizeof *thr); if (!thr) { fprintf(stderr, "calloc"); exit(2); } thr->matched = 0; ml = thr->cur = thr->childs; thr->cur->m = m; } else { ml = thr->cur + 1; if (thr->cur->m->depth < m->depth) { /* previous mail is a parent */ thr->cur->m->flags |= FLAG_PARENT; ml->parent = thr->cur; } else if (thr->cur->m->depth == m->depth) { /* same depth == same parent */ ml->parent = thr->cur->parent; } else if (thr->cur->m->depth > m->depth) { /* find parent mail */ struct mlist *pl; for (pl = thr->cur; pl->m->depth >= m->depth; pl--) ; ml->parent = pl; } m->flags |= FLAG_CHILD; thr->cur->next = ml; thr->cur = ml; ml->m = m; } for (ml = ml->parent; ml; ml = ml->parent) ml->m->replies++; m->fpath = strdup(m->fpath); } void oneline(char *file) { struct mailinfo *m; m = mailfile(file); if (expr && !eval(expr, m)) goto out; fputs(file, stdout); putchar('\n'); kept++; out: if (m->msg) blaze822_free(m->msg); if (m->sb) free(m->sb); free(m); } int main(int argc, char *argv[]) { long i; int c; argv0 = argv[0]; now = time(0); num = 1; while ((c = getopt(argc, argv, "Tt:")) != -1) switch (c) { case 'T': Tflag = need_thr = 1; break; case 't': expr = chain(expr, EXPR_AND, parse_expr(optarg)); break; default: fprintf(stderr, "Usage: %s [-T] [-t test] [msglist ...]\n", argv0); exit(1); } if (optind != argc) for (c = optind; c < argc; c++) expr = chain(expr, EXPR_AND, parse_msglist(argv[c])); if (isatty(0)) i = blaze822_loop1(":", need_thr ? collect : oneline); else i = blaze822_loop(0, 0, need_thr ? collect : oneline); /* print and free last thread */ if (Tflag && thr) do_thr(); fprintf(stderr, "%ld mails tested, %ld picked.\n", i, kept); return 0; } mblaze-0.3.2/mprev000077700000000000000000000000001324061207500151032mlessustar00rootroot00000000000000mblaze-0.3.2/mquote000077500000000000000000000006341324061207500142310ustar00rootroot00000000000000#!/bin/sh # mquote MSG - format MSG as a quotation : ${from:=$(mhdr -d -h x-original-from "$1")} : ${from:=$(mhdr -d -h from "$1")} : ${from:=Someone} printf '%s wrote:\n' "$from" { mshow -R "$1" || mshow -h '' -N "$1"; } | sed -n '/^-- $/,$!p' | # strip signature sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' | # strip empty lines sed 's/^/> /' # prefix with > mblaze-0.3.2/mrep000077700000000000000000000000001324061207500145252mcomustar00rootroot00000000000000mblaze-0.3.2/mscan.c000066400000000000000000000254771324061207500142520ustar00rootroot00000000000000#ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" #include "u8decode.h" static int cols; static wchar_t replacement = L'?'; static char *cur; static char *aliases[32]; static int alias_idx; static int Iflag; static int nflag; static int curyear; static time_t now; static char default_fflag[] = "%c%u%r %-3n %10d %17f %t %2i%s"; static char *fflag = default_fflag; ssize_t u8putstr(FILE *out, char *s, ssize_t l, int pad) { ssize_t ol = l; while (*s && l > 0) { if (*s == '\t') *s = ' '; if ((unsigned)*s < 32 || *s == 127) { // C0 fprintf(out, "%lc", (wint_t)(*s == 127 ? 0x2421 : 0x2400+*s)); s++; l--; } else { uint32_t c; int r = u8decode(s, &c); if (r < 0) { r = 1; fprintf(out, "%lc", (wint_t)replacement); } else { l -= wcwidth((wchar_t)c); if (l >= 0) fwrite(s, 1, r, out); } s += r; } } if (pad) while (l-- > 0) putc(' ', out); if (l < 0) l = 0; return ol - l; } int itsme(char *v) { int i; char *disp, *addr; while ((v = blaze822_addr(v, &disp, &addr))) for (i = 0; addr && i < alias_idx; i++) if (strcmp(aliases[i], addr) == 0) return 1; return 0; } static int init; void numline(char *file) { if (!init) { // delay loading of the seq until we need to scan the first // file, in case someone in the pipe updated the map before char *seq = blaze822_seq_open(0); blaze822_seq_load(seq); cur = blaze822_seq_cur(); init = 1; } while (*file == ' ' || *file == '\t') file++; long lineno = blaze822_seq_find(file); if (lineno) printf("%ld\n", lineno); else printf("%s\n", file); } static char * fmt_date(struct message *msg, int w, int iso) { static char date[32]; char *v; if (!msg) return ""; v = blaze822_hdr(msg, "date"); if (!v) return "(unknown)"; time_t t = blaze822_date(v); if (t == -1) return "(invalid)"; struct tm *tm; tm = localtime(&t); if (iso) { if (w >= 19) strftime(date, sizeof date, "%Y-%m-%d %H:%M:%S", tm); else if (w >= 16) strftime(date, sizeof date, "%Y-%m-%d %H:%M", tm); else strftime(date, sizeof date, "%Y-%m-%d", tm); } else if (w < 10) { if (tm->tm_year != curyear) strftime(date, sizeof date, "%b%y", tm); else if (t > now || now - t > 86400) strftime(date, sizeof date, "%d%b", tm); else strftime(date, sizeof date, "%H:%M", tm); } else { if (tm->tm_year != curyear) strftime(date, sizeof date, "%Y-%m-%d", tm); else if (t > now || now - t > 86400) strftime(date, sizeof date, "%a %b %e", tm); else strftime(date, sizeof date, "%a %H:%M", tm); } return date; } static char * fmt_subject(struct message *msg, char *file, int strip) { static char subjdec[100]; char *subj = "(no subject)"; char *v; if (!msg) { snprintf(subjdec, sizeof subjdec, "\\_ %s", file); return subjdec; } if ((v = blaze822_hdr(msg, "subject"))) subj = v; blaze822_decode_rfc2047(subjdec, subj, sizeof subjdec - 1, "UTF-8"); if (strip) { size_t i; for (i = 0; subjdec[i]; ) { if (subjdec[i] == ' ') { i++; continue; } else if (strncasecmp("re:", subjdec+i, 3) == 0 || strncasecmp("aw:", subjdec+i, 3) == 0) { i += 3; continue; } else if (strncasecmp("fwd:", subjdec+i, 4) == 0) { i += 4; continue; } break; } return subjdec + i; } return subjdec; } static char * fmt_from(struct message *msg) { static char fromdec[256]; char *from = "(unknown)"; char *v, *w; if (!msg) return ""; if ((v = blaze822_hdr(msg, "from"))) { blaze822_decode_rfc2047(fromdec, v, sizeof fromdec - 1, "UTF-8"); fromdec[sizeof fromdec - 1] = 0; from = fromdec; if (itsme(fromdec) && ((w = blaze822_hdr(msg, "to")))) { memcpy(fromdec, "TO:", 4); blaze822_decode_rfc2047(fromdec + 3, w, sizeof fromdec - 1 - 3, "UTF-8"); from = fromdec; } else { char *disp, *addr; blaze822_addr(fromdec, &disp, &addr); if (disp) from = disp; else if (addr) from = addr; } } return from; } static char * fmt_to_flag(struct message *msg) { char *v; if (!msg || !alias_idx) return " "; if ((v = blaze822_hdr(msg, "to")) && itsme(v)) return ">"; else if ((v = blaze822_hdr(msg, "cc")) && itsme(v)) return "+"; else if ((v = blaze822_hdr(msg, "resent-to")) && itsme(v)) return ":"; else if ((v = blaze822_hdr(msg, "from")) && itsme(v)) return "<"; else return " "; } static ssize_t print_human(intmax_t i, int w) { double d = i / 1024.0; const char *u = "\0\0M\0G\0T\0P\0E\0Z\0Y\0"; while (d >= 1024) { u += 2; d /= 1024.0; } if (d < 1.0) return printf("%*.2f", w, d); else if (!*u) return printf("%*.0f", w, d); else if (d < 10.0) return printf("%*.1f%s", w-1, d, u); else return printf("%*.0f%s", w-1, d, u); } void oneline(char *file) { if (!init) { // delay loading of the seq until we need to scan the first // file, in case someone in the pipe updated the map before char *seq = blaze822_seq_open(0); blaze822_seq_load(seq); cur = blaze822_seq_cur(); init = 1; } int indent = 0; while (*file == ' ' || *file == '\t') { indent++; file++; } struct message *msg = blaze822(file); char *flags = msg ? strstr(file, ":2,") : 0; if (!flags) flags = ""; else flags += 3; int wleft = cols; char *f; for (f = fflag; *f; f++) { if (*f == '\\') { f++; switch (*f) { case 'n': putchar('\n'); wleft = cols; break; case 't': putchar('\t'); wleft -= (8 - wleft % 8); break; default: putchar('\\'); wleft--; putchar(*f); wleft--; } continue; } if (*f != '%') { putchar(*f); wleft--; continue; } f++; int w = 0; if ((*f >= '0' && *f <= '9') || *f == '-') { errno = 0; char *e; w = strtol(f, &e, 10); if (errno != 0) w = 0; else f = e; } if (!*f) break; switch (*f) { case '%': putchar('%'); wleft--; break; case 'c': if (cur && strcmp(cur, file) == 0) putchar('>'); else putchar(' '); wleft--; break; case 'u': // unseen if (strchr(flags, 'F')) putchar('*'); else if (msg && !strchr(flags, 'S')) putchar('.'); else if (strchr(flags, 'T')) putchar('x'); else putchar(' '); wleft--; break; case 'r': // replied if (strchr(flags, 'R')) putchar('-'); else if (strchr(flags, 'P')) putchar(':'); else putchar(' '); wleft--; break; case 't': // to-flag wleft -= printf("%s", fmt_to_flag(msg)); break; case 'M': // raw Maildir flags if (!w) w = -3; wleft -= printf("%*s", w, flags); break; case 'n': { long lineno = msg ? blaze822_seq_find(file) : 0; if (lineno) wleft -= printf("%*ld", w, lineno); else wleft -= printf("%*s", w, ""); } break; case 'd': case 'D': if (!w) w = 10; wleft -= printf("%*s", w, fmt_date(msg, w, Iflag || *f == 'D')); break; case 'f': if (w < 0) w += wleft; if (w) wleft -= u8putstr(stdout, fmt_from(msg), w, 1); else wleft -= u8putstr(stdout, fmt_from(msg), wleft, 0); break; case 'i': { int z; if (!w) w = 1; if (indent >= 10) { wleft -= printf("..%2d..", indent); indent = 10 - 6/w; } for (z = 0; z < w*indent; z++) putchar(' '); wleft -= w*indent; } break; case 's': case 'S': if (w < 0) w += wleft; if (w) wleft -= u8putstr(stdout, fmt_subject(msg, file, *f == 'S'), w, 1); else wleft -= u8putstr(stdout, fmt_subject(msg, file, *f == 'S'), wleft, 0); break; case 'b': { struct stat st; if (msg) { if (stat(file, &st) != 0) st.st_size = 0; wleft -= print_human(st.st_size, w); } else { wleft -= printf("%.*s", w, ""); } } break; case 'F': { char *e = file + strlen(file); while (file < e && *e != '/') e--; e--; while (file < e && *e != '/') e--; while (file < e && *e == '/') e--; char *b = e; e++; while (file < b && *b != '/') b--; if (*b == '/') b++; if (*b == '.') b++; if (w) { if (w < 0) w = -w; wleft -= printf("%*.*s", -w, (int)(e-b < w ? e-b : w), b); } else { wleft -= printf("%.*s", (int)(e-b), b); } } break; case 'R': if (w) wleft -= printf("%*.*s", w, w, file); else wleft -= printf("%s", file); break; case 'I': { char *m = msg ? blaze822_hdr(msg, "message-id") : 0; if (!m) m = "(unknown)"; if (w) wleft -= printf("%*.*s", w, w, m); else wleft -= printf("%s", m); } break; default: putchar('%'); putchar(*f); wleft -= 2; } } printf("\n"); blaze822_free(msg); } int main(int argc, char *argv[]) { pid_t pid1 = -1; int c; while ((c = getopt(argc, argv, "If:n")) != -1) switch (c) { case 'I': Iflag++; break; case 'f': fflag = optarg; break; case 'n': nflag = 1; break; default: fprintf(stderr, "Usage: mscan [-n] [-f format] [-I] [msgs...]\n"); exit(1); } if (nflag) { if (argc == optind && isatty(0)) blaze822_loop1(":", numline); else blaze822_loop(argc-optind, argv+optind, numline); return 0; } now = time(0); struct tm *tm = localtime(&now); curyear = tm->tm_year; setlocale(LC_ALL, ""); // for wcwidth later if (wcwidth(0xfffd) > 0) replacement = 0xfffd; struct winsize w; int ttyfd = open("/dev/tty", O_RDONLY | O_NOCTTY); if (ttyfd >= 0 && ioctl(ttyfd, TIOCGWINSZ, &w) == 0) { cols = w.ws_col; char *pg; pg = getenv("MBLAZE_PAGER"); if (!pg) pg = getenv("PAGER"); if (pg && *pg && strcmp(pg, "cat") != 0) { pid1 = pipeto(pg); if (pid1 < 0) fprintf(stderr, "mscan: spawning pager '%s': %s\n", pg, strerror(errno)); } } if (ttyfd >= 0) close(ttyfd); if (getenv("COLUMNS")) cols = atoi(getenv("COLUMNS")); if (cols <= 40) cols = 80; char *f = blaze822_home_file("profile"); struct message *config = blaze822(f); if (config) { char *v, *d, *a; if ((v = blaze822_hdr(config, "local-mailbox"))) while (alias_idx < (int)(sizeof aliases / sizeof aliases[0]) && (v = blaze822_addr(v, &d, &a))) if (a) aliases[alias_idx++] = strdup(a); if ((v = blaze822_hdr(config, "alternate-mailboxes"))) while (alias_idx < (int)(sizeof aliases / sizeof aliases[0]) && (v = blaze822_addr(v, &d, &a))) if (a) aliases[alias_idx++] = strdup(a); if ((v = blaze822_hdr(config, "scan-format"))) if (fflag == default_fflag) // NB. == fflag = v; } long i; if (argc == optind && isatty(0)) i = blaze822_loop1(":", oneline); else i = blaze822_loop(argc-optind, argv+optind, oneline); fprintf(stderr, "%ld mails scanned\n", i); if (pid1 > 0) pipeclose(pid1); return 0; } mblaze-0.3.2/msed.c000066400000000000000000000134361324061207500140710ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "blaze822.h" static char *expr; char * subst(char *str, char *srch, char *repl, char *flags) { static char buf[4096]; char *bufe = buf + sizeof buf; int dflag = !!strchr(flags, 'd'); int gflag = !!strchr(flags, 'g'); int iflag = !!strchr(flags, 'i'); #define APP(o, l) do { if (bufe-b < (ssize_t)l) return str; memcpy(b, str+i+o, l); b += l; } while (0) #define APPC(c) do { if (b >= bufe) return str; *b++ = c; } while (0) regex_t srchrx; regmatch_t pmatch[10]; if (regcomp(&srchrx, srch, iflag ? REG_ICASE : 0) != 0) return str; char *b = buf; regoff_t i = 0; while (1) { if (regexec(&srchrx, str+i, 9, pmatch, 0) != 0) break; if (dflag) return 0; APP(0, pmatch[0].rm_so); char *t = repl; while (*t) { // & == \0 if (*t == '&' || (*t == '\\' && isdigit(*(t+1)))) { int n; if (*t == '&') { t++; n = 0; } else { t++; n = *t++ - '0'; } APP(pmatch[n].rm_so, pmatch[n].rm_eo - pmatch[n].rm_so); } else if (*t == '\\' && *(t+1)) { t++; APPC(*t++); } else { APPC(*t++); } } i += pmatch[0].rm_eo; // advance to end of match if (!gflag) break; } if (i > 0) { // any match? APP(0, strlen(str + i)); *b = 0; return buf; } return str; } void printhdr(char *hdr, int rest) { int uc = 1; while (*hdr && *hdr != ':') { putc(uc ? toupper(*hdr) : *hdr, stdout); uc = (*hdr == '-'); hdr++; } if (rest) { printf("%s\n", hdr); } } void sed(char *file) { struct message *msg = blaze822_file(file); if (!msg) return; char *h = 0; while ((h = blaze822_next_header(msg, h))) { regex_t headerrx; char headersel[1024]; char *v = strchr(h, ':'); if (!v) continue; v++; while (*v && (*v == ' ' || *v == '\t')) v++; v = strdup(v); char *e = expr; while (*e) { while (*e && (*e == ' ' || *e == '\t' || *e == '\n' || *e == ';')) e++; *headersel = 0; if (*e == '/') { e++; char *s = e; // parse_headers, sets headersel while (*e && *e != '/') e++; snprintf(headersel, sizeof headersel, "^(%.*s)*:", (int)(e-s), s); for (s = headersel; *s && *(s+1); s++) if (*s == ':') *s = '|'; int rv; if ((rv = regcomp(&headerrx, headersel, REG_EXTENDED)) != 0) { char buf[100]; regerror(rv, &headerrx, buf, sizeof buf); fprintf(stderr, "msed: %s\n", buf); exit(1); } if (*e) e++; } char sep; char *s; if (!*headersel || regexec(&headerrx, h, 0, 0, 0) == 0) { switch (*e) { case 'd': free(v); v = 0; break; case 'a': // skipped here; e++; if ((*e == ' ' || *e == ';' || *e == '\n' || !*e)) { break; } sep = *e++; if (!sep) { fprintf(stderr, "msed: unterminated a command\n"); exit(1); } while (*e && *e != sep) e++; if (*e == sep) e++; if (!(*e == ' ' || *e == ';' || *e == '\n' || !*e)) { fprintf(stderr, "msed: unterminated a command\n"); exit(1); } break; case 'c': sep = *++e; s = ++e; while (*e && *e != sep) e++; free(v); v = strndup(s, e-s); break; case 's': sep = *++e; s = ++e; while (*e && *e != sep) e++; char *t = ++e; while (*e && *e != sep) e++; char *u = ++e; while (*e == 'd' || *e == 'g' || *e == 'i') e++; if (!(*e == ' ' || *e == ';' || *e == '\n' || !*e)) { fprintf(stderr, "msed: unterminated s command\n"); exit(1); } // XXX stack allocate char *from = strndup(s, t-s-1); char *to = strndup(t, u-t-1); char *flags = strndup(u, e-u); char *ov = v; char *r = subst(ov, from, to, flags); if (r) v = strdup(r); else v = 0; free(ov); free(from); free(to); free(flags); break; default: fprintf(stderr, "msed: unknown command: '%c'\n", *e); exit(1); } } while (*e && *e != ';' && *e != '\n') e++; } if (v) { printhdr(h, 0); printf(": %s\n", v); free(v); } } // loop, do all a// char *hs, *he; char *e = expr; while (*e) { while (*e && (*e == ' ' || *e == '\t' || *e == '\n' || *e == ';')) e++; hs = he = 0; if (*e == '/') { e++; hs = e; // parse_headers, sets headersel while (*e && *e != '/') e++; he = e; if (*e) e++; } char sep; char *s; char *h = 0; char *v = 0; switch (*e) { case 'a': if (he != hs) { h = strndup(hs, he-hs); } else { fprintf(stderr, "msed: used command a without header name\n"); exit(1); } e++; if (*e == ' ' || *e == '\t' || *e == '\n' || *e == ';' || !*e) { fprintf(stderr, "msed: no header value for %s\n", h); exit(1); } else { sep = *e; if (!sep) { fprintf(stderr, "msed: unterminated a command\n"); exit(1); } s = ++e; while (*e && *e != sep) e++; v = strndup(s, e-s); } if (blaze822_chdr(msg, h)) { free(h); free(v); break; } printhdr(h, 0); printf(": %s\n", v); free(h); free(v); break; case 'c': case 'd': case 's': // ignore here; break; } while (*e && *e != ';' && *e != '\n') e++; } printf("\n"); fwrite(blaze822_body(msg), 1, blaze822_bodylen(msg), stdout); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "")) != -1) switch (c) { default: fprintf(stderr, "Usage: msed [expr] [msgs...]\n"); exit(1); } expr = argv[optind]; optind++; if (argc == optind && isatty(0)) blaze822_loop1(".", sed); else blaze822_loop(argc-optind, argv+optind, sed); return 0; } mblaze-0.3.2/mseq.c000066400000000000000000000142031324061207500140770ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" static int fflag; static int rflag; static int Aflag; static char *cflag; static char *Cflag; static int Sflag; struct name { char *id; char *file; }; static void *names; int nameorder(const void *a, const void *b) { struct name *ia = (struct name *)a; struct name *ib = (struct name *)b; return strcmp(ia->id, ib->id); } char * namefind(char *id) { struct name key, **result; key.id = id; if (!(result = tfind(&key, &names, nameorder))) return 0; return (*result)->file; } void namescan(char *dir) { DIR *fd; struct dirent *d; fd = opendir(dir); if (!fd) return; while ((d = readdir(fd))) { #if defined(DT_REG) && defined(DT_UNKNOWN) if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN) continue; #endif if (d->d_name[0] == '.') continue; char file[PATH_MAX]; snprintf(file, sizeof file, "%s/%s", dir, d->d_name); char *e; if ((e = strstr(d->d_name, ":2,"))) *e = 0; struct name *c = malloc(sizeof (struct name)); c->id = strdup(d->d_name); c->file = strdup(file); tsearch(c, &names, nameorder); } closedir(fd); // add dir so know we scanned it already struct name *c = malloc(sizeof (struct name)); c->id = c->file = strdup(dir); tsearch(c, &names, nameorder); } char * search(char *file) { char dir[PATH_MAX]; char *e; if ((e = strrchr(file, '/'))) { snprintf(dir, sizeof dir, "%.*s", (int)(e-file), file); file = e+1; } else { snprintf(dir, sizeof dir, "."); } if (!namefind(dir)) namescan(dir); if ((e = strstr(file, ":2,"))) *e = 0; return namefind(file); } /* strategy to find a Maildir file name: - if file exists, all good - try a few common different flags - index the containing dir, try to search for the id - if its in new/, try the same in cur/ */ int fix(FILE *out, char *file) { int i; for (i = 0; *file == ' '; i++, file++) ; char buf[PATH_MAX]; char *bufptr = buf; if (*file == '<' || access(file, F_OK) == 0) { bufptr = file; goto ok; } char *e; char *sep; if ((e = strstr(file, ":2,"))) { sep = ""; e[3] = 0; } else { sep = ":2,"; } snprintf(buf, sizeof buf, "%s%s", file, sep); if (access(buf, F_OK) == 0) goto ok; snprintf(buf, sizeof buf, "%s%sS", file, sep); if (access(buf, F_OK) == 0) goto ok; snprintf(buf, sizeof buf, "%s%sRS", file, sep); if (access(buf, F_OK) == 0) goto ok; snprintf(buf, sizeof buf, "%s%sFS", file, sep); if (access(buf, F_OK) == 0) goto ok; if ((bufptr = search(file))) goto ok; char *ee = strrchr(file, '/'); if (ee >= file + 3 && ee[-3] == 'n' && ee[-2] == 'e' && ee[-1] == 'w') { ee[-3] = 'c'; ee[-2] = 'u'; ee[-1] = 'r'; return fix(out, file-i); } return 0; ok: while (i--) putc(' ', out); fprintf(out, "%s\n", bufptr); return 1; } void cat(FILE *src, FILE *dst) { char buf[4096]; size_t rd; rewind(src); while ((rd = fread(buf, 1, sizeof buf, src)) > 0) fwrite(buf, 1, rd, dst); if (!feof(src)) { perror("mseq: fread"); exit(2); } } int stdinmode() { char *line = 0; char *l; size_t linelen = 0; ssize_t rd; FILE *outfile; char tmpfile[PATH_MAX]; char oldfile[PATH_MAX]; char *seqfile = 0; if (Sflag) { seqfile = getenv("MAILSEQ"); if (!seqfile) seqfile = blaze822_home_file("seq"); snprintf(tmpfile, sizeof tmpfile, "%s-", seqfile); snprintf(oldfile, sizeof oldfile, "%s.old", seqfile); int fd = open(tmpfile, O_RDWR | O_EXCL | O_CREAT, 0666); if (fd < 0) { fprintf(stderr, "mseq: Could not create temporary sequence file '%s': %s.\n", tmpfile, strerror(errno)); fprintf(stderr, "mseq: Ensure %s exists and is writable.\n", blaze822_home_file("")); exit(2); } outfile = fdopen(fd, "w+"); if (Aflag) { FILE *seq = fopen(seqfile, "r"); if (seq) { cat(seq, outfile); fclose(seq); } } } else { outfile = stdout; } while ((rd = getdelim(&line, &linelen, '\n', stdin)) != -1) { if (line[rd-1] == '\n') line[rd-1] = 0; l = line; if (rflag) while (*l == ' ' || *l == '\t') l++; if (fflag) fix(outfile, l); else fprintf(outfile, "%s\n", l); } if (Sflag) { fflush(outfile); if (rename(seqfile, oldfile) < 0 && errno != ENOENT) { perror("mseq: rename"); exit(2); } if (rename(tmpfile, seqfile) < 0) { perror("mseq: rename"); exit(2); } if (!isatty(1)) cat(outfile, stdout); fclose(outfile); } free(line); return 0; } void overridecur(char *file) { static int once = 0; if (once++) return; while (*file == ' ') file++; setenv("MAILDOT", file, 1); } void setcur(char *file) { static int once = 0; if (once++) return; while (*file == ' ') file++; unsetenv("MAILDOT"); blaze822_seq_setcur(file); } int main(int argc, char *argv[]) { int c; while ((c = getopt(argc, argv, "c:frAC:S")) != -1) switch (c) { case 'c': cflag = optarg; break; case 'f': fflag = 1; break; case 'r': rflag = 1; break; case 'A': Sflag = Aflag = 1; break; case 'C': Cflag = optarg; break; case 'S': Sflag = 1; break; default: usage: fprintf(stderr, "Usage: mseq [-fr] [-c msg] [msgs...]\n" " mseq -S [-fr] < sequence\n" " mseq -A [-fr] < sequence\n" " mseq -C msg\n" ); exit(1); } if (cflag) blaze822_loop1(cflag, overridecur); if (Cflag) { blaze822_loop1(Cflag, setcur); return 0; } if (Sflag && optind != argc) { fprintf(stderr, "error: -S/-A doesn't take arguments.\n"); goto usage; } if (optind == argc && !isatty(0)) return stdinmode(); char *seq = blaze822_seq_open(0); if (!seq) return 1; int i; char *f; char *a; struct blaze822_seq_iter iter = { 0 }; if (optind == argc) { a = ":"; i = argc; goto hack; } for (i = optind; i < argc; i++) { a = argv[i]; hack: if (strchr(a, '/')) { printf("%s\n", a); continue; } while ((f = blaze822_seq_next(seq, a, &iter))) { char *s = f; if (rflag) while (*s == ' ' || *s == '\t') s++; if (fflag) fix(stdout, s); else printf("%s\n", s); free(f); } } return 0; } mblaze-0.3.2/mshow.c000066400000000000000000000410251324061207500142710ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" static int rflag; static int Rflag; static int qflag; static int Fflag; static int Hflag; static int Lflag; static int Nflag; static int tflag; static int nflag; static char defaulthflags[] = "from:subject:to:cc:date:reply-to:"; static char *hflag = defaulthflags; static char *xflag; static char *Oflag; static char fallback_ct[] = "text/plain"; struct message *filters; static int mimecount; static int safe_output; static int reply_found; static char defaultAflags[] = "text/plain:text/html"; static char *Aflag = defaultAflags; static int printable(int c) { return (unsigned)c-0x20 < 0x5f; } size_t print_ascii(char *body, size_t bodylen) { if (safe_output) { safe_u8putstr(body, bodylen, stdout); return bodylen; } else { return fwrite(body, 1, bodylen, stdout); } } void printhdr(char *hdr) { int uc = 1; while (*hdr && *hdr != ':' && printable(*hdr)) { putc(uc ? toupper(*hdr) : *hdr, stdout); uc = (*hdr == '-'); hdr++; } if (*hdr) { print_ascii(hdr, strlen(hdr)); fputc('\n', stdout); } } void print_u8recode(char *body, size_t bodylen, char *srcenc) { iconv_t ic; ic = iconv_open("UTF-8", srcenc); if (ic == (iconv_t)-1) { printf("unsupported encoding: %s\n", srcenc); return; } char final_char = 0; char buf[4096]; while (bodylen > 0) { char *bufptr = buf; size_t buflen = sizeof buf; size_t r = iconv(ic, &body, &bodylen, &bufptr, &buflen); if (bufptr != buf) { print_ascii(buf, bufptr-buf); final_char = bufptr[-1]; } if (r != (size_t)-1) { // done, flush iconv bufptr = buf; buflen = sizeof buf; r = iconv(ic, 0, 0, &bufptr, &buflen); if (bufptr != buf) { print_ascii(buf, bufptr-buf); final_char = bufptr[-1]; } if (r != (size_t)-1) break; } if (r == (size_t)-1 && errno != E2BIG) { perror("iconv"); break; } } if (final_char != '\n') printf("\n"); iconv_close(ic); } char * mimetype(char *ct) { char *s; if (!ct) return 0; for (s = ct; *s && *s != ';' && *s != ' ' && *s != '\t'; s++) ; return strndup(ct, s-ct); } char * tlmimetype(char *ct) { char *s; if (!ct) return 0; for (s = ct; *s && *s != ';' && *s != ' ' && *s != '\t' && *s != '/'; s++) ; return strndup(ct, s-ct); } char * mime_filename(struct message *msg) { static char buf[512]; char *v; char *filename = 0; if ((v = blaze822_hdr(msg, "content-disposition"))) { if (blaze822_mime2231_parameter(v, "filename", buf, sizeof buf, "UTF-8")) filename = buf; } else if ((v = blaze822_hdr(msg, "content-type"))) { if (blaze822_mime2231_parameter(v, "name", buf, sizeof buf, "UTF-8")) filename = buf; } return filename; } static void choose_alternative(struct message *msg, int depth); void print_filename(char *filename) { if (filename) { printf(" name=\""); safe_u8putstr(filename, strlen(filename), stdout); printf("\""); } } blaze822_mime_action render_mime(int depth, struct message *msg, char *body, size_t bodylen) { char *ct = blaze822_hdr(msg, "content-type"); if (!ct) ct = fallback_ct; char *mt = mimetype(ct); char *tlmt = tlmimetype(ct); char *filename = mime_filename(msg); mimecount++; if (!Nflag) { int i; for (i = 0; i < depth+1; i++) printf("--- "); printf("%d: %s size=%zd", mimecount, mt, bodylen); print_filename(filename); } char *cmd; blaze822_mime_action r = MIME_CONTINUE; if (filters && ((cmd = blaze822_chdr(filters, mt)) || (cmd = blaze822_chdr(filters, tlmt)))) { char *charset = 0, *cs, *cse; if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) { charset = strndup(cs, cse-cs); if (!Nflag) printf(" charset=\"%s\"", charset); setenv("PIPE_CHARSET", charset, 1); free(charset); } setenv("PIPE_CONTENTTYPE", ct, 1); char *output; size_t outlen; int e = filter(body, bodylen, cmd, &output, &outlen); if (e == 0 || e == 62) { // replace output (62 == raw) if (!Nflag) printf(" render=\"%s\" ---\n", cmd); if (outlen) { if (e == 0) print_ascii(output, outlen); else fwrite(output, 1, outlen, stdout); if (output[outlen-1] != '\n') putchar('\n'); } } else if (e == 63) { // skip filter free(output); goto nofilter; } else if (e == 64) { // decode output again if (!Nflag) printf(" filter=\"%s\" ---\n", cmd); struct message *imsg = blaze822_mem(output, outlen); if (imsg) blaze822_walk_mime(imsg, depth+1, render_mime); blaze822_free(imsg); } else if (e >= 65 && e <= 80) { // choose N-64th part struct message *imsg = 0; int n = e - 64; if (!Nflag) printf(" selector=\"%s\" part=%d ---\n", cmd, n); while (blaze822_multipart(msg, &imsg)) { if (--n == 0) blaze822_walk_mime(imsg, depth+1, render_mime); } blaze822_free(imsg); } else { if (!Nflag) printf(" filter=\"%s\" FAILED status=%d", cmd, e); free(output); goto nofilter; } free(output); r = MIME_PRUNE; } else { nofilter: if (!Nflag) printf(" ---\n"); if (strncmp(ct, "text/", 5) == 0 || strncmp(ct, "message/delivery-status", 23) == 0) { char *charset = 0, *cs, *cse; if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) charset = strndup(cs, cse-cs); if (!charset || strcasecmp(charset, "utf-8") == 0 || strcasecmp(charset, "utf8") == 0 || strcasecmp(charset, "us-ascii") == 0) { print_ascii(body, bodylen); if (bodylen > 0 && body[bodylen-1] != '\n') putchar('\n'); } else { print_u8recode(body, bodylen, charset); } free(charset); } else if (strncmp(ct, "message/rfc822", 14) == 0) { struct message *imsg = blaze822_mem(body, bodylen); char *h = 0; while (imsg && (h = blaze822_next_header(imsg, h))) { char d[4096]; blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8"); printhdr(d); } printf("\n"); } else if (strncmp(ct, "multipart/alternative", 21) == 0) { choose_alternative(msg, depth); r = MIME_PRUNE; } else if (strncmp(ct, "multipart/", 10) == 0) { ; // default blaze822_mime_walk action } else { printf("no filter or default handler\n"); } } free(mt); free(tlmt); return r; } static void choose_alternative(struct message *msg, int depth) { int n = 1; int m = 0; char *p = Aflag + strlen(Aflag); struct message *imsg = 0; while (blaze822_multipart(msg, &imsg)) { m++; char *ict = blaze822_hdr(imsg, "content-type"); if (!ict) ict = fallback_ct; char *imt = mimetype(ict); char *s = strstr(Aflag, imt); if (s && s < p && (s[strlen(imt)] == 0 || s[strlen(imt)] == ':')) { p = s; n = m; } free(imt); } blaze822_free(imsg); imsg = 0; while (blaze822_multipart(msg, &imsg)) if (--n == 0) blaze822_walk_mime(imsg, depth+1, render_mime); blaze822_free(imsg); } blaze822_mime_action reply_mime(int depth, struct message *msg, char *body, size_t bodylen) { (void)depth; char *ct = blaze822_hdr(msg, "content-type"); char *mt = mimetype(ct); char *tlmt = tlmimetype(ct); if (!ct || strncmp(ct, "text/plain", 10) == 0) { char *charset = 0, *cs, *cse; if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) charset = strndup(cs, cse-cs); if (!charset || strcasecmp(charset, "utf-8") == 0 || strcasecmp(charset, "utf8") == 0 || strcasecmp(charset, "us-ascii") == 0) print_ascii(body, bodylen); else print_u8recode(body, bodylen, charset); reply_found++; free(charset); } free(mt); free(tlmt); return MIME_CONTINUE; } blaze822_mime_action list_mime(int depth, struct message *msg, char *body, size_t bodylen) { (void)body; char *ct = blaze822_hdr(msg, "content-type"); if (!ct) ct = fallback_ct; char *mt = mimetype(ct); char *filename = mime_filename(msg); printf(" %*.s%d: %s size=%zd", depth*2, "", ++mimecount, mt, bodylen); print_filename(filename); printf("\n"); free(mt); return MIME_CONTINUE; } void list(char *file) { while (*file == ' ' || *file == '\t') file++; struct message *msg = blaze822_file(file); if (!msg) return; mimecount = 0; printf("%s\n", file); blaze822_walk_mime(msg, 0, list_mime); } void reply(char *file) { while (*file == ' ' || *file == '\t') file++; struct message *msg = blaze822_file(file); if (!msg) return; reply_found = 0; blaze822_walk_mime(msg, 0, reply_mime); if (!reply_found) exit(1); } static int extract_argc; static char **extract_argv; static int extract_stdout; static const char * basenam(const char *s) { char *r = strrchr(s, '/'); return r ? r + 1 : s; } static int writefile(char *name, char *buf, ssize_t len) { int fd = open(basenam(name), O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd == -1) { perror("open"); return -1; } ssize_t wr = 0, n; do { if ((n = write(fd, buf + wr, len - wr)) == -1) { if (errno == EINTR) { continue; } else { perror("write"); return -1; } } wr += n; } while (wr < len); close(fd); return 0; } blaze822_mime_action extract_mime(int depth, struct message *msg, char *body, size_t bodylen) { (void)depth; char *filename = mime_filename(msg); mimecount++; if (extract_argc == 0) { if (extract_stdout) { // output all parts fwrite(body, 1, bodylen, stdout); } else { // extract all named attachments if (filename) { safe_u8putstr(filename, strlen(filename), stdout); printf("\n"); writefile(filename, body, bodylen); } } } else { int i; for (i = 0; i < extract_argc; i++) { char *a = extract_argv[i]; char *b; errno = 0; long d = strtol(a, &b, 10); if (errno == 0 && !*b && d == mimecount) { // extract by id if (extract_stdout) { if (rflag) { fwrite(blaze822_orig_header(msg), 1, blaze822_headerlen(msg), stdout); if (blaze822_orig_header(msg)[ blaze822_headerlen(msg)] == '\r') printf("\r\n\r\n"); else printf("\n\n"); fwrite(blaze822_body(msg), 1, blaze822_bodylen(msg), stdout); } else { fwrite(body, 1, bodylen, stdout); } } else { char buf[255]; char *bufptr; if (filename) { bufptr = filename; } else { snprintf(buf, sizeof buf, "attachment%d", mimecount); bufptr = buf; } printf("%s\n", bufptr); writefile(bufptr, body, bodylen); } } else if (filename && fnmatch(a, filename, FNM_PATHNAME) == 0) { // extract by name if (extract_stdout) { if (rflag) { fwrite(blaze822_orig_header(msg), 1, blaze822_headerlen(msg), stdout); printf("\n\n"); fwrite(blaze822_body(msg), 1, blaze822_bodylen(msg), stdout); } else { fwrite(body, 1, bodylen, stdout); } } else { safe_u8putstr(filename, strlen(filename), stdout); printf("\n"); writefile(filename, body, bodylen); } } } } return MIME_CONTINUE; } void extract_cb(char *file) { struct message *msg = blaze822_file(file); if (!msg) return; mimecount = 0; blaze822_walk_mime(msg, 0, extract_mime); } void extract(char *file, int argc, char **argv, int use_stdout) { extract_argc = argc; extract_argv = argv; extract_stdout = use_stdout; blaze822_loop1(file, extract_cb); } static char *newcur; static void print_date_header(char *v) { static time_t now = -1; if (now == -1) { setenv("TZ", "", 1); tzset(); now = time(0); } printf("Date: "); print_ascii(v, strlen(v)); time_t t = blaze822_date(v); if (t == -1) { printf(" (invalid)"); } else { printf(" ("); time_t d = t < now ? now - t : t - now; char l; if (d > 60*60*24*7*52) l = 'y'; else if (d > 60*60*24*7) l = 'w'; else if (d > 60*60*24) l = 'd'; else if (d > 60*60) l = 'h'; else if (d > 60) l = 'm'; else l = 's'; int p = 3; long z; switch (l) { case 'y': z = d / (60*60*24*7*52); d = d % (60*60*24*7*52); if (z > 0) { printf("%ld year%s", z, z > 1 ? "s" : ""); if (!--p) break; printf(", "); } /* FALL THROUGH */ case 'w': z = d / (60*60*24*7); d = d % (60*60*24*7); if (z > 0) { printf("%ld week%s", z, z > 1 ? "s" : ""); if (!--p) break; printf(", "); } /* FALL THROUGH */ case 'd': z = d / (60*60*24); d = d % (60*60*24); if (z > 0) { printf("%ld day%s", z, z > 1 ? "s" : ""); if (!--p) break; printf(", "); } /* FALL THROUGH */ case 'h': z = d / (60*60); d = d % (60*60); if (z > 0) { printf("%ld hour%s", z, z > 1 ? "s" : ""); if (!--p) break; printf(", "); } /* FALL THROUGH */ case 'm': z = d / (60); d = d % (60); if (z > 0) { printf("%ld minute%s", z, z > 1 ? "s" : ""); if (!--p) break; printf(", "); } /* FALL THROUGH */ case 's': z = d; printf("%ld second%s", z, z > 1 ? "s" : ""); } if (t < now) printf(" ago)"); else printf(" in the future)"); } printf("\n"); } static void print_decode_header(char *h, char *v) { char d[16384]; blaze822_decode_rfc2047(d, v, sizeof d, "UTF-8"); printhdr(h); fputc(':', stdout); fputc(' ', stdout); print_ascii(d, strlen(d)); fputc('\n', stdout); } void show(char *file) { struct message *msg; while (*file == ' ' || *file == '\t') file++; if (newcur) { printf("\014\n"); free(newcur); } newcur = strdup(file); if (qflag && !Hflag) msg = blaze822(file); else msg = blaze822_file(file); if (!msg) { fprintf(stderr, "mshow: %s: %s\n", file, strerror(errno)); return; } if (Hflag) { // raw headers fwrite(blaze822_orig_header(msg), 1, blaze822_headerlen(msg), stdout); printf("\n"); } else if (Lflag) { // all headers char *h = 0; while ((h = blaze822_next_header(msg, h))) { char d[4096]; blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8"); printhdr(d); } } else { // selected headers char *h = hflag; char *v; while (*h) { char *n = strchr(h, ':'); if (n) *n = 0; v = blaze822_chdr(msg, h); if (v) { if (strcasecmp("date", h) == 0) print_date_header(v); else print_decode_header(h, v); } if (n) { *n = ':'; h = n + 1; } else { break; } } } if (qflag) // no body goto done; printf("\n"); if (rflag) { // raw body print_ascii(blaze822_body(msg), blaze822_bodylen(msg)); goto done; } mimecount = 0; blaze822_walk_mime(msg, 0, render_mime); done: blaze822_free(msg); } int main(int argc, char *argv[]) { pid_t pid1 = -1, pid2 = -1; int c; while ((c = getopt(argc, argv, "h:A:qrtFHLNx:O:Rn")) != -1) switch (c) { case 'h': hflag = optarg; break; case 'A': Aflag = optarg; break; case 'q': qflag = 1; break; case 'r': rflag = 1; break; case 'F': Fflag = 1; break; case 'H': Hflag = 1; break; case 'L': Lflag = 1; break; case 'N': Nflag = 1; break; case 't': tflag = 1; break; case 'x': xflag = optarg; break; case 'O': Oflag = optarg; break; case 'R': Rflag = 1; break; case 'n': nflag = 1; break; default: fprintf(stderr, "Usage: mshow [-h headers] [-A mimetypes] [-nqrFHLN] [msgs...]\n" " mshow -x msg parts...\n" " mshow -O msg parts...\n" " mshow -t msgs...\n" " mshow -R msg\n" ); exit(1); } if (!rflag && !Oflag && !Rflag) safe_output = 1; if (safe_output && isatty(1)) { char *pg; pg = getenv("MBLAZE_PAGER"); if (!pg) { pg = getenv("PAGER"); if (pg && strcmp(pg, "less") == 0) { static char lesscmd[] = "less -RFXe"; pg = lesscmd; } } if (pg && *pg && strcmp(pg, "cat") != 0) { pid2 = pipeto(pg); if (pid2 < 0) fprintf(stderr, "mshow: spawning pager '%s': %s\n", pg, strerror(errno)); else if (!getenv("MBLAZE_NOCOLOR")) pid1 = pipeto("mcolor"); // ignore error } } if (xflag) { // extract extract(xflag, argc-optind, argv+optind, 0); } else if (Oflag) { // extract to stdout extract(Oflag, argc-optind, argv+optind, 1); } else if (tflag) { // list if (argc == optind && isatty(0)) blaze822_loop1(".", list); else blaze822_loop(argc-optind, argv+optind, list); } else if (Rflag) { // render for reply blaze822_loop(argc-optind, argv+optind, reply); } else { // show if (!(qflag || rflag || Fflag)) { char *f = getenv("MAILFILTER"); if (!f) f = blaze822_home_file("filter"); if (f) filters = blaze822(f); } if (argc == optind && isatty(0)) blaze822_loop1(".", show); else blaze822_loop(argc-optind, argv+optind, show); if (!nflag) // don't set cur blaze822_seq_setcur(newcur); } if (pid2 > 0) pipeclose(pid2); if (pid1 > 0) pipeclose(pid1); return 0; } mblaze-0.3.2/msort.c000066400000000000000000000137311324061207500143030ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "blaze822.h" struct mail { char *file; long idx; char *from; char *subj; time_t date; time_t mtime; off_t size; }; struct mail *mails; ssize_t mailalloc = 1024; int idx; int rflag; static int (*sortorders[16])(const void *, const void *); int order_idx; int mystrverscmp(const char *, const char *); char * fetch_subj(char *file) { struct message *msg = blaze822(file); if (!msg) return " (error)"; char *v = blaze822_hdr(msg, "subject"); if (!v) { blaze822_free(msg); return " (no subject)"; } char *ov = 0; char *s; while (v != ov) { while (*v == ' ') v++; if (strncasecmp(v, "Re:", 3) == 0) v += 3; if (strncasecmp(v, "Aw:", 3) == 0) v += 3; if (strncasecmp(v, "Sv:", 3) == 0) v += 3; if (strncasecmp(v, "Wg:", 3) == 0) v += 3; if (strncasecmp(v, "Fwd:", 4) == 0) v += 4; // XXX skip [prefix]? ov = v; } s = strdup(v); blaze822_free(msg); return s; } char * fetch_from(char *file) { char *from = " (unknown)"; struct message *msg = blaze822(file); if (!msg) return " (error)"; char *v = blaze822_hdr(msg, "from"); if (v) { char *disp, *addr; blaze822_addr(v, &disp, &addr); if (disp) from = strdup(disp); else if (addr) from = strdup(addr); } blaze822_free(msg); return from; } time_t fetch_date(char *file) { time_t t = -1; struct message *msg = blaze822(file); if (!msg) return -1; char *v = blaze822_hdr(msg, "date"); if (v) t = blaze822_date(v); blaze822_free(msg); return t; } time_t fetch_mtime(char *file) { struct stat st; if (stat(file, &st) < 0) return -1; return st.st_mtime; } off_t fetch_size(char *file) { struct stat st; if (stat(file, &st) < 0) return 0; return st.st_size; } int subjorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; if (!ia->subj) ia->subj = fetch_subj(ia->file); if (!ib->subj) ib->subj = fetch_subj(ib->file); return strcasecmp(ia->subj, ib->subj); } int fromorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; if (!ia->from) ia->from = fetch_from(ia->file); if (!ib->from) ib->from = fetch_from(ib->file); return strcasecmp(ia->from, ib->from); } int sizeorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; if (!ia->size) ia->size = fetch_size(ia->file); if (!ib->size) ib->size = fetch_size(ib->file); if (ia->size > ib->size) return 1; else if (ia->size < ib->size) return -1; return 0; } int mtimeorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; if (!ia->mtime) ia->mtime = fetch_mtime(ia->file); if (!ib->mtime) ib->mtime = fetch_mtime(ib->file); if (ia->mtime > ib->mtime) return 1; else if (ia->mtime < ib->mtime) return -1; return 0; } int dateorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; if (!ia->date) ia->date = fetch_date(ia->file); if (!ib->date) ib->date = fetch_date(ib->file); if (ia->date > ib->date) return 1; else if (ia->date < ib->date) return -1; return 0; } int fileorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; return mystrverscmp(ia->file, ib->file); } int unreadorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; char *fa = strstr(ia->file, ":2,"); char *fb = strstr(ib->file, ":2,"); int unreada = fa ? !strchr(fa, 'S') : 0; int unreadb = fb ? !strchr(fb, 'S') : 0; return unreada - unreadb; } int flaggedorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; char *fa = strstr(ia->file, ":2,"); char *fb = strstr(ib->file, ":2,"); int unreada = fa ? !!strchr(fa, 'F') : 0; int unreadb = fb ? !!strchr(fb, 'F') : 0; return unreadb - unreada; } int idxorder(const void *a, const void *b) { struct mail *ia = (struct mail *)a; struct mail *ib = (struct mail *)b; if (ia->idx > ib->idx) return 1; else if (ia->idx < ib->idx) return -1; else return 0; } void add(char *file) { if (idx >= mailalloc) { mailalloc *= 2; if (mailalloc < 0) exit(-1); mails = realloc(mails, sizeof (struct mail) * mailalloc); if (!mails) exit(-1); memset(mails+mailalloc/2, 0, sizeof (struct mail) * mailalloc/2); } if (!mails) exit(-1); mails[idx].file = strdup(file); mails[idx].idx = idx; idx++; } int order(const void *a, const void *b) { int i, r; for (i = 0, r = 0; i < order_idx; i++) { r = (sortorders[i])(a, b); if (r != 0) return r; } return idxorder(a, b); } void addorder(int (*sortorder)(const void *, const void *)) { if (order_idx < (int)(sizeof sortorders / sizeof sortorders[0])) sortorders[order_idx++] = sortorder; } int main(int argc, char *argv[]) { int c, i; while ((c = getopt(argc, argv, "fdsFMSUIr")) != -1) switch (c) { case 'f': addorder(fromorder); break; case 'd': addorder(dateorder); break; case 's': addorder(subjorder); break; case 'F': addorder(fileorder); break; case 'M': addorder(mtimeorder); break; case 'S': addorder(sizeorder); break; case 'U': addorder(unreadorder); break; case 'I': addorder(flaggedorder); break; case 'r': rflag = !rflag; break; default: fprintf(stderr, "Usage: msort [-r] [-fdsFMSUI] [msgs...]\n"); exit(1); } mails = calloc(sizeof (struct mail), mailalloc); if (!mails) exit(-1); if (argc == optind && isatty(0)) blaze822_loop1(":", add); else blaze822_loop(argc-optind, argv+optind, add); qsort(mails, idx, sizeof (struct mail), order); if (rflag) for (i = idx-1; i >= 0; i--) printf("%s\n", mails[i].file); else for (i = 0; i < idx; i++) printf("%s\n", mails[i].file); return 0; } mblaze-0.3.2/mthread.c000066400000000000000000000166251324061207500145700ustar00rootroot00000000000000/* jwz-style threading * clean-room implementation of https://www.jwz.org/doc/threading.html * without looking at any code * * subject threading and sibling sorting is not done yet */ #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" static int vflag; static int optional; struct container { char *mid; char *file; struct message *msg; time_t date; struct container *parent; struct container *child; struct container *next; int optional; }; static void *mids; int midorder(const void *a, const void *b) { struct container *ia = (struct container *)a; struct container *ib = (struct container *)b; return strcmp(ia->mid, ib->mid); } char * mid(struct message *msg) { char *v; v = blaze822_hdr(msg, "message-id"); // XXX intern mid? if (v) { char *m; m = strchr(v, '<'); if (!m) return strdup(v); v = strchr(m, '>'); if (!v) return strdup(m); return strndup(m+1, v-m-1); } else { // invent new message-id for internal tracking static long i; char buf[32]; snprintf(buf, sizeof buf, "thread%08ld@localhost", ++i); return strdup(buf); } } struct container * midcont(char *mid) { struct container key, **result; key.mid = mid; if (!(result = tfind(&key, &mids, midorder))) { struct container *c = malloc(sizeof (struct container)); c->mid = mid; c->file = 0; c->msg = 0; c->date = -1; c->optional = 0; c->parent = c->child = c->next = 0; return *(struct container **)tsearch(c, &mids, midorder); } else { return *result; } } struct container * store_id(char *file, struct message *msg) { struct container *c; c = midcont(mid(msg)); c->file = strdup(file); c->msg = msg; c->optional = optional; return c; } int reachable(struct container *child, struct container *parent) { int r = 0; if (strcmp(child->mid, parent->mid) == 0) return 1; if (child->child) r |= reachable(child->child, parent); if (child->next) r |= reachable(child->next, parent); return r; } void thread(char *file) { struct message *msg; while (*file == ' ' || *file == '\t') file++; msg = blaze822(file); if (!msg) return; struct container *c = store_id(file, msg); char *mid = ""; char *v, *m; struct container *parent = 0, *me = 0; if ((v = blaze822_hdr(msg, "date"))) { c->date = blaze822_date(v); } else { c->date = -1; } v = blaze822_hdr(msg, "references"); if (v) { parent = 0; while (1) { m = strchr(v, '<'); if (!m) break; v = strchr(m, '>'); if (!v) break; mid = strndup(m+1, v-m-1); // XXX free? me = midcont(mid); if (me == c) continue; if (parent && !me->parent && !reachable(me, parent) && !reachable(parent, me)) { me->parent = parent; me->next = parent->child; parent->child = me; } parent = me; } } v = blaze822_hdr(msg, "in-reply-to"); char *irt; if (v) { m = strchr(v, '<'); if (!m) goto out; v = strchr(m, '>'); if (!v) goto out; irt = strndup(m+1, v-m-1); if (strcmp(irt, mid) != 0) { parent = midcont(irt); } else { free(irt); } } out: if (parent && parent != c) { struct container *r; // check we don't introduce a new loop if (reachable(parent, c) || reachable(c, parent)) goto out2; if (c->parent == parent) { // already correct goto out2; } else if (c->parent) { // if we already have a wrong parent, orphan us first if (c->parent->child == c) // first in list c->parent->child = c->parent->child->next; for (r = c->parent->child; r; r = r->next) { if (r->next == c) r->next = c->next; } c->next = 0; } c->parent = parent; // add at the end if (!parent->child) { parent->child = c; } else { for (r = parent->child; r && r->next; r = r->next) if (r == c) goto out2; r->next = c; c->next = 0; } out2: // someone said our parent was our child, a lie if (c->child == c->parent) { c->child->parent = 0; c->child = 0; } } } time_t newest(struct container *c, int depth) { time_t n = -1; if (!c) return n; do { if (c->child) { time_t r = newest(c->child, depth+1); if (n < r) n = r; } if (n < c->date) n = c->date; } while ((c = c->next)); return n; } struct container *top; struct container *lastc; void find_root(const void *nodep, const VISIT which, const int depth) { (void)depth; if (which == preorder || which == leaf) { struct container *c = *(struct container **)nodep; if (!c->parent) { lastc->next = c; c->next = 0; time_t r = newest(c->child, 0); if (c->date < r) c->date = r; lastc = c; } } } void find_roots() { top = malloc(sizeof (struct container)); top->msg = 0; top->date = -1; top->file = 0; top->next = top->child = top->parent = 0; top->optional = 0; top->mid = "(top)"; lastc = top; twalk(mids, find_root); top->child = top->next; top->next = 0; } void prune_tree(struct container *c, int depth) { do { if (c->child) prune_tree(c->child, depth+1); if (depth >= 0 && !c->file && c->child && !c->child->next) { // turn into child if we don't exist and only have a child c->mid = c->child->mid; c->file = c->child->file; c->msg = c->child->msg; c->date = c->child->date; c->optional = c->child->optional; c->child = c->child->child; } } while ((c = c->next)); } int alloptional(struct container *c) { do { if (!c->optional && c->file) return 0; if (c->child && !alloptional(c->child)) return 0; } while ((c = c->next)); return 1; } static int dateorder(const void *a, const void *b) { struct container *ia = *(struct container **)a; struct container *ib = *(struct container **)b; if (ia->date < ib->date) return -1; else if (ia->date > ib->date) return 1; return 0; } void sort_tree(struct container *c, int depth) { if (c && c->child) { struct container *r; int i, j; for (r = c->child, i = 0; r; r = r->next, i++) sort_tree(r, depth+1); if (i == 1) // no sort needed return; struct container **a = calloc(sizeof (struct container *), i); if (!a) return; for (r = c->child, i = 0; r; r = r->next, i++) a[i] = r; qsort(a, i, sizeof (struct container *), dateorder); c->child = a[0]; for (j = 0; j+1 < i; j++) a[j]->next = a[j+1]; a[i-1]->next = 0; free(a); } } void print_tree(struct container *c, int depth) { do { // skip toplevel threads when they are unresolved or all optional if (depth <= 1 && (c->optional || !c->file) && (!c->child || alloptional(c->child))) continue; if (depth >= 0) { int i; for (i = 0; i < depth; i++) printf(" "); if (c->file) printf("%s\n", c->file); else printf("<%s>\n", c->mid); } if (c->child) print_tree(c->child, depth+1); } while ((c = c->next)); } int main(int argc, char *argv[]) { int c; long i; optional = 1; while ((c = getopt(argc, argv, "S:v")) != -1) switch (c) { case 'S': blaze822_loop1(optarg, thread); break; case 'v': vflag = 1; break; default: fprintf(stderr, "Usage: mthread [-v] [-S dir] [msgs...]\n"); exit(1); } optional = 0; if (argc == optind && isatty(0)) i = blaze822_loop1(":", thread); else i = blaze822_loop(argc-optind, argv+optind, thread); find_roots(); if (!vflag) prune_tree(top, -1); sort_tree(top, -1); print_tree(top, -1); fprintf(stderr, "%ld mails threaded\n", i); return 0; } mblaze-0.3.2/mymemmem.c000066400000000000000000000110401324061207500147510ustar00rootroot00000000000000// taken straight from musl@c718f9fc // incooperates fix from <20170629213533.18744-1-amonakov@ispras.ru> /* Copyright © 2005-2014 Rich Felker, et al. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) { uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) if (hw == nw) return (char *)h-2; return hw == nw ? (char *)h-2 : 0; } static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) { uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8; uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8; for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) if (hw == nw) return (char *)h-3; return hw == nw ? (char *)h-3 : 0; } static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) { uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) if (hw == nw) return (char *)h-4; return hw == nw ? (char *)h-4 : 0; } #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)<(b)?(a):(b)) #define BITOP(a,b,op) \ ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l) { size_t i, ip, jp, k, p, ms, p0, mem, mem0; size_t byteset[32 / sizeof(size_t)] = { 0 }; size_t shift[256]; /* Computing length of needle and fill shift table */ for (i=0; i n[jp+k]) { jp += k; k = 1; p = jp - ip; } else { ip = jp++; k = p = 1; } } ms = ip; p0 = p; /* And with the opposite comparison */ ip = -1; jp = 0; k = p = 1; while (jp+k ms+1) ms = ip; else p = p0; /* Periodic needle? */ if (memcmp(n, n+p, ms+1)) { mem0 = 0; p = MAX(ms, l-ms-1) + 1; } else mem0 = l-p; mem = 0; /* Search loop */ for (;;) { /* If remainder of haystack is shorter than needle, done */ if ((size_t)(z-h) < l) return 0; /* Check last byte first; advance by shift on mismatch */ if (BITOP(byteset, h[l-1], &)) { k = l-shift[h[l-1]]; if (k) { if (mem0 && mem && k < p) k = l-p; h += k; mem = 0; continue; } } else { h += l; mem = 0; continue; } /* Compare right half */ for (k=MAX(ms+1,mem); kmem && n[k-1] == h[k-1]; k--); if (k <= mem) return (char *)h; h += p; mem = mem0; } } void *mymemmem(const void *h0, size_t k, const void *n0, size_t l) { const unsigned char *h = h0, *n = n0; /* Return immediately on empty needle */ if (!l) return (void *)h; /* Return immediately when needle is longer than haystack */ if (k #include int mystrverscmp(const char *l0, const char *r0) { const unsigned char *l = (const void *)l0; const unsigned char *r = (const void *)r0; size_t i, dp, j; int z = 1; /* Find maximal matching prefix and track its maximal digit * suffix and whether those digits are all zeros. */ for (dp=i=0; l[i]==r[i]; i++) { int c = l[i]; if (!c) return 0; if (!isdigit(c)) dp=i+1, z=1; else if (c!='0') z=0; } if (l[dp]!='0' && r[dp]!='0') { /* If we're not looking at a digit sequence that began * with a zero, longest digit string is greater. */ for (j=i; isdigit(l[j]); j++) if (!isdigit(r[j])) return 1; if (isdigit(r[j])) return -1; } else if (z && dp // from musl@1cc81f5cb, slightly tweaked static long long __year_to_secs(long long year, int *is_leap) { if (year-2ULL <= 136) { int y = year; int leaps = (y-68)>>2; if (!((y-68)&3)) { leaps--; if (is_leap) *is_leap = 1; } else if (is_leap) *is_leap = 0; return 31536000*(y-70) + 86400*leaps; } int cycles, centuries, leaps, rem; cycles = (year-100) / 400; rem = (year-100) % 400; if (rem < 0) { cycles--; rem += 400; } if (!rem) { *is_leap = 1; centuries = 0; leaps = 0; } else { if (rem >= 200) { if (rem >= 300) centuries = 3, rem -= 300; else centuries = 2, rem -= 200; } else { if (rem >= 100) centuries = 1, rem -= 100; else centuries = 0; } if (!rem) { *is_leap = 0; leaps = 0; } else { leaps = rem / 4U; rem %= 4U; *is_leap = !rem; } } leaps += 97*cycles + 24*centuries - *is_leap; return (year-100) * 31536000LL + leaps * 86400LL + 946684800 + 86400; } static int __month_to_secs(int month, int is_leap) { static const int secs_through_month[] = { 0, 31*86400, 59*86400, 90*86400, 120*86400, 151*86400, 181*86400, 212*86400, 243*86400, 273*86400, 304*86400, 334*86400 }; int t = secs_through_month[month]; if (is_leap && month >= 2) t+=86400; return t; } time_t tm_to_secs(const struct tm *tm) { int is_leap; long long year = tm->tm_year; int month = tm->tm_mon; if (month >= 12 || month < 0) { int adj = month / 12; month %= 12; if (month < 0) { adj--; month += 12; } year += adj; } long long t = __year_to_secs(year, &is_leap); t += __month_to_secs(month, is_leap); t += 86400LL * (tm->tm_mday-1); t += 3600LL * tm->tm_hour; t += 60LL * tm->tm_min; t += tm->tm_sec; return t; } mblaze-0.3.2/pipeto.c000066400000000000000000000031411324061207500144310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include pid_t pipeto(const char *cmdline) { int pipe0[2]; // stdout -> stdin int pipe1[2]; // child errno -> parent pid_t pid; if (pipe(pipe0) < 0) return -1; if (pipe(pipe1) < 0) return -1; pid = fork(); if (pid < 0) { return -1; } else if (pid == 0) { // in child close(pipe1[0]); // close errno pipe on successful exec fcntl(pipe1[1], F_SETFD, FD_CLOEXEC); if (dup2(pipe0[0], 0) < 0) exit(111); close(pipe0[0]); close(pipe0[1]); // split cmdline, just on spaces char *argv[16]; int argc = 0; char *cp = strdup(cmdline); if (!cp) exit(111); while (argc < 16 && *cp) { argv[argc++] = cp; cp = strchr(cp, ' '); if (!cp) break; *cp++ = 0; while (*cp == ' ') cp++; } argv[argc] = 0; if (argv[0]) execvp(argv[0], argv); else errno = EINVAL; // execvp failed, write errno to parent int e = errno; if (write(pipe1[1], &e, sizeof e) < 0) exit(111); // do a magic dance for gcc -Wunused-result exit(111); } else { // in parent close(pipe1[1]); int e; ssize_t n = read(pipe1[0], &e, sizeof e); if (n < 0) e = errno; close(pipe1[0]); if (n == 0) { // child executed successfully, redirect stdout to it if (dup2(pipe0[1], 1) < 0) return -1; close(pipe0[0]); close(pipe0[1]); return pid; } else { errno = e; return -1; } } // return pid; } int pipeclose(pid_t pid) { int s; fflush(0); close(1); waitpid(pid, &s, 0); return s; } mblaze-0.3.2/rfc2045.c000066400000000000000000000077471324061207500142360ustar00rootroot00000000000000#include #include #include #include #include "blaze822.h" #include "blaze822_priv.h" // needs to be writable char textplain[] = "text/plain; charset=US-ASCII"; int blaze822_check_mime(struct message *msg) { char *v = blaze822_hdr(msg, "mime-version"); if (v && v[0] && v[0] == '1' && v[1] && v[1] == '.' && v[2] && v[2] == '0' && (!v[3] || iswsp(v[3]))) return 1; v = blaze822_hdr(msg, "content-transfer-encoding"); if (v) return 1; return 0; } int blaze822_mime_body(struct message *msg, char **cto, char **bodyo, size_t *bodyleno, char **bodychunko) { if (!msg->body || !msg->bodyend) { *bodyo = 0; *bodyleno = 0; *bodychunko = 0; return 0; } char *ct = blaze822_hdr(msg, "content-type"); char *cte = blaze822_hdr(msg, "content-transfer-encoding"); if (!ct) ct = textplain; char *s = ct; while (*s && *s != ';') { *s = lc(*s); s++; } *cto = ct; if (cte) { if (strncasecmp(cte, "quoted-printable", 16) == 0) { blaze822_decode_qp(msg->body, msg->bodyend, bodyo, bodyleno, 0); *bodychunko = *bodyo; } else if (strncasecmp(cte, "base64", 6) == 0) { blaze822_decode_b64(msg->body, msg->bodyend, bodyo, bodyleno); *bodychunko = *bodyo; } else { cte = 0; } } if (!cte) { *bodyo = msg->body; *bodyleno = msg->bodyend - msg->body; *bodychunko = 0; } return 1; } int blaze822_mime_parameter(char *s, char *name, char **starto, char **stopo) { if (!s) return 0; s = strchr(s, ';'); if (!s) return 0; s++; size_t namelen = strlen(name); while (*s) { while (iswsp(*s)) s++; if (strncasecmp(s, name, namelen) == 0 && s[namelen] == '=') { s += namelen + 1; break; } s = strchr(s+1, ';'); if (!s) return 0; s++; } if (!s || !*s) return 0; char *e; if (*s == '"') { s++; e = strchr(s, '"'); if (!e) return 0; } else { e = s; while (*e && !iswsp(*e) && *e != ';') e++; } *starto = s; *stopo = e; return 1; } int blaze822_multipart(struct message *msg, struct message **imsg) { char *s = blaze822_hdr(msg, "content-type"); if (!s) return 0; while (*s && *s != ';') s++; if (!*s) return 0; char *boundary, *boundaryend; if (!blaze822_mime_parameter(s, "boundary", &boundary, &boundaryend)) return 0; char mboundary[256]; size_t boundarylen = boundaryend-boundary+2; if (boundarylen >= 256) return 0; mboundary[0] = '-'; mboundary[1] = '-'; memcpy(mboundary+2, boundary, boundarylen-2); mboundary[boundarylen] = 0; char *prevpart; if (*imsg) prevpart = (*imsg)->bodyend; else prevpart = msg->body; char *part = mymemmem(prevpart, msg->bodyend - prevpart, mboundary, boundarylen); if (!part) return 0; part += boundarylen; if (*part == '\r') part++; if (*part == '\n') part++; else if (*part == '-' && part < msg->bodyend && *(part+1) == '-') return 0; else return 0; // XXX error condition? char *nextpart = mymemmem(part, msg->bodyend - part, mboundary, boundarylen); if (!nextpart) return 0; // XXX error condition if (nextpart == part) // invalid empty MIME part return 0; // XXX error condition if (*(nextpart-1) == '\n') nextpart--; if (*(nextpart-1) == '\r') nextpart--; *imsg = blaze822_mem(part, nextpart-part); return 1; } blaze822_mime_action blaze822_walk_mime(struct message *msg, int depth, blaze822_mime_callback visit) { char *ct, *body, *bodychunk; size_t bodylen; blaze822_mime_action r = MIME_CONTINUE; if (blaze822_mime_body(msg, &ct, &body, &bodylen, &bodychunk)) { r = visit(depth, msg, body, bodylen); if (r == MIME_CONTINUE) { if (strncmp(ct, "multipart/", 10) == 0) { struct message *imsg = 0; while (blaze822_multipart(msg, &imsg)) { r = blaze822_walk_mime(imsg, depth+1, visit); if (r == MIME_STOP) break; } } else if (strncmp(ct, "message/rfc822", 14) == 0) { struct message *imsg = blaze822_mem(body, bodylen); if (imsg) blaze822_walk_mime(imsg, depth+1, visit); } } free(bodychunk); } return r; } mblaze-0.3.2/rfc2047.c000066400000000000000000000146701324061207500142310ustar00rootroot00000000000000#include #include #include #include #include #include #include "blaze822.h" #include "blaze822_priv.h" // XXX keep trying bytewise on invalid iconv int blaze822_decode_qp(char *start, char *stop, char **deco, size_t *decleno, int underscore) { static signed char hex[] = { -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 }; char *buf = malloc(stop - start + 1); if (!buf) return 0; *deco = buf; char *s = start; while (s < stop) { if (*s == '=' && s[1] == '\n') { s += 2; } else if (*s == '=' && s[1] == '\r' && s[2] == '\n') { s += 3; } else if (*s == '=' && s+2 < stop) { unsigned char c1 = s[1]; unsigned char c2 = s[2]; s += 3; if (c1 > 127 || c2 > 127 || hex[c1] < 0 || hex[c2] < 0) { *buf++ = '='; *buf++ = c1; *buf++ = c2; continue; } *buf++ = (hex[c1] << 4) | hex[c2]; } else if (underscore && *s == '_') { *buf++ = ' '; s++; } else { *buf++ = *s++; } } *buf = 0; *decleno = buf - *deco; return 1; } int blaze822_decode_b64(char *s, char *e, char **deco, size_t *decleno) { static signed char b64[128] = { -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 }; char *buf = malloc((e - s) / 4 * 3 + 1); if (!buf) return 0; *deco = buf; while (s + 4 <= e) { while (s < e && isfws((unsigned char)*s)) s++; if (s >= e) break; uint32_t v = 0; unsigned char t = 0; unsigned char c0 = s[0], c1 = s[1], c2 = s[2], c3 = s[3]; s += 4; if ((c0 | c1 | c2 | c3) > 127) goto error; v |= b64[c0]; t |= b64[c0]; v <<= 6; v |= b64[c1]; t |= b64[c1]; v <<= 6; v |= b64[c2]; t |= b64[c2]; v <<= 6; v |= b64[c3]; t |= b64[c3]; if (t >= 64) { error: *buf++ = '?'; *buf++ = '?'; *buf++ = '?'; continue; } char d2 = v & 0xff; v >>= 8; char d1 = v & 0xff; v >>= 8; char d0 = v & 0xff; if (c1 != '=') *buf++ = d0; if (c2 != '=') *buf++ = d1; if (c3 != '=') *buf++ = d2; } *buf = 0; *decleno = buf - *deco; return 1; } int blaze822_decode_rfc2047(char *dst, char *src, size_t dlen, char *tgtenc) { iconv_t ic = (iconv_t)-1; char *srcenc = 0; char *startdst = dst; size_t startdlen = dlen; char *b = src; // XXX use memmem char *s = strstr(src, "=?"); if (!s) goto nocodeok; // keep track of partial multibyte sequences char *partial = 0; size_t partiallen = 0; do { char *t; t = b; while (t < s) // strip space-only inbetween encoded words if (!isfws(*t++)) { if (partial) // mixed up encodings goto nocode; while (b < s && dlen) { *dst++ = *b++; dlen--; } break; } if (!dlen) break; s += 2; char *e = strchr(s, '?'); if (!e) goto nocode; *e = 0; if (!srcenc || strcmp(srcenc, s) != 0) { if (partial) // mixed up encodings goto nocode; free(srcenc); srcenc = strdup(s); char *lang = strchr(srcenc, '*'); if (lang) *lang = 0; // kill RFC2231 language tag if (!srcenc) goto nocode; if (ic != (iconv_t)-1) iconv_close(ic); ic = iconv_open(tgtenc, srcenc); } *e = '?'; e++; if (ic == (iconv_t)-1) goto nocode; char enc = lc(*e++); if (*e++ != '?') goto nocode; char *start = e; char *stop = strstr(e, "?="); if (!stop) goto nocode; char *dec = 0, *decchunk; size_t declen = 0; if (enc == 'q') blaze822_decode_qp(start, stop, &dec, &declen, 1); else if (enc == 'b') blaze822_decode_b64(start, stop, &dec, &declen); else goto nocode; if (partial) { dec = realloc(dec, declen + partiallen); if (!dec) goto nocode; memmove(dec + partiallen, dec, declen); memcpy(dec, partial, partiallen); declen += partiallen; free(partial); partial = 0; partiallen = 0; } decchunk = dec; ssize_t r = iconv(ic, &dec, &declen, &dst, &dlen); if (r < 0) { if (errno == E2BIG) { break; } else if (errno == EILSEQ) { goto nocode; } else if (errno == EINVAL) { partial = malloc(declen); if (!partial) goto nocode; memcpy(partial, dec, declen); partiallen = declen; } else { perror("iconv"); goto nocode; } } while (!partial && declen && dlen) { *dst++ = *dec++; declen--; dlen--; } free(decchunk); b = stop + 2; } while (dlen && (s = strstr(b, "=?"))); while (*b && dlen > 1) { *dst++ = *b++; dlen--; } if (memchr(startdst, 0, dst - startdst)) { dst = startdst; dlen = startdlen; goto nocodeok; } *dst = 0; if (ic != (iconv_t)-1) iconv_close(ic); free(srcenc); return 1; nocode: fprintf(stderr, "error decoding rfc2047\n"); if (ic != (iconv_t)-1) iconv_close(ic); nocodeok: free(srcenc); while (*src && dlen > 1) { *dst++ = *src++; dlen--; } *dst = 0; return 1; } #ifdef TEST int main() { char *r; size_t l; char test[] = "Keld_J=F8rn_Simonsen"; blaze822_decode_qp(test, test + sizeof test, &r, &l); printf("%s %d\n", r, l); char *r2; size_t l2; char test2[] = "SWYgeW91IGNhbiByZWFkIHRoaXMgeW8="; // dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg=="; blaze822_decode_b64(test2, test2+sizeof test2, &r2, &l2); printf("%s %d\n", r2, l2); char test3[] = "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= "; char test3dec[255]; blaze822_decode_rfc2047(test3dec, test3, sizeof test3dec, "UTF-8"); printf("%s\n", test3dec); char test4[] = "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= " "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= z " "=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?="; char test4dec[255]; blaze822_decode_rfc2047(test4dec, test4, sizeof test4dec, "UTF-8"); printf("%s\n", test4dec); char test5[] = "=?UTF-8?Q?z=E2=80?= =?UTF-8?Q?=99z?="; char test5dec[255]; blaze822_decode_rfc2047(test5dec, test5, sizeof test5dec, "UTF-8"); printf("%s\n", test5dec); } #endif mblaze-0.3.2/rfc2231.c000066400000000000000000000052401324061207500142150ustar00rootroot00000000000000#include #include #include #include #include "blaze822.h" int blaze822_mime2231_parameter(char *s, char *name, char *dst, size_t dlen, char *tgtenc) { static signed char hex[] = { -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 }; int i = 0; char namenum[64]; char *srcenc = 0; char *dststart = dst; char *dstend = dst + dlen; char *sbuf, *ebuf; snprintf(namenum, sizeof namenum, "%s*", name); if (blaze822_mime_parameter(s, namenum, &sbuf, &ebuf)) { i = 100; goto found_extended; } if (blaze822_mime_parameter(s, name, &sbuf, &ebuf)) { i = 100; goto found_plain; } while (i < 100) { snprintf(namenum, sizeof namenum, "%s*%d*", name, i); if (blaze822_mime_parameter(s, namenum, &sbuf, &ebuf)) { found_extended: // decode extended if (i == 0 || i == 100) { // extended-initial-value char *encstart = sbuf; sbuf = strchr(sbuf, '\''); if (!sbuf) return 0; srcenc = strndup(encstart, sbuf - encstart); if (!srcenc) return 0; sbuf = strchr(sbuf+1, '\''); if (!sbuf) return 0; sbuf++; } while (sbuf < ebuf && dst < dstend) { if (sbuf[0] == '%') { unsigned char c1 = sbuf[1]; unsigned char c2 = sbuf[2]; if (c1 < 127 && c2 < 127 && hex[c1] > -1 && hex[c2] > -1) { *dst++ = (hex[c1] << 4) | hex[c2]; sbuf += 3; } else { *dst++ = *sbuf++; } } else { *dst++ = *sbuf++; } } *dst = 0; } else { namenum[strlen(namenum) - 1] = 0; // strip last * if (blaze822_mime_parameter(s, namenum, &sbuf, &ebuf)) { found_plain: // copy plain if (ebuf - sbuf < dstend - dst) { memcpy(dst, sbuf, ebuf - sbuf); dst += ebuf - sbuf; } *dst = 0; } else { break; } } i++; } if (i <= 0) return 0; if (!srcenc) return 1; iconv_t ic = iconv_open(tgtenc, srcenc); free(srcenc); if (ic == (iconv_t)-1) return 1; size_t tmplen = dlen; char *tmp = malloc(tmplen); if (!tmp) return 1; char *tmpend = tmp; size_t dstlen = dst - dststart; dst = dststart; size_t r = iconv(ic, &dst, &dstlen, &tmpend, &tmplen); if (r == (size_t)-1) { free(tmp); return 1; } iconv_close(ic); // copy back memcpy(dststart, tmp, tmpend - tmp); dststart[tmpend - tmp] = 0; free(tmp); return 1; } mblaze-0.3.2/safe_u8putstr.c000066400000000000000000000023561324061207500157540ustar00rootroot00000000000000#include #include #include "u8decode.h" void safe_u8putstr(char *s0, size_t l, FILE *stream) { // tty-safe output of s, with relaxed utf-8 semantics: // - C0 and C1 are displayed as escape sequences // - valid utf-8 is printed as is // - rest is assumed to be latin-1, and translated into utf-8 // - translate CRLF to CR unsigned char *s = (unsigned char *)s0; unsigned char *e = s + l; uint32_t c; while (s < e) { int l = u8decode((char *)s, &c); if (l == -1) { l = 1; if (*s <= 0x9fu) { // C1 fputc(0xe2, stream); fputc(0x90, stream); fputc(0x80+0x1b, stream); fputc(0xe2, stream); fputc(0x90, stream); fputc(*s, stream); } else { /* invalid utf-8, assume it was latin-1 */ fputc(0xc0 | (*s >> 6), stream); fputc(0x80 | (*s & 0x3f), stream); } } else if (c < 32 && *s != ' ' && *s != '\t' && *s != '\n' && *s != '\r') { // C0 fputc(0xe2, stream); fputc(0x90, stream); fputc(0x80+*s, stream); } else if (c == 127) { // DEL fputc(0xe2, stream); fputc(0x90, stream); fputc(0xa1, stream); } else if (c == '\r') { if (e - s > 1 && s[1] == '\n') s++; fputc(*s, stream); } else { fwrite(s, 1, l, stream); } s += l; } } mblaze-0.3.2/seq.c000066400000000000000000000221251324061207500137240ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include "blaze822.h" #include "blaze822_priv.h" char * blaze822_home_file(char *basename) { static char path[PATH_MAX]; static char *homedir; static char *profile; if (!profile) profile = getenv("MBLAZE"); if (profile) { snprintf(path, sizeof path, "%s/%s", profile, basename); return path; } if (!homedir) homedir = getenv("HOME"); if (homedir && !*homedir) homedir = 0; if (!homedir) { struct passwd *pw = getpwuid(getuid()); if (pw) homedir = pw->pw_dir; } if (!homedir) return "/dev/null/homeless"; snprintf(path, sizeof path, "%s/.mblaze/%s", homedir, basename); return path; } char * blaze822_seq_open(char *file) { if (!file) file = getenv("MAILSEQ"); if (!file) file = blaze822_home_file("seq"); char *map; off_t len; int r = slurp(file, &map, &len); if (r != 0) { fprintf(stderr, "could not read sequence '%s': %s\n", file, strerror(r)); return 0; } return map; } static void *msgnums; struct msgnum { char *file; long pos; }; int msgnumorder(const void *a, const void *b) { struct msgnum *ia = (struct msgnum *)a; struct msgnum *ib = (struct msgnum *)b; return strcmp(ia->file, ib->file); } long blaze822_seq_find(char *file) { struct msgnum key, **result; key.file = file; if (!(result = tfind(&key, &msgnums, msgnumorder))) return 0; return (*result)->pos; } int blaze822_seq_load(char *map) { char *s, *t; long line; for (s = map, line = 0; s; s = t+1) { t = strchr(s, '\n'); if (!t) break; line++; while (*s && iswsp(*s)) s++; char *e = t; while (s < e && isfws(*(e-1))) e--; // printf("{%.*s}\n", e-s, s); char *f = strndup(s, e-s); if (!f) return -1; struct msgnum key, **result; key.file = f; if (!(result = tfind(&key, &msgnums, msgnumorder))) { struct msgnum *c = malloc(sizeof (struct msgnum)); c->file = f; c->pos = line; tsearch(c, &msgnums, msgnumorder); } } return 0; } char * blaze822_seq_cur(void) { static char b[PATH_MAX]; char *override = getenv("MAILDOT"); if (override) return override; char *curlink = getenv("MAILCUR"); if (!curlink) curlink = blaze822_home_file("cur"); ssize_t r = readlink(curlink, b, sizeof b - 1); if (r < 0) return 0; b[r] = 0; return b; } int blaze822_seq_setcur(char *s) { char *override = getenv("MAILDOT"); if (override) return 0; char curtmplink[PATH_MAX]; char *curlink = getenv("MAILCUR"); if (!curlink) curlink = blaze822_home_file("cur"); if (snprintf(curtmplink, sizeof curtmplink, "%s-", curlink) >= PATH_MAX) return -1; // truncation if (unlink(curtmplink) < 0 && errno != ENOENT) return -1; if (symlink(s, curtmplink) < 0) return -1; if (rename(curtmplink, curlink) < 0) return -1; return 0; } static char * parse_relnum(char *a, long cur, long start, long last, long *out) { long base; char *b; if (strcmp(a, "+") == 0) a = ".+1"; else if (strcmp(a, "-") == 0) a = ".-1"; else if (strcmp(a, ".") == 0) a = ".+0"; if (*a == '$') { a++; base = last; } else if (*a == '.') { a++; base = cur; } else if (*a == '-') { base = last + 1; } else if (*a == '+') { base = start; } else { base = 0; } long d; if (strchr(":=_^", *a)) { d = 0; b = a; } else { errno = 0; d = strtol(a, &b, 10); if (errno != 0) { perror("strtol"); exit(2); } } *out = base + d; if (*out <= 0) *out = 1; if (*out > last) *out = last; return b; } static int parse_thread(char *map, long a, long *starto, long *stopo) { char *s, *t; long line; long start = 0, stop = 0, state = 0; for (s = map, line = 0; s; s = t+1) { t = strchr(s, '\n'); if (!t) break; line++; if (!iswsp(*s)) { if (state == 0) { start = line; } else if (state == 1) { stop = line - 1; state = 2; break; } } if (line == a) state = 1; while (*s && iswsp(*s)) s++; } if (state == 1) { stop = line; state = 2; } if (state == 2) { *starto = start; *stopo = stop; return 0; } return 1; } static int parse_subthread(char *map, long a, long *stopo) { char *s, *t; long line; long stop = 0; int minindent = -1; for (s = map, line = 0; s; s = t+1) { t = strchr(s, '\n'); if (!t) { minindent = -1; break; } line++; int indent = 0; while (*s && iswsp(*s)) { s++; indent++; } if (line == a) minindent = indent; if (line > a && indent <= minindent) { stop = line - 1; break; } } if (line < a) return 1; if (minindent == -1) stop = line; *stopo = stop; return 0; } static int parse_parent(char *map, long *starto, long *stopo) { char *s, *t; long line; long previndent[256] = { 0 }; for (s = map, line = 0; s; s = t+1) { t = strchr(s, '\n'); if (!t) break; line++; long indent = 0; while (*s && iswsp(*s)) { s++; indent++; } if (indent > 255) indent = 255; previndent[indent] = line; if (line == *starto) { if (previndent[indent-1]) { *starto = *stopo = previndent[indent-1]; return 0; } else { return 1; } } } return 1; } static int parse_range(char *map, char *a, long *start, long *stop, long cur, long lines) { *start = 0; *stop = 1; while (*a && *a != ':' && *a != '=' && *a != '_' && *a != '^') { char *b = parse_relnum(a, cur, 0, lines, start); if (a == b) return 1; a = b; } if (*start == 0) *start = strchr("=^_", *a) ? cur : 1; while (*a == '^') { a++; if (parse_parent(map, start, stop)) return 2; } if (*a == ':') { a++; if (!*a) { *stop = lines; } else { char *b = parse_relnum(a, cur, *start, lines, stop); if (a == b) return 1; } } else if (*a == '=') { return parse_thread(map, *start, start, stop); } else if (*a == '_') { return parse_subthread(map, *start, stop); } else if (!*a) { *stop = *start; } else { return 1; } return 0; } void find_cur(char *map, struct blaze822_seq_iter *iter) { char *s, *t; long cur = 0; const char *curfile = blaze822_seq_cur(); iter->lines = 0; for (s = map; s; s = t+1) { t = strchr(s, '\n'); if (!t) break; while (*s == ' ' || *s == '\t') s++; // printf("{%.*s}\n", t-s, s); iter->lines++; if (!cur && curfile && strncmp(s, curfile, strlen(curfile)) == 0 && (s[strlen(curfile)] == '\n' || s[strlen(curfile)] == ' ' || s[strlen(curfile)] == '\t')) iter->cur = iter->lines; } } char * blaze822_seq_next(char *map, char *range, struct blaze822_seq_iter *iter) { if (strcmp(range, ".") == 0) { if (!iter->lines && !iter->start) { iter->lines = 1; find_cur(map, iter); iter->start = iter->stop = iter->line = iter->cur + 1; char *cur = blaze822_seq_cur(); return cur ? strdup(cur) : 0; } else { return 0; } } if (!map) return 0; if (!iter->lines) // count total lines find_cur(map, iter); if (!iter->start) { int ret = parse_range(map, range, &iter->start, &iter->stop, iter->cur, iter->lines); if (ret == 1) { fprintf(stderr, "can't parse range: %s\n", range); return 0; } else if (ret == 2) { fprintf(stderr, "message not found for specified range: %s\n", range); return 0; } iter->s = map; iter->line = 1; } if (!iter->s) return 0; while (iter->line < iter->start) { char *t = strchr(iter->s, '\n'); if (!t) return 0; iter->line++; iter->s = t + 1; } if (iter->line > iter->stop) { iter->start = iter->stop = 0; // reset iteration return 0; } char *t = strchr(iter->s, '\n'); if (!t) return 0; iter->cur = iter->line; iter->line++; char *r = strndup(iter->s, t-iter->s); iter->s = t + 1; return r; } static long iterdir(char *dir, void (*cb)(char *)) { DIR *fd, *fd2; struct dirent *d; long i = 0; fd = opendir(dir); if (!fd) { if (errno == ENOTDIR) cb(dir); return 1; } char sub[PATH_MAX]; snprintf(sub, sizeof sub, "%s/cur", dir); fd2 = opendir(sub); if (fd2) { closedir(fd); fd = fd2; } while ((d = readdir(fd))) { #if defined(DT_REG) && defined(DT_UNKNOWN) if (d->d_type != DT_REG && d->d_type != DT_UNKNOWN) continue; #endif if (d->d_name[0] == '.') continue; if (fd2) snprintf(sub, sizeof sub, "%s/cur/%s", dir, d->d_name); else snprintf(sub, sizeof sub, "%s/%s", dir, d->d_name); cb(sub); i++; } closedir(fd); return i; } long blaze822_loop(int argc, char *argv[], void (*cb)(char *)) { char *line = 0; size_t linelen = 0; ssize_t rd; long i = 0; if (argc == 0) { while ((rd = getdelim(&line, &linelen, '\n', stdin)) != -1) { if (line[rd-1] == '\n') line[rd-1] = 0; cb(line); i++; } free(line); return i; } char *map = blaze822_seq_open(0); struct blaze822_seq_iter iter = { 0 }; int j = 0; for (i = 0; i < argc; i++) { if (strchr(argv[i], '/')) { // a file name j += iterdir(argv[i], cb); } else { while ((line = blaze822_seq_next(map, argv[i], &iter))) { cb(line); free(line); j++; } } } return j; } long blaze822_loop1(char *arg, void (*cb)(char *)) { char *args[] = { arg }; return blaze822_loop(1, args, cb); } mblaze-0.3.2/slurp.c000066400000000000000000000014651324061207500143050ustar00rootroot00000000000000#include #include #include #include #include #include int slurp(char *filename, char **bufo, off_t *leno) { int fd; struct stat st; ssize_t nread = 0; ssize_t n; int r = 0; fd = open(filename, O_RDONLY); if (fd < 0) { r = errno; goto out; } if (fstat(fd, &st) < 0) { r = errno; goto out; } if (st.st_size == 0) { *bufo = ""; *leno = 0; return 0; } *bufo = malloc(st.st_size + 1); if (!*bufo) { r = ENOMEM; goto out; } do { if ((n = read(fd, *bufo + nread, st.st_size - nread)) < 0) { if (errno == EINTR) { continue; } else { r = errno; goto out; } } if (!n) break; nread += n; } while (nread < st.st_size); *leno = nread; (*bufo)[st.st_size] = 0; out: close(fd); return r; } mblaze-0.3.2/squeeze_slash.c000066400000000000000000000003221324061207500160020ustar00rootroot00000000000000void squeeze_slash(char *arg) { char *s, *t; // squeeze slashes s = t = arg; while ((*s++ = *t)) while (*t++ == '/' && *t == '/') ; // remove trailing slashes s--; while (*--s == '/') *s = 0; } mblaze-0.3.2/t/000077500000000000000000000000001324061207500132315ustar00rootroot00000000000000mblaze-0.3.2/t/1000-mmime.t000077500000000000000000000007171324061207500151100ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 4 cat <tmp References: Body EOF # https://github.com/chneukirchen/mblaze/issues/20 check 'mime -r runs' 'mmime -r tmp2' check 'no overlong lines' 'awk "{if(length(\$0)>=80)exit 1}" "inbox/cur/1:2," From: Rajwinder Kaur Subject: namaste Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: ! cat <"inbox/cur/2:2," From: имярек <имярек@example.com>, Rajwinder Kaur Subject: Здравствуйте Date: Thu, 30 Mar 2017 15:42:10 +0200 Message-Id: ! cat <"inbox/cur/3:2," From: rajwinder@example.com Subject: Здраво Date: Thu, 30 Mar 2017 15:40:32 +0200 Message-Id: ! cat <"inbox/cur/4:2," From: Perico de los palotes Subject: Hola Date: Thu, 30 Mar 2017 16:20:11 +0200 Message-Id: Foo: Perico de los palotes Long: heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelloooooooooooooooooo@woooooooooooooooooooooooooooooooooooooorld.com ! # from rfc2047.c cat <"inbox/cur/5:2," DecodeISO8859: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= DecodeLongISO8859: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= z =?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?= DecodeUTF8: =?UTF-8?Q?z=E2=80?= =?UTF-8?Q?=99z?= ! cat <seq inbox/cur/1:2, inbox/cur/2:2, inbox/cur/3:2, inbox/cur/4:2, inbox/cur/5:2, ! export MAILSEQ=seq check_same 'from one' 'maddr 1' 'echo "Rajwinder Kaur "' check_same 'from address' 'maddr -a 1' 'echo "rajwinder@example.com"' cat <expect имярек <имярек@example.com> Rajwinder Kaur ! check_same 'from two' 'maddr 2' 'cat expect' check_same 'from addr only' 'maddr 3' 'echo "rajwinder@example.com"' check_test 'from name only' -eq 0 'maddr 4 | wc -l' check_same 'specific header' 'maddr -h foo 4' 'echo "Perico de los palotes "' check_same 'long addr' 'maddr -h long 4' 'echo "heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelloooooooooooooooooo@woooooooooooooooooooooooooooooooooooooorld.com"' check_same 'decode iso8859' 'maddr -h DecodeISO8859 5' 'echo "Keld Jørn Simonsen "' check_same 'decode long iso8859' 'maddr -h DecodeLongISO8859 5' 'echo "If you can read this you understand the example. z a b "' check_same 'decode utf8' 'maddr -h DecodeUTF8 5' 'echo "z’z "' ) mblaze-0.3.2/t/1501-maddr-regress.t000077500000000000000000000036331324061207500165510ustar00rootroot00000000000000#!/bin/sh cd ${0%/*} . ./lib.sh plan 26 check_addr() { printf "From: %s\n" "$1" | check_test "parse $1" = "$2" "maddr /dev/stdin" } check_addr 'foo@example.org' 'foo@example.org' check_addr '' 'foo@example.org' check_addr 'bar ' 'bar ' check_addr 'bar quux ' 'bar quux ' check_addr 'bar quux ' 'bar quux ' check_addr ' (bar)' 'bar ' check_addr '"Real Name" ' 'Real Name ' check_addr '"Dr. Real Name" ' 'Dr. Real Name ' check_addr '"Real Name" (show this) ' 'Real Name (show this) ' check_addr '"Real Name" (ignore this)' 'Real Name (ignore this) ' check_addr '(nested (comments mail@here ) heh) "yep (yap)" ' 'yep (yap) ' check_addr 'ignore-this: ' 'foo@example.org' check_addr 'ignore-this : ' 'foo@example.org' check_addr '"foo"@example.org' 'foo@example.org' check_addr '"Barqux" "foo"@example.org' 'Barqux ' check_addr 'Space Man <"space man"@example.org>' 'Space Man <"space man"@example.org>' check_addr 'Space Man <"space man"@example.org>' 'Space Man <"space man"@example.org>' check_addr '' 'spaceman@example.org' check_addr '< spaceman@example.org >' 'spaceman@example.org' check_addr '' 'spaceman@example.org' check_addr 'space\man@example.org' 'spaceman@example.org' check_addr 'what is <"thisit"@example.org>' 'what is <"thisit"@example.org>' check_addr 'foo@example.org ' 'foo@example.org' check_addr 'foo@[::1] (ipv6)' 'ipv6 ' # invalid addresses check_addr '' 'foobar@qux.com' check_addr '"abc@def"@ghi' ''mblaze-0.3.2/t/2000-mpick.t000077500000000000000000000037651324061207500151160ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 13 rm -rf test.dir mkdir test.dir ( cd test.dir mkdir -p inbox/cur touch "inbox/cur/1:2,S" touch "inbox/cur/2:2,ST" touch "inbox/cur/3:2,SRT" touch "inbox/cur/4:2,SRFT" touch "inbox/cur/5:2,T" touch "inbox/cur/6:2,SRF" touch "inbox/cur/7:2,SR" touch "inbox/cur/8:2,SF" touch "inbox/cur/9:2," check_same 'flag trashed' 'mlist inbox | mpick :T' 'mlist -T inbox' check_same 'flag not trashed' 'mlist inbox | mpick -t "!trashed"' 'mlist -t inbox' check_same 'flag seen' 'mlist inbox | mpick :S' 'mlist -S inbox' check_same 'flag not seen' 'mlist inbox | mpick -t !seen' 'mlist -s inbox' check_same 'flag seen and trashed' 'mlist inbox | mpick :S :T' 'mlist -ST inbox' check_same 'flag seen and not trashed' 'mlist inbox | mpick -t "seen && !trashed"' 'mlist -St inbox' check_same 'flag replied' 'mlist inbox | mpick :R' 'mlist -R inbox' check_same 'flag forwarded' 'mlist inbox | mpick :F' 'mlist -F inbox' cat <"inbox/cur/1:2,S" From: Peter Example Subject: Hey whats up? Date: Thu, 30 Mar 2017 15:41:17 +0200 Message-Id: Greetings ! cat <"inbox/cur/9:2," From: Peter Example Subject: wow nice subject Date: Thu, 30 Mar 2017 15:42:00 +0200 Message-Id: shit happens ! cat <"inbox/cur/5:2,T" From: Obvious spam Subject: look at this awesome pdf Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: Foo: bar Check my resume! Greetings #application/pdf ../../mshow ! check 'search subject' 'mlist inbox | mpick /wow | grep -q inbox/cur/9:2,' check_test 'search addr' -eq 2 'mlist inbox | mpick peter@example.org | wc -l' check_test 'search name' -eq 2 'mlist inbox | mpick "Peter Example" | wc -l' check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"pdf\"" | wc -l' check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l' ) mblaze-0.3.2/t/3000-magrep.t000066400000000000000000000137001324061207500152520ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 10 rm -rf test.dir mkdir test.dir ( cd test.dir mkdir -p "inbox/cur" cat <"inbox/cur/1:2," From: Piet Pompies Subject: wow nice subject Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: shit happens ! cat <"inbox/cur/2:2," From: Piet Pompies Subject: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: Greetings ! cat <"inbox/cur/3:2," From: Piet Pompies Subject: 1 multi subject one Subject: 2 multi subject two Subject: 3 multi subject three Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: ! cat <"inbox/cur/4:2," To: "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" , "John Doe" ! cat <seq inbox/cur/1:2, inbox/cur/2:2, inbox/cur/3:2, inbox/cur/4:2, ! export MAILSEQ=seq check_test 'subject' -eq 1 'magrep subject:nice | wc -l' check_test 'ignorecase' -eq 1 'magrep -i subject:NICE | wc -l' check_test 'invert' -eq 2 'magrep -v subject:nice | wc -l' check_test 'max matches' -eq 2 'magrep -m 2 from:Piet | wc -l' check_test 'long subject' -eq 1 'magrep subject:aliqua | wc -l' check_test 'decode large rfc2047 header' -eq 1 'magrep -d to:John | wc -l' echo 'inbox/cur/1:2,: subject: wow nice subject' >expect check_same 'print' 'magrep -p subject:nice' 'cat expect' echo 'inbox/cur/1:2,: subject: nice' >expect check_same 'print match' 'magrep -po subject:nice' 'cat expect' echo 'nice' >expect check_same 'print match only' 'magrep -o subject:nice' 'cat expect' echo 'inbox/cur/3:2,' >expect check_same 'multiple subjects' 'magrep subject:multi' 'cat expect' ) mblaze-0.3.2/t/4000-msed.t000066400000000000000000000014541324061207500147330ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 6 rm -rf test.dir mkdir test.dir ( cd test.dir mkdir -p "inbox/cur" cat <"inbox/cur/1:2," From: Rajwinder Kaur Subject: Hello Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: body ! cat <seq inbox/cur/1:2, ! export MAILSEQ=seq check 'append new' 'msed "/foobar/a/value/" 1 | grep "Foobar: value"' check 'append existing' 'msed "/subject/a/world/" 1 | grep -v "world"' check_test 'append multiple' -eq 2 'msed "/foo/a/catch/;/bar/a/catch/" 1 | grep -c catch' check 'change' 'msed "/subject/c/world/" 1 | grep "Subject: world"' check 'delete' 'msed "/message-id/d" 1 | grep -v "Message-Id"' check 'substitute' 'msed "/subject/s/\(Hello\)/\1 World/" 1 | grep "^Subject: Hello World$"' ) mblaze-0.3.2/t/5000-mscan.t000066400000000000000000000007611324061207500151050ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 2 rm -rf test.dir mkdir test.dir ( cd test.dir mkdir -p "inbox/cur" cat <"inbox/cur/1:2," From: Rajwinder Kaur Subject: Hello Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: body ! cat <seq inbox/cur/1:2, ! export MAILSEQ=seq check_same 'ISO date' 'TZ=utc mscan -f "%16D" 1' 'echo "2017-03-30 13:42"' check_same 'from name' 'mscan -f "%f" 1' 'echo "Rajwinder Kaur"' ) mblaze-0.3.2/t/6000-msort.t000066400000000000000000000027641324061207500151560ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 5 rm -rf test.dir mkdir test.dir ( cd test.dir mkdir -p "inbox/cur" cat <"inbox/cur/1:2," From: Rajwinder Kaur Subject: namaste Date: Thu, 30 Mar 2017 15:42:05 +0200 Message-Id: ! cat <"inbox/cur/2:2," From: имярек <имярек@example.com> Subject: Здравствуйте Date: Thu, 30 Mar 2017 15:42:10 +0200 Message-Id: ! cat <"inbox/cur/3:2," From: Pera Perić Subject: Здраво Date: Thu, 30 Mar 2017 15:40:32 +0200 Message-Id: ! cat <"inbox/cur/4:2," From: Perico de los palotes Subject: Hola Date: Thu, 30 Mar 2017 16:20:11 +0200 Message-Id: ! cat <seq inbox/cur/1:2, inbox/cur/2:2, inbox/cur/3:2, inbox/cur/4:2, ! export MAILSEQ=seq check_same 'filename' 'msort -F' 'cat seq' cat <expect inbox/cur/3:2, inbox/cur/1:2, inbox/cur/2:2, inbox/cur/4:2, ! check_same 'date' 'msort -d' 'cat expect' cat <expect inbox/cur/4:2, inbox/cur/2:2, inbox/cur/1:2, inbox/cur/3:2, ! check_same 'reverse date' 'msort -dr' 'cat expect' cat <expect inbox/cur/2:2, inbox/cur/3:2, inbox/cur/4:2, inbox/cur/1:2, ! check_same 'from' 'msort -f' 'cat expect' cat <expect inbox/cur/3:2, inbox/cur/2:2, inbox/cur/4:2, inbox/cur/1:2, ! check_same 'subject' 'msort -s' 'cat expect' ) mblaze-0.3.2/t/7000-mseq.t000066400000000000000000000016361324061207500147550ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 10 rm -rf test.dir mkdir test.dir ( cd test.dir cat <seq inbox/cur/1:2, inbox/cur/2:2, inbox/cur/3:2, inbox/cur/4:2, inbox/cur/5_1:2, inbox/cur/6_2:2, inbox/cur/7_3:2, inbox/cur/8_4:2, inbox/cur/9:2, inbox/cur/10:2, ! export MAILCUR=cur MAILSEQ=seq check 'set current' 'mseq -C 1 && mseq . | grep "inbox/cur/1:2,"' check 'set next' 'mseq -C + && mseq . | grep "inbox/cur/2:2,"' check 'set prev' 'mseq -C - && mseq . | grep "inbox/cur/1:2,"' check 'last' 'mseq "$" | grep "inbox/cur/10:2,"' check_test 'whole thread' -eq 4 'mseq 6= | wc -l' check_test 'subthread' -eq 2 'mseq 7_ | wc -l' check 'parent' 'mseq 6^ | grep "inbox/cur/5_1:2,"' check_test 'range' -eq 3 'mseq 1:3 | wc -l' cat <seq inbox/cur/1:2, inbox/cur/2:2, inbox/cur/3:2, ! check_test 'whole thread at the end' -eq 3 'mseq 2= | wc -l' check_test 'subthread at the end' -eq 2 'mseq 2_ | wc -l' ) mblaze-0.3.2/t/8000-mflag.t000066400000000000000000000022521324061207500150720ustar00rootroot00000000000000#!/bin/sh -e cd ${0%/*} . ./lib.sh plan 16 rm -rf test.dir mkdir test.dir ( cd test.dir cat <seq inbox/cur/1:2, inbox/cur/2:2, ! mkdir -p inbox/cur while read f; do touch "$f"; done /dev/null 1>&2; then printf 'ok - %s\n' "$msg" else printf 'not ok - %s\n' "$msg" false fi true } check_test() { msg=$1; op=$2; test=$3; shift 3 if [ "$(eval "$@" 2>/dev/null)" "$op" "$test" ]; then printf 'ok - %s\n' "$msg" else printf 'not ok - %s\n' "$msg" false fi true } check_same() { msg=$1 shift eval "$1" 2>/dev/null 1>&2 >out1 || true eval "$2" 2>/dev/null 1>&2 >out2 || true diff -u out1 out2 || true check "$msg" cmp out1 out2 } mblaze-0.3.2/u8decode.h000066400000000000000000000022021324061207500146330ustar00rootroot00000000000000#include // Decode one UTF-8 codepoint into cp, return number of bytes to next one. // On invalid UTF-8, return -1, and do not change cp. // Invalid codepoints are not checked. // // This code is meant to be inlined, if cp is unused it can be optimized away. static int u8decode(const char *cs, uint32_t *cp) { const uint8_t *s = (uint8_t *)cs; if (*s == 0) { *cp = 0; return 0; } if (*s < 0x80) { *cp = *s; return 1; } if (*s < 0xc2) { return -1; } //cont+overlong if (*s < 0xe0) { *cp = *s & 0x1f; goto u2; } if (*s < 0xf0) { if (*s == 0xe0 && (s[1] & 0xe0) == 0x80) return -1; //overlong if (*s == 0xed && (s[1] & 0xe0) == 0xa0) return -1; //surrogate *cp = *s & 0x0f; goto u3; } if (*s < 0xf5) { if (*s == 0xf0 && (s[1] & 0xf0) == 0x80) return -1; //overlong if (*s == 0xf4 && (s[1] > 0x8f)) return -1; //too high *cp = *s & 0x07; goto u4; } return -1; u4: if ((*++s & 0xc0) != 0x80) return -1; *cp = (*cp << 6) | (*s & 0x3f); u3: if ((*++s & 0xc0) != 0x80) return -1; *cp = (*cp << 6) | (*s & 0x3f); u2: if ((*++s & 0xc0) != 0x80) return -1; *cp = (*cp << 6) | (*s & 0x3f); return s - (uint8_t *)cs + 1; }