├── .mailmap ├── .travis.yml ├── COPYING ├── GNUmakefile ├── INSTALL.md ├── NEWS.md ├── README ├── VERSION ├── VIOLATIONS.md ├── blaze822.c ├── blaze822.h ├── blaze822_priv.h ├── contrib ├── README.md ├── _mblaze ├── malternative ├── mblow ├── mcount ├── mencrypt ├── menter ├── mfillmid ├── mgpg ├── mhasatt ├── mmailto ├── mmairix ├── mopenall ├── mp7m ├── mpeek ├── mraw ├── mrecode ├── msearch ├── msearch.1 ├── msendmail ├── msign ├── msuck ├── mtwoscan ├── mverify └── mvi ├── filter.c ├── filter.example ├── maddr.c ├── magrep.c ├── man ├── maddr.1 ├── magrep.1 ├── mblaze-profile.5 ├── mblaze.7 ├── mbnc.1 ├── mcom.1 ├── mdeliver.1 ├── mdirs.1 ├── mexport.1 ├── mflag.1 ├── mflow.1 ├── mfwd.1 ├── mgenmid.1 ├── mhdr.1 ├── minc.1 ├── mless.1 ├── mlist.1 ├── mmime.1 ├── mmkdir.1 ├── mmsg.7 ├── mpick.1 ├── mrefile.1 ├── mrep.1 ├── mscan.1 ├── msed.1 ├── mseq.1 ├── mshow.1 ├── msort.1 └── mthread.1 ├── mbnc ├── mcolor ├── mcom ├── mdate.c ├── mdeliver.c ├── mdirs.c ├── mexport.c ├── mflag.c ├── mflow.c ├── mgenmid.c ├── mhdr.c ├── minc.c ├── mless ├── mlesskey.example ├── mlist.c ├── mmime.c ├── mmkdir ├── mpick.c ├── mquote ├── mrep ├── mscan.c ├── msed.c ├── mseq.c ├── mshow.c ├── msort.c ├── mthread.c ├── mymemmem.c ├── mystrverscmp.c ├── mytimegm.c ├── pipeto.c ├── rfc2045.c ├── rfc2047.c ├── rfc2231.c ├── safe_u8putstr.c ├── seq.c ├── slurp.c ├── squeeze_slash.c ├── t ├── 1000-mmime.t ├── 1100-mhdr.t ├── 1500-maddr.t ├── 1501-maddr-regress.t ├── 1700-mshow.t ├── 1701-mshow-regress.t ├── 1702-mshow-attachments.t ├── 1800-mexport.t ├── 1900-mdeliver.t ├── 2000-mpick.t ├── 3000-magrep.t ├── 4000-msed.t ├── 4500-mflow.t ├── 5000-mscan.t ├── 6000-msort.t ├── 7000-mseq.t ├── 8000-mflag.t ├── 9000-minc.t └── lib.sh ├── u8decode.h └── xpledge.h /.mailmap: -------------------------------------------------------------------------------- 1 | Leah Neukirchen Christian Neukirchen 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | os: 4 | - linux 5 | - osx 6 | 7 | compiler: 8 | - clang 9 | - gcc 10 | 11 | script: 12 | - case "$(uname)" in 13 | Linux) make CFLAGS="-g -O2 -Wall -Wno-switch -Wextra -D_FORTIFY_SOURCE=2" && make check || exit 1 ;; 14 | Darwin) make LDLIBS=-liconv && make check || exit 1 ;; 15 | esac 16 | 17 | notifications: 18 | email: false 19 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | mblaze is in the public domain. 2 | 3 | To the extent possible under law, Leah Neukirchen 4 | has waived all copyright and related or neighboring rights to this work. 5 | http://creativecommons.org/publicdomain/zero/1.0/ 6 | 7 | 8 | The files mystrverscmp.c and mymemmem 9 | are MIT-licensed and were written by Rich Felker. 10 | 11 | mpick.c is largely based on code by Leah Neukirchen 12 | and has been contributed by Duncan Overbruck 13 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | CFLAGS?=-g -O2 -fstack-protector-strong -D_FORTIFY_SOURCE=2 2 | override CFLAGS:=-Wall -Wno-switch -Wextra $(CFLAGS) 3 | LDFLAGS?=-fstack-protector-strong 4 | LDLIBS=-lrt 5 | 6 | OS := $(shell uname) 7 | 8 | ifeq ($(OS),OpenBSD) 9 | LOCALBASE=/usr/local 10 | override CFLAGS+=-I$(LOCALBASE)/include -pthread 11 | LDLIBS=-L$(LOCALBASE)/lib -liconv -pthread 12 | endif 13 | 14 | ifeq ($(OS),Darwin) 15 | LDLIBS=-liconv 16 | endif 17 | 18 | DESTDIR= 19 | PREFIX=/usr/local 20 | BINDIR=$(PREFIX)/bin 21 | MANDIR=$(PREFIX)/share/man 22 | 23 | ALL = maddr magrep mdate mdeliver mdirs mexport mflag mflow mgenmid mhdr minc mlist mmime mpick mscan msed mseq mshow msort mthread 24 | SCRIPT = mcolor mcom mless mmkdir mquote museragent 25 | 26 | all: $(ALL) museragent 27 | 28 | $(ALL) : % : %.o 29 | maddr magrep mdeliver mdirs mexport mflag mflow mgenmid mhdr mpick mscan msed \ 30 | mshow msort mthread : blaze822.o mymemmem.o mytimegm.o 31 | maddr magrep mdeliver mdirs mexport mflag mgenmid mhdr minc mlist mpick mscan \ 32 | msed mseq mshow msort mthread : seq.o slurp.o mystrverscmp.o 33 | maddr magrep mflow mhdr mpick mscan mshow : rfc2047.o 34 | magrep mflow mhdr mshow : rfc2045.o 35 | mshow : filter.o safe_u8putstr.o rfc2231.o pipeto.o 36 | mscan : pipeto.o 37 | mmime : slurp.o 38 | minc mlist : squeeze_slash.o 39 | 40 | museragent: FRC 41 | @test -n "$$SOURCE_DATE_EPOCH" || BUILDDATE=$$(date '+ (%Y-%m-%d)'); \ 42 | printf '#!/bin/sh\nprintf "User-Agent: mblaze/%s%s\\n"\n' \ 43 | "$$({ git describe --always --dirty 2>/dev/null || cat VERSION; } | sed 's/^v//')" \ 44 | "$$BUILDDATE" >$@ 45 | @chmod +x $@ 46 | 47 | README: man/mblaze.7 48 | mandoc -Tutf8 $< | col -bx >$@ 49 | 50 | clean: FRC 51 | -rm -f $(ALL) *.o museragent 52 | 53 | check: FRC all 54 | PATH=$$(pwd):$$PATH prove -v =1.2 with mdeliver <1.2 if you 20 | require verbatim message delivery. 21 | * mshow: add "-A all" to render all attachments 22 | * msed: match header names case insensitively 23 | * mless: prefer setting LESSKEYIN and using .mlesskey 24 | * mcom: take Delivered-To into account for choosing From address 25 | * Many bug fixes. 26 | 27 | ## 1.1 (2021-01-14) 28 | 29 | * mcom: allow tilde prefixed path for profile's outbox setting 30 | * mcom: detect and report mmime errors 31 | * add contrib/mmailto, a handler for mailto: links 32 | * Bug fixes. 33 | 34 | ## 1.0 (2020-09-12) 35 | 36 | * Caution! Backwards incompatible changes: 37 | * As a message name, `-` now refers to the message on the standard input, 38 | and not the previous message anymore. Use `.-` to refer to the previous 39 | message in a short way. 40 | The tools will print a warning if you use `-` and the standard input 41 | comes from a TTY. 42 | * mpick: use the -F flag to read script files. 43 | * mpick: remove msglist support, use plain mmsg(7) arguments. 44 | * Many mblaze tools now make use of pledge(2) on OpenBSD. 45 | * add contrib/mcount, a tool to count mails 46 | * mrep: use Reply-From configuration to find From header 47 | * Many bug fixes. 48 | 49 | ## 0.7 (2020-05-15) 50 | 51 | * All tools now follow symlinks to mails. 52 | * mdirs: add -a to list all subfolders, ignoring Maildir++ convention. 53 | * mcom: add preview alias for show command. 54 | * mrep/mbnc: allow only one message as argument. 55 | * maddr: add -d to only print display name. 56 | * mthread: add -r to reverse top-level order. 57 | * mlist: print number of matches when message selection is in place. 58 | * mpick: many improvements. 59 | * Many bug fixes. 60 | 61 | ## 0.6 (2020-01-18) 62 | 63 | * mfwd: prefix Subject with "Fwd:". 64 | * mscan: add dottime formatting. 65 | * mlist: look at maildir/new too for messages. 66 | * Many bug and portability fixes. 67 | 68 | ## 0.5.1 (2019-03-03) 69 | 70 | * mdeliver: preserve mtime in mrefile 71 | * mdirs: add -0 to separate folders by NUL characters 72 | * Fixes for buffer-overflows, found by fuzzing. 73 | * Fixes for memleaks. 74 | 75 | ## 0.5 (2019-02-09) 76 | 77 | * New tool msearch to wrap several mail indexers. 78 | * New zsh completion _mblaze. 79 | * mnext/mprev were removed (you can call `mless +`/`mless .-`). 80 | * The GnuPG tools in contrib/ now use gpg2. 81 | * mshow exits with error if it could not extract all attachments 82 | * mrep: add -noquote to disable quoting the message replied to 83 | * mdeliver: keep permissions of messages 84 | * mcom: aborting the editor is now more like delete than cancel 85 | * mcom: add -send to send directly without editing 86 | * mcom: check if mail is formatted sensibly 87 | * mpick: new flag -v for statistics 88 | * mscan: new flag -v for statistics 89 | * magrep: add -h, which is like -p but doesn't print the file name 90 | * mscan: prioritize displaying trashed mail over other markers 91 | * mpick: fix off-by-one in expression parsing 92 | * Many bug fixes. 93 | 94 | ## 0.4 (2018-08-15) 95 | 96 | * New tool mrefile to move or copy messages. 97 | * contrib/mp7m: add application/pkcs7-mime decoder 98 | * mcom: allow setting arbitrary headers from command line 99 | * mcom: add -body to prepopulate drafts 100 | * mcom: mark drafts as seen after sending 101 | * mcom: update flags after mrep/mbnc/mfwd 102 | * mshow: add -B to decode MIME parameters in broken mails 103 | * magrep: add -l flag 104 | * Many bug fixes. 105 | 106 | ## 0.3.2 (2018-02-13) 107 | 108 | * magrep: add *:REGEX to search in any header 109 | * Fix of a buffer overflow in blaze822_multipart. 110 | * Small bug fixes. 111 | * Many documentation improvements by Larry Hynes. 112 | 113 | ## 0.3.1 (2018-01-30) 114 | 115 | * mless: support $NO_COLOR 116 | * mcolor: support $NO_COLOR 117 | * blaze822.h: ensure PATH_MAX is defined 118 | * Improved documentation. 119 | * Many fixes for address parser. 120 | 121 | ## 0.3 (2018-01-12) 122 | 123 | * New tool mflow to reformat format=flowed plain text mails. 124 | * New tool mbnc to bounce mails (send original mail to someone else). 125 | * mshow filters can output raw text now, e.g. for HTML rendering with colors. 126 | * mrep can quote mail that doesn't have a plain text part. 127 | * mcom runs mmime when deemed necessary. 128 | * mhdr can extract MIME parameters. 129 | * New contrib: mrecode 130 | * New contrib: mraw 131 | * mshow regards non-MIME mails as MIME mails with one part now. 132 | * mshow -F to disable MIME filters. 133 | * mpick supports negations now. 134 | * msed can remove headers depending on their value. 135 | * Improved UTF-8 parsing. 136 | * Improved documentation. 137 | * Numerous bug fixes and portability fixes. 138 | 139 | ## 0.2 (2017-07-17) 140 | 141 | * New sequence syntax `m:+n` for `n` messages after message `m`. 142 | * Threading shortcuts `=`, `_`, `^` for `.=`, `._`, `.^`. 143 | * Sequence related errors are now reported. 144 | * minc and mlist normalize slashes in paths. 145 | * mfwd now generates conforming message/rfc822 parts. 146 | * mthread can add optional folders (e.g. your outbox) to resolve message ids. 147 | * mcom now adds Date: just before sending or cancelling the mail. 148 | * VIOLATIONS.md documents how mblaze works with certain common mistakes. 149 | * Full documentation revamp by Larry Hynes. 150 | * Fix rare crash looking for mail body. 151 | * Numerous small bug and portability fixes. 152 | 153 | ## 0.1 (2017-06-24) 154 | 155 | * Initial release 156 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.3 2 | -------------------------------------------------------------------------------- /VIOLATIONS.md: -------------------------------------------------------------------------------- 1 | # Standard-violations detected in the wild during development of mblaze 2 | 3 | This list is probably not complete. 4 | 5 | * RFC5322 assumes CRLF line endings throughout, but Maildir messages 6 | are generally using Unix line endings. mblaze accepts both, and 7 | only uses CRLF when required (e.g. for signing). 8 | 9 | * Backslashes in atoms (RFC 5322, 3.2.3) are parsed as if they were 10 | inside quoted strings. 11 | 12 | * Return-path is accepted without angle-addr (RFC5322, 3.6.7). 13 | 14 | * Encoded words within quoted strings (RFC2047, 5.3) are decoded for 15 | header printing. 16 | 17 | * Encoded words within MIME parameters (RFC2047, 5.3) are NOT decoded. 18 | 19 | * Empty encoded words are decoded as empty string (RFC2047, 2). 20 | 21 | * Split multi-octet characters between encoded words (RFC2047, 5.3) 22 | are reassembled if the encodings agree. 23 | 24 | * Date parsing is strict, obsolete timezone and two-digit years are 25 | not parsed (RFC5322, 4.3). 26 | 27 | * Mails without MIME-Version (RFC2045, 4) are still subject to 28 | MIME decoding if the Content-Transfer-Encoding header is present. 29 | -------------------------------------------------------------------------------- /blaze822.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #if !defined(PATH_MAX) 8 | #define PATH_MAX 4096 9 | #endif 10 | 11 | // maildir filename suffix: use a semicolon on winnt/ntfs, otherwise a colon 12 | #ifdef __winnt__ 13 | #define MAILDIR_COLON ';' 14 | #define MAILDIR_COLON_SPEC_VER ";2" 15 | #define MAILDIR_COLON_SPEC_VER_COMMA ";2," 16 | #else 17 | #define MAILDIR_COLON ':' 18 | #define MAILDIR_COLON_SPEC_VER ":2" 19 | #define MAILDIR_COLON_SPEC_VER_COMMA ":2," 20 | #endif 21 | 22 | struct message; 23 | 24 | // blaze822.c 25 | 26 | struct message *blaze822(char *file); // just header 27 | struct message *blaze822_file(char *file); // header + body (read(2)) 28 | struct message *blaze822_mem(char *buf, size_t len); // header + body 29 | 30 | char *blaze822_hdr_(struct message *mesg, const char *hdr, size_t len); 31 | #define blaze822_hdr(mesg, hdr) blaze822_hdr_(mesg, "\0" hdr ":", 2+strlen((hdr))) 32 | char *blaze822_chdr(struct message *mesg, const char *chdr); 33 | 34 | char *blaze822_next_header(struct message *mesg, char *prev); 35 | 36 | void blaze822_free(struct message *mesg); 37 | 38 | time_t blaze822_date(char *); 39 | char *blaze822_addr(char *, char **, char **); 40 | char *blaze822_body(struct message *mesg); 41 | size_t blaze822_bodylen(struct message *mesg); 42 | size_t blaze822_headerlen(struct message *mesg); 43 | 44 | char *blaze822_orig_header(struct message *mesg); 45 | 46 | // rfc2047.c 47 | 48 | int blaze822_decode_rfc2047(char *, char *, size_t, char *); 49 | int blaze822_decode_qp(char *start, char *stop, char **deco, size_t *decleno, int underscore); 50 | int blaze822_decode_b64(char *start, char *stop, char **deco, size_t *decleno); 51 | 52 | // rfc2045.c 53 | 54 | int blaze822_check_mime(struct message *msg); 55 | int blaze822_mime_body(struct message *msg, char **cto, char **bodyo, size_t *bodyleno, char **bodychunko); 56 | int blaze822_multipart(struct message *msg, struct message **imsg); 57 | int blaze822_mime_parameter(char *s, char *name, char **starto, char **stopo); 58 | 59 | typedef enum { MIME_CONTINUE, MIME_STOP, MIME_PRUNE } blaze822_mime_action; 60 | typedef blaze822_mime_action (*blaze822_mime_callback)(int, struct message *, char *, size_t); 61 | blaze822_mime_action blaze822_walk_mime(struct message *, int, blaze822_mime_callback); 62 | 63 | // rfc2231.c 64 | 65 | int blaze822_mime2231_parameter(char *, char *, char *, size_t, char *); 66 | 67 | // seq.c 68 | 69 | char *blaze822_seq_open(char *file); 70 | int blaze822_seq_load(char *map); 71 | long blaze822_seq_find(char *ref); 72 | 73 | 74 | char *blaze822_seq_cur(void); 75 | int blaze822_seq_setcur(char *s); 76 | 77 | struct blaze822_seq_iter { 78 | long lines; 79 | long cur; 80 | long start; 81 | long stop; 82 | 83 | long line; 84 | char *s; 85 | }; 86 | 87 | char *blaze822_seq_next(char *map, char *range, struct blaze822_seq_iter *iter); 88 | long blaze822_loop(int, char **, void (*)(char *)); 89 | long blaze822_loop1(char *arg, void (*cb)(char *)); 90 | char *blaze822_home_file(char *basename); 91 | 92 | // filter.c 93 | 94 | int filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno); 95 | 96 | // mygmtime.c 97 | 98 | time_t mytimegm(const struct tm *tm); 99 | 100 | 101 | // slurp.c 102 | 103 | int slurp(char *filename, char **bufo, off_t *leno); 104 | 105 | // safe_u8putstr.c 106 | 107 | #include 108 | void safe_u8putstr(char *s0, size_t l, int oneline, FILE *stream); 109 | 110 | // pipeto.c 111 | 112 | pid_t pipeto(const char *cmdline); 113 | int pipeclose(pid_t pid); 114 | 115 | // squeeze_slash.c 116 | 117 | void squeeze_slash(char *); 118 | -------------------------------------------------------------------------------- /blaze822_priv.h: -------------------------------------------------------------------------------- 1 | struct message { 2 | char *msg; 3 | char *end; 4 | char *body; 5 | char *bodyend; 6 | char *bodychunk; 7 | char *orig_header; 8 | }; 9 | 10 | // WSP = SP / HTAB 11 | #define iswsp(c) (((c) == ' ' || (c) == '\t')) 12 | 13 | #define isfws(c) (((unsigned char)(c) == ' ' || (unsigned char)(c) == '\t' || (unsigned char)(c) == '\n' || (unsigned char)(c) == '\r')) 14 | 15 | // 7bit-ASCII only lowercase/uppercase 16 | #define lc(c) (((unsigned)(c)-'A') < 26 ? ((c) | 0x20) : (c)) 17 | #define uc(c) (((unsigned)(c)-'a') < 26 ? ((c) & 0xdf) : (c)) 18 | 19 | // dirent type that can be a mail/dir (following symlinks) 20 | #if defined(DT_REG) && defined(DT_LNK) && defined(DT_UNKNOWN) 21 | #define MAIL_DT(t) (t == DT_REG || t == DT_LNK || t == DT_UNKNOWN) 22 | #define DIR_DT(t) (t == DT_DIR || t == DT_UNKNOWN) 23 | #else 24 | #define MAIL_DT(t) (1) 25 | #define DIR_DT(t) (1) 26 | #endif 27 | 28 | void *mymemmem(const void *h0, size_t k, const void *n0, size_t l); 29 | -------------------------------------------------------------------------------- /contrib/README.md: -------------------------------------------------------------------------------- 1 | # mblaze contrib 2 | 3 | This directory contains a few scripts and hacks that are not 4 | officially supported or subject to any robustness, portability or 5 | stability criteria. 6 | 7 | Use at your own risk. 8 | -------------------------------------------------------------------------------- /contrib/malternative: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # malternative - multipart/alternative decoding policy 3 | 4 | { 5 | echo "Content-Type: $PIPE_CONTENTTYPE" 6 | echo 7 | cat 8 | } | mshow -t - | awk ' 9 | BEGIN { split("", ct) } 10 | /^ [0-9]/ { ct[++n] = $2 } 11 | function prefer(t) { for (i in ct) if (ct[i] == t) exit(64+i) } 12 | END { 13 | prefer("text/plain") 14 | prefer("text/html") 15 | exit 64+1 # default to first part 16 | }' 17 | -------------------------------------------------------------------------------- /contrib/mblow: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # mblow - post an article via NNTP 3 | 4 | require 'socket' 5 | require 'optparse' 6 | 7 | params = ARGV.getopts("s:") 8 | 9 | port = 119 10 | if params["s"] =~ /(.*):(.*)/ 11 | params["s"] = $1 12 | port = Integer($2) 13 | end 14 | 15 | SERVER = params["s"] || ENV["NNTPSERVER"] || "news" 16 | 17 | nntp = TCPSocket.new SERVER, port 18 | 19 | msg = nntp.gets 20 | abort msg unless msg =~ /^200 / 21 | 22 | nntp.write "POST\r\n" 23 | msg = nntp.gets 24 | 25 | abort msg unless msg =~ /^340 / 26 | 27 | while line = gets 28 | line.chomp! 29 | line.sub!(/\A\./, '..') 30 | nntp.write(line + "\r\n") 31 | end 32 | 33 | nntp.write(".\r\n") 34 | msg = nntp.gets 35 | 36 | abort msg unless msg =~ /^240 / 37 | puts msg 38 | -------------------------------------------------------------------------------- /contrib/mcount: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mcount - count mails from sequence on stdin 3 | 4 | [ -t 1 ] && stdout=1 5 | 6 | awk -v stdout=$stdout ' 7 | !/^ * "/dev/stderr" } 10 | ' 11 | -------------------------------------------------------------------------------- /contrib/mencrypt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mencrypt PLAINMSG - generate a PGP/MIME signed and encrypted message 3 | 4 | [ -f "$1" ] || exit 1 5 | 6 | IFS=' 7 | ' 8 | FLAGS=$(maddr -a -h from:to:cc:bcc: "$1" |sort -u |sed 's/^/--recipient=/') 9 | 10 | FROM=$(maddr -a -h from "$1" | sed 1q) 11 | [ "$FROM" ] && key="--default-key=$FROM" 12 | 13 | if command -v gpg2 >/dev/null; then 14 | GPG=gpg2 15 | else 16 | GPG=gpg 17 | fi 18 | 19 | TMPD=$(mktemp -d -t mencrypt.XXXXXX) 20 | trap "rm -rf '$TMPD'" INT TERM EXIT 21 | 22 | awk '/^$/,0' "$1" | 23 | mmime | 24 | $GPG "$key" --armor --encrypt --sign $FLAGS -o "$TMPD/msg.asc" || 25 | exit $? 26 | 27 | printf 'Version: 1\n' >"$TMPD/version" 28 | 29 | { 30 | sed '/^$/q' "$1" 31 | printf '#application/pgp-encrypted %s/version\n' "$TMPD" 32 | printf '#application/octet-stream %s/msg.asc\n' "$TMPD" 33 | } | 34 | mmime -t 'multipart/encrypted; protocol="application/pgp-encrypted"' 35 | -------------------------------------------------------------------------------- /contrib/menter: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # menter [MSG] - run subshell in temporary directory with MSG extracted 3 | 4 | [ "$#" -eq 0 ] && set -- . 5 | 6 | f="$(mseq "$1" | sed 1q)" 7 | [ -z "$f" ] && printf 'No message %s.\n' "$1" 1>&2 && exit 1 8 | 9 | dir=$(mktemp -d -t menter.XXXXXX) 10 | cd "$dir" 11 | mshow -t "$1" 12 | mshow -x "$1" 2>/dev/null 13 | ls -l 14 | ln -s "$f" msg 15 | "${SHELL:-/bin/sh}" || true 16 | echo rm -r "$dir" 17 | rm -r "$dir" 18 | -------------------------------------------------------------------------------- /contrib/mfillmid: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mfillmid - fill in files for message-ids (via mairix) 3 | 4 | exec awk ' 5 | function q(a) { gsub("\\47", "\47\\\47\47", a); return "\47"a"\47" } 6 | /<..*>/ { 7 | match($0, "<..*>") 8 | mid = substr($0, RSTART+1, RLENGTH-2) 9 | if ("mairix -r m:" q(mid) | getline file) { 10 | print substr($0, 0, RSTART-1) file 11 | next 12 | } 13 | } 14 | { print }' 15 | -------------------------------------------------------------------------------- /contrib/mgpg: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if command -v gpg2 >/dev/null; then 4 | GPG=gpg2 5 | else 6 | GPG=gpg 7 | fi 8 | 9 | tmp=$(mktemp -t mgpg.XXXXXX) 10 | trap "rm -f '$tmp'" INT TERM EXIT 11 | 12 | { 13 | echo "Content-Type: $PIPE_CONTENTTYPE" 14 | echo 15 | cat 16 | } > "$tmp" 17 | 18 | n=$(mshow -t "$tmp" | awk -F: ' 19 | /: application\/pgp-encrypted/ {supported = 1} 20 | /: application\/octet-stream/ {if (supported) print $1}') 21 | 22 | if [ "$n" ]; then 23 | mshow -O "$tmp" "$n" | $GPG --quiet -d 2>&1 || exit 0 24 | exit 64 25 | fi 26 | exit 63 27 | -------------------------------------------------------------------------------- /contrib/mhasatt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mhasatt [RANGE...] - print mails with real attachments 3 | 4 | mshow -t "${@:-:}" | awk ' 5 | /^[^ 0-9]/ && /\// { file = $0 } 6 | /^.*[0-9]*: (application\/pkcs7-signature)/ { next } 7 | /^.*[0-9]*: (application|image|video)\// || /^.*[0-9]*:.*name=/ 8 | { if (file) print file; file = 0 } 9 | ' 10 | -------------------------------------------------------------------------------- /contrib/mmailto: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mmailto mailto:... - mailto: handler spawning mcom in a terminal emulator 3 | 4 | tryterm() { 5 | if [ -z "$TERMINAL" ] && command -v "$1" >/dev/null; then 6 | TERMINAL="$*" 7 | fi 8 | } 9 | 10 | tryterm x-terminal-emulator 11 | tryterm urxvt 12 | tryterm xterm 13 | 14 | if [ -z "$TERMINAL" ]; then 15 | echo 'No terminal emulator found, set $TERMINAL.' 1>&2 16 | exit 1 17 | fi 18 | 19 | IFS=' 20 | ' 21 | 22 | exec $TERMINAL -e mcom $( 23 | awk -v url="$1" ' 24 | 25 | function decode(s) { 26 | hexdigits = "0123456789abcdef" 27 | for (i = 1; i < length(s); i++) { 28 | if (substr(s, i, 3) ~ /%[0-9a-fA-F][0-9a-fA-F]/) { 29 | c = sprintf("%c", (index(hexdigits, tolower(substr(s, i+1, 1)))-1) * 16 + \ 30 | index(hexdigits, tolower(substr(s, i+2, 1)))-1) 31 | if (c == "\n") c = " " 32 | s = substr(s, 1, i-1) c substr(s, i+3) 33 | i += 2 34 | } 35 | } 36 | return s 37 | } 38 | 39 | BEGIN { 40 | url = decode(url) 41 | sub(/^mailto:/, "", url) 42 | split(url, parts, "?") 43 | to = parts[1] 44 | split(parts[2], fields, "&") 45 | args[1] = to 46 | for (i in fields) { 47 | split(fields[i], kv, "=") 48 | if (kv[1] != "r") { 49 | args[length(args)+1] = "-" kv[1] 50 | args[length(args)+1] = kv[2] 51 | } 52 | } 53 | for (i in args) { 54 | print decode(args[i]) 55 | } 56 | } 57 | ' 58 | ) 59 | -------------------------------------------------------------------------------- /contrib/mmairix: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mmairix QUERY - mblaze wrapper around mairix 3 | 4 | mairix -r "$@" | sed 's,//*,/,g' | msort -rd | mless 5 | -------------------------------------------------------------------------------- /contrib/mopenall: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # mopenall [MSG] - open every attachements in xdg-open 3 | 4 | [ "$#" -eq 0 ] && set -- . 5 | 6 | f="$(mseq "$1" | sed 1q)" 7 | [ -z "$f" ] && printf 'No message %s.\n' "$1" 1>&2 && exit 1 8 | 9 | dir=$(mktemp -d -t menter.XXXXXX) 10 | cd "$dir" 11 | mshow -t "$1" 12 | mshow -B -x "$1" 2>/dev/null 13 | for f in * ; do 14 | xdg-open "$f" & 15 | done 16 | wait 17 | echo rm -r "$dir" 18 | rm -r "$dir" 19 | -------------------------------------------------------------------------------- /contrib/mp7m: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mp7m - decode S/MIME .p7m format 3 | 4 | tmp=$(mktemp -t mp7m.XXXXXX) 5 | trap "rm -f '$tmp'" INT TERM EXIT 6 | 7 | cat >"$tmp" 8 | 9 | if openssl pkcs7 -print_certs <"$tmp" | 10 | openssl smime -verify -in "$tmp" -inform DER -noverify -signer /dev/stdin 11 | then 12 | exit 64 13 | else 14 | exit 63 15 | fi 16 | -------------------------------------------------------------------------------- /contrib/mpeek: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mpeek - wrapper around mscan with a different seq 3 | 4 | export MAILSEQ=${MBLAZE:-$HOME/.mblaze}/peek.seq 5 | 6 | if [ -t 0 ]; then 7 | mseq "$@" 8 | else 9 | mseq -S | mscan "$@" 10 | fi 11 | 12 | -------------------------------------------------------------------------------- /contrib/mraw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mraw [MSGS...] - display raw messages 3 | 4 | IFS=' 5 | ' 6 | exec cat -- $(mseq "${@:-.}" | sed 's/^ *//') 7 | -------------------------------------------------------------------------------- /contrib/mrecode: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mrecode - recode stdin respecting PIPE_CHARSET into UTF-8 3 | 4 | case "$PIPE_CHARSET" in 5 | ''|*[Uu][Nn][Kk][Nn][Oo][Ww][Nn]*) exec cat;; 6 | *) exec iconv -f "$PIPE_CHARSET" -t UTF-8;; 7 | esac 8 | 9 | -------------------------------------------------------------------------------- /contrib/msearch: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | MBLAZE=${MBLAZE:-$HOME/.mblaze} 4 | engine=$(mhdr -h search-engine "$MBLAZE/profile") 5 | 6 | while getopts nmx opt; do 7 | case $opt in 8 | n) 9 | engine=notmuch 10 | ;; 11 | m) 12 | engine=mu 13 | ;; 14 | x) 15 | engine=mairix 16 | ;; 17 | '?') 18 | printf "Usage: %s: [-n | -m | -x] query\n" "$0" 1>&2 19 | exit 1 20 | ;; 21 | esac 22 | done 23 | shift $(($OPTIND - 1)) 24 | 25 | [ -z "$engine" ] && engine=notmuch 26 | 27 | case $engine in 28 | notmuch) 29 | exec notmuch search --output=files "$@" 30 | ;; 31 | mu) 32 | exec mu find --fields l "$@" 33 | ;; 34 | mairix) 35 | if [ "$#" -eq 0 ]; then 36 | printf "Usage: %s -x query\n" "$0" 1>&2 37 | exit 1 38 | fi 39 | exec mairix -r "$@" 40 | ;; 41 | *) 42 | echo "Unsupported search engine: $engine" 43 | exit 1 44 | ;; 45 | esac 46 | -------------------------------------------------------------------------------- /contrib/msearch.1: -------------------------------------------------------------------------------- 1 | .Dd September 27, 2018 2 | .Dt MSEARCH 1 3 | .Os 4 | .Sh NAME 5 | .Nm msearch 6 | .Nd search messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl n | Fl m | Fl x 10 | .Op Fl - 11 | .Op Ar query 12 | .Sh DESCRIPTION 13 | .Nm 14 | searches for messages matching 15 | .Ar query , 16 | using an external search engine, and prints file names separated by newlines. 17 | .Pp 18 | The options are as follows: 19 | .Bl -tag -width Ds 20 | .It Fl n 21 | Search using 22 | .Xr notmuch-search 1 . 23 | .It Fl m 24 | Search using 25 | .Xr mu-find 1 . 26 | .It Fl x 27 | Search using 28 | .Xr mairix 1 . 29 | .It Fl - 30 | Stop scanning for arguments. 31 | .It Ar query 32 | The search query, whose interpretation is up to the search engine. 33 | This can also contain additional arguments for them 34 | .El 35 | .Pp 36 | If no search engine is specified as an argument, 37 | .Sq Li Search-Engine\&: 38 | from 39 | .Pa "${MBLAZE:-$HOME/.mblaze}/profile" 40 | is used. 41 | Valid values for this setting are 42 | .Sq notmuch , 43 | .Sq mairix , 44 | and 45 | .Sq mu . 46 | If that is unset, 47 | .Xr notmuch-search 1 48 | is used. 49 | .Sh ENVIRONMENT 50 | .Bl -tag -width Ds 51 | .It Ev MBLAZE 52 | Directory containing mblaze configuration files. 53 | (Default: 54 | .Pa $HOME/.mblaze ) 55 | .El 56 | .Sh EXIT STATUS 57 | .Ex -std 58 | Note that the called programs may have their own exit values. 59 | .Sh SEE ALSO 60 | .Xr mairix 1 , 61 | .Xr mu-find 1 , 62 | .Xr notmuch-search 1 , 63 | .Xr mblaze-profile 5 64 | .Sh AUTHORS 65 | .An Thomas Schneider Aq Mt qsx@chaotikum.eu 66 | .Sh LICENSE 67 | .Nm 68 | is in the public domain. 69 | .Pp 70 | To the extent possible under law, 71 | the creator of this work 72 | has waived all copyright and related or 73 | neighboring rights to this work. 74 | .Pp 75 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 76 | -------------------------------------------------------------------------------- /contrib/msendmail: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # msendmail TO... < msg - compose MIME mail noninteractively 3 | # -F from-name 4 | # -a attach-file 5 | # -b bcc 6 | # -c cc 7 | # -f from 8 | # -s subject 9 | # -m msg-paragraph 10 | 11 | IFS=' 12 | ' 13 | 14 | MBLAZE=${MBLAZE:-$HOME/.mblaze} 15 | from=$(mhdr -h local-mailbox "$MBLAZE/profile") 16 | 17 | subj= 18 | bcc= 19 | cc= 20 | msg= 21 | fromname= 22 | att= 23 | 24 | hdr() { 25 | [ -z "$2" ] && return 26 | printf '%s: ' "$1" 27 | shift 28 | printf '%s\n' "$@" | sed ':a;N;s/\n/, /;$!b a' 29 | } 30 | 31 | while getopts a:s:b:c:m:f:F: opt; do 32 | case "$opt" in 33 | s) subj=$OPTARG;; 34 | b) bcc="$bcc$IFS$OPTARG";; 35 | c) cc="$cc$IFS$OPTARG";; 36 | m) msg="$msg$OPTARG$IFS$IFS";; 37 | f) from=$OPTARG;; 38 | F) fromname=$OPTARG;; 39 | a) att="$att$IFS$OPTARG";; 40 | [?]) exit 1;; 41 | esac 42 | done 43 | shift $((OPTIND-1)) 44 | 45 | [ -n "$from" ] && [ -n "$fromname" ] && from="$fromname <$from>" 46 | 47 | { 48 | hdr To "$@" 49 | hdr Cc $cc 50 | hdr Bcc $bcc 51 | hdr Subject $subj 52 | hdr From $from 53 | hdr User-Agent "mblaze/beta (msendmail)" 54 | hdr Message-Id "$(mgenmid)" 55 | hdr Date "$(mdate)" 56 | 57 | printf '\n' 58 | if [ -n "$msg" ]; then 59 | printf '%s' "$msg" 60 | else 61 | cat 62 | fi 63 | 64 | for a in $att; do 65 | printf '#%s %s\n' "$(file -b --mime-type -- "$a")" "$a" 66 | done 67 | } | mmime 68 | -------------------------------------------------------------------------------- /contrib/msign: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # msign PLAINMSG - generate a PGP/MIME signed message 3 | 4 | [ -f "$1" ] || exit 1 5 | 6 | if command -v gpg2 >/dev/null; then 7 | GPG=gpg2 8 | else 9 | GPG=gpg 10 | fi 11 | 12 | IFS=' 13 | ' 14 | 15 | TMPD=$(mktemp -d -t msign.XXXXXX) 16 | trap "rm -rf '$TMPD'" INT TERM EXIT 17 | 18 | FROM=$(maddr -a -h from "$1" | sed 1q) 19 | [ "$FROM" ] && key="--default-key=$FROM" 20 | 21 | awk '/^$/,0' "$1" | mmime | sed 's/$/ /' >"$TMPD"/content 22 | $GPG $key --armor --detach-sign -o "$TMPD"/signature.asc "$TMPD"/content || 23 | exit $? 24 | 25 | { 26 | sed '/^$/q' "$1" 27 | printf '#mblaze/raw %s/content\n' "$TMPD" 28 | printf '#application/pgp-signature %s/signature.asc\n' "$TMPD" 29 | } | 30 | mmime -t 'multipart/signed; micalg="pgp-sha1"; protocol="application/pgp-signature"' 31 | 32 | -------------------------------------------------------------------------------- /contrib/msuck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # msuck - suck NNTP groups into Maildirs 3 | # 4 | # msuck [-s NNTPSERVER[:PORT]] [-d BASEDIR] [-l LIMIT] GROUPS... to fetch GROUPS 5 | # msuck [-s NNTPSERVER[:PORT]] -L to list all groups 6 | 7 | require 'socket' 8 | require 'fileutils' 9 | require 'optparse' 10 | 11 | $delivery = 0 12 | HOST = Socket.gethostname 13 | def genname(id) 14 | $delivery += 1 15 | t = Time.now 16 | "%d.M%06dP%05dQ%d.%s,N=%d" % [t.tv_sec, t.tv_usec, $$, $delivery, HOST, id] 17 | end 18 | 19 | params = ARGV.getopts("d:fl:s:L") 20 | dir = params["d"] || '.' 21 | LIMIT = if params["l"] 22 | Integer(params["l"]) 23 | else 24 | 10 25 | end 26 | 27 | port = 119 28 | if params["s"] =~ /(.*):(.*)/ 29 | params["s"] = $1 30 | port = Integer($2) 31 | end 32 | 33 | SERVER = params["s"] || ENV["NNTPSERVER"] || "news" 34 | 35 | nntp = TCPSocket.new SERVER, port 36 | 37 | msg = nntp.gets 38 | abort msg unless msg =~ /^20[01] / 39 | 40 | if params["L"] # list all groups 41 | nntp.write("LIST NEWSGROUPS\r\n") 42 | msg = nntp.gets 43 | if msg !~ /^215 / 44 | abort msg 45 | end 46 | 47 | loop { 48 | msg = nntp.gets 49 | break if msg == ".\r\n" 50 | puts msg 51 | } 52 | 53 | exit 54 | end 55 | 56 | STDOUT.sync = true 57 | 58 | ARGV.each { |group| 59 | 60 | nntp.write("GROUP #{group}\r\n") 61 | 62 | msg = nntp.gets 63 | unless msg =~ /^211 / 64 | STDERR.puts msg 65 | $exit = 69 66 | next 67 | end 68 | 69 | _, number, low, high, _ = msg.split(" ", 5) 70 | 71 | number = number.to_i 72 | low = low.to_i 73 | high = high.to_i 74 | 75 | low = high - LIMIT + 1 if number > LIMIT - 1 76 | low = 1 if low <= 0 77 | 78 | FileUtils.mkdir_p(File.join(dir, group, "cur")) 79 | FileUtils.mkdir_p(File.join(dir, group, "new")) 80 | FileUtils.mkdir_p(File.join(dir, group, "tmp")) 81 | 82 | have = Dir.entries(File.join(dir, group, "cur")). 83 | map { |f| $1.to_i if f =~ /N=(\d+)/ }.compact 84 | 85 | ourhigh = have.max 86 | if ourhigh && low < ourhigh && !params["f"] 87 | low = ourhigh + 1 88 | end 89 | 90 | next if low > high 91 | 92 | printf "%s %d-%d ", group, low, high 93 | 94 | nntp.write("STAT #{low}\r\n") 95 | msg = nntp.gets 96 | _, num, mid, _ = msg.split(" ", 4) 97 | 98 | loop { 99 | unless have.include? num.to_i 100 | nntp.write("ARTICLE\r\n") 101 | msg = nntp.gets 102 | 103 | if msg =~ /^220 / 104 | _, num, mid, _ = msg.split(" ", 4) 105 | 106 | text = ["X-Msuck: nntp://#{SERVER}/#{group}/#{num}\n"] 107 | loop { 108 | msg = nntp.gets 109 | break if msg == ".\r\n" 110 | msg.sub!(/\A\./, "") 111 | msg.sub!(/\r\n\z/, "\n") 112 | text << msg 113 | } 114 | text = text.join 115 | 116 | name = genname(num) 117 | 118 | File.write(File.join(dir, group, "tmp", name), text) 119 | File.rename(File.join(dir, group, "tmp", name), 120 | File.join(dir, group, "cur", name + ":2,")) 121 | print "." 122 | else 123 | STDERR.puts msg 124 | end 125 | else 126 | print "=" 127 | end 128 | 129 | nntp.write("NEXT\r\n") 130 | msg = nntp.gets 131 | if msg !~ /^223 / 132 | break 133 | end 134 | _, num, mid, _ = msg.split(" ", 4) 135 | } 136 | 137 | puts 138 | } 139 | 140 | exit $exit || 0 141 | -------------------------------------------------------------------------------- /contrib/mtwoscan: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mtwoscan - mscan with a two-line format 3 | 4 | exec mscan -f '%-4n%c%2i%s\n %2i%-3M %16D (%b) %f' "$@" 5 | -------------------------------------------------------------------------------- /contrib/mverify: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mverify MSG - verify a OpenPGP or SMIME message 3 | 4 | # Needs gpg2 (for OpenPGP) and openssl (for SMIME). 5 | 6 | if command -v gpg2 >/dev/null; then 7 | GPG=gpg2 8 | else 9 | GPG=gpg 10 | fi 11 | 12 | [ "$#" -eq 0 ] && set -- . 13 | 14 | mshow -t "$1" | DOS2UNIX='/ $/!s/$/ /' awk -v "msg=$1" ' 15 | { match($0, "^ *"); indent = RLENGTH } 16 | $2 == "text/plain" { plain++ } 17 | $2 == "multipart/signed" { signed = 0+$1; si = indent; next } 18 | signed && !content && indent == si+2 { content = 0+$1; next } 19 | signed && content && !signature && indent == si+2 { signature = 0+$1; type = $2 } 20 | function q(a) { gsub("\\47", "\47\\\47\47", a); return "\47"a"\47" } 21 | END { 22 | if (type == "" && plain) { // guess plain text armored signature 23 | exit(system("mshow -r " q(msg) " | '$GPG' --verify")); 24 | } else if (type == "") { 25 | print("No signature found.") 26 | exit(100) 27 | } else if (type == "application/pgp-signature") { 28 | exit(system("mshow -r -O " q(msg) " " q(content) \ 29 | " | sed $DOS2UNIX | " \ 30 | " { mshow -O " q(msg) " " q(signature) \ 31 | " | '$GPG' --verify - /dev/fd/3; } 3<&0")) 32 | } else if (type == "application/pkcs7-signature") { 33 | exit(system("mshow -r -O " q(msg) " " q(signed) \ 34 | " | openssl smime -verify")) 35 | } else { 36 | print("Cannot verify signatures of type " type ".") 37 | exit(2) 38 | } 39 | } 40 | ' 41 | -------------------------------------------------------------------------------- /filter.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | int 15 | filter(char *input, size_t inlen, char *cmd, char **outputo, size_t *outleno) 16 | { 17 | char *output; 18 | ssize_t outlen; 19 | ssize_t outalloc = 4096; 20 | pid_t pid; 21 | sigset_t mask, orig_mask; 22 | int r; 23 | 24 | sigemptyset(&mask); 25 | sigaddset(&mask, SIGPIPE); 26 | sigprocmask(SIG_BLOCK, &mask, &orig_mask); 27 | 28 | outlen = 0; 29 | output = malloc(outalloc); 30 | if (!output) 31 | goto fail; 32 | 33 | int pipe0[2]; 34 | int pipe1[2]; 35 | 36 | if (pipe(pipe0) != 0 || pipe(pipe1) != 0) 37 | goto fail; 38 | 39 | int got = fcntl(pipe0[1], F_GETFL); 40 | if (got > 0) 41 | fcntl(pipe0[1], F_SETFL, got | O_NONBLOCK); 42 | 43 | char *argv[] = { "/bin/sh", "-c", cmd, (char *)0 }; 44 | 45 | if (!(pid = fork())) { 46 | dup2(pipe0[0], 0); 47 | close(pipe0[1]); 48 | close(pipe0[0]); 49 | 50 | dup2(pipe1[1], 1); 51 | close(pipe1[0]); 52 | close(pipe1[1]); 53 | 54 | execvp(argv[0], argv); 55 | exit(-1); 56 | } 57 | close(pipe0[0]); 58 | close(pipe1[1]); 59 | 60 | if (pid < 0) { 61 | close(pipe0[1]); 62 | close(pipe1[0]); 63 | goto fail; 64 | } 65 | 66 | struct pollfd fds[2]; 67 | 68 | fds[0].fd = pipe1[0]; 69 | fds[0].events = POLLIN | POLLHUP; 70 | fds[1].fd = pipe0[1]; 71 | fds[1].events = POLLOUT; 72 | 73 | while ((fds[0].fd >= 0 || fds[1].fd >= 0) && 74 | poll(fds, sizeof fds / sizeof fds[0], -1) >= 0) { 75 | if (fds[0].revents & POLLIN) { 76 | if (outlen + 512 > outalloc) { 77 | outalloc *= 2; 78 | if (outalloc < 0) 79 | exit(-1); 80 | output = realloc(output, outalloc); 81 | if (!output) 82 | exit(-1); 83 | } 84 | ssize_t ret = read(fds[0].fd, output + outlen, 512); 85 | if (ret > 0) 86 | outlen += ret; 87 | else 88 | close(fds[0].fd); 89 | } else if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { 90 | fds[0].fd = -1; 91 | } 92 | 93 | if (fds[1].revents & POLLOUT) { 94 | ssize_t ret = write(fds[1].fd, input, inlen); 95 | if (ret > 0) { 96 | input += ret; 97 | inlen -= ret; 98 | } 99 | if (ret <= 0 && errno == EAGAIN) { 100 | /* ignore */ 101 | } else if (ret <= 0 || inlen == 0) 102 | close(fds[1].fd); 103 | } else if (fds[1].revents & (POLLERR | POLLHUP | POLLNVAL)) { 104 | fds[1].fd = -1; 105 | } 106 | } 107 | 108 | // ok to fail when closed already 109 | close(pipe0[1]); 110 | close(pipe1[0]); 111 | 112 | int status; 113 | waitpid(pid, &status, 0); 114 | r = WEXITSTATUS(status); 115 | 116 | *outputo = output; 117 | *outleno = outlen; 118 | 119 | if (0) { 120 | fail: 121 | *outputo = 0; 122 | *outleno = 0; 123 | free(output); 124 | r = -1; 125 | } 126 | 127 | sigpending(&mask); 128 | if (sigismember(&mask, SIGPIPE)) { 129 | int sig; 130 | sigwait(&mask, &sig); 131 | } 132 | sigprocmask(SIG_SETMASK, &orig_mask, 0); 133 | 134 | return r; 135 | } 136 | 137 | #ifdef TEST 138 | int 139 | main() 140 | { 141 | char *input = "foo\nbar\nbaz"; 142 | int e; 143 | 144 | char *output; 145 | size_t outlen; 146 | 147 | e = filter(input, strlen(input), "rev;exit 2", &output, &outlen); 148 | 149 | fwrite(output, 1, outlen, stdout); 150 | printf("%ld -> %d\n", outlen, e); 151 | 152 | return 0; 153 | } 154 | #endif 155 | -------------------------------------------------------------------------------- /filter.example: -------------------------------------------------------------------------------- 1 | text/plain: mflow 2 | text/html: lynx -dump -stdin -nomargins ${PIPE_CHARSET:+-assume_charset $PIPE_CHARSET} 3 | application/pdf: pdftotext - - | par 4 | application: file -b - 5 | image: file -b - 6 | -------------------------------------------------------------------------------- /maddr.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "blaze822.h" 10 | #include "xpledge.h" 11 | 12 | static int aflag; 13 | static int dflag; 14 | static char defaulthflags[] = "from:sender:reply-to:to:cc:bcc:" 15 | "resent-from:resent-sender:resent-to:resent-cc:resent-bcc:"; 16 | static char *hflag = defaulthflags; 17 | 18 | void 19 | print_quoted(char *s) 20 | { 21 | char *t; 22 | 23 | for (t = s; *t; t++) 24 | if ((unsigned char)*t < 32 || strchr("()<>[]:;@\\,.\"", *t)) 25 | goto quote; 26 | 27 | printf("%s", s); 28 | return; 29 | 30 | quote: 31 | putchar('"'); 32 | for (t = s; *t; t++) { 33 | if (*t == '"' || *t == '\\') 34 | putchar('\\'); 35 | putchar(*t); 36 | } 37 | putchar('"'); 38 | 39 | } 40 | 41 | void 42 | addr(char *file) 43 | { 44 | while (*file == ' ' || *file == '\t') 45 | file++; 46 | 47 | struct message *msg = blaze822(file); 48 | if (!msg) 49 | return; 50 | 51 | char *h = hflag; 52 | char *v; 53 | while (*h) { 54 | char *n = strchr(h, ':'); 55 | if (n) 56 | *n = 0; 57 | v = blaze822_chdr(msg, h); 58 | if (v) { 59 | char *disp, *addr; 60 | char ddec[16384]; 61 | while ((v = blaze822_addr(v, &disp, &addr))) { 62 | if (disp) { 63 | blaze822_decode_rfc2047(ddec, disp, sizeof ddec - 1, "UTF-8"); 64 | ddec[sizeof ddec - 1] = 0; 65 | disp = ddec; 66 | } 67 | 68 | if (disp && addr && strcmp(disp, addr) == 0) 69 | disp = 0; 70 | 71 | if (aflag) { 72 | if (addr) 73 | printf("%s\n", addr); 74 | } else if (dflag) { 75 | if (disp) { 76 | print_quoted(disp); 77 | printf("\n"); 78 | } 79 | } else { 80 | if (disp && addr) { 81 | print_quoted(disp); 82 | printf(" <%s>\n", addr); 83 | } else if (addr) { 84 | printf("%s\n", addr); 85 | } 86 | } 87 | } 88 | } 89 | if (n) { 90 | *n = ':'; 91 | h = n + 1; 92 | } else { 93 | break; 94 | } 95 | } 96 | blaze822_free(msg); 97 | } 98 | 99 | int 100 | main(int argc, char *argv[]) 101 | { 102 | int c; 103 | while ((c = getopt(argc, argv, "adh:")) != -1) 104 | switch (c) { 105 | case 'a': aflag = 1; break; 106 | case 'd': dflag = 1; break; 107 | case 'h': hflag = optarg; break; 108 | default: 109 | fprintf(stderr, 110 | "Usage: maddr [-ad] [-h headers] [msgs...]\n"); 111 | exit(1); 112 | } 113 | 114 | xpledge("stdio rpath", ""); 115 | 116 | if (argc == optind && isatty(0)) 117 | blaze822_loop1(":", addr); 118 | else 119 | blaze822_loop(argc-optind, argv+optind, addr); 120 | 121 | return 0; 122 | } 123 | -------------------------------------------------------------------------------- /magrep.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "blaze822.h" 15 | #include "xpledge.h" 16 | 17 | static int aflag; 18 | static int cflag; 19 | static int dflag; 20 | static int hflag; 21 | static int iflag; 22 | static int lflag; 23 | static int oflag; 24 | static int pflag; 25 | static int qflag; 26 | static int vflag; 27 | static long mflag; 28 | static long matches; 29 | 30 | static regex_t pattern; 31 | static char *header; 32 | static char *curfile; 33 | 34 | int 35 | match(char *file, char *hdr, char *s) 36 | { 37 | if (oflag && !cflag && !qflag && !vflag && !lflag) { 38 | regmatch_t pmatch = {0}; 39 | int len, matched; 40 | matched = 0; 41 | while (*s && regexec(&pattern, s, 1, &pmatch, 0) == 0) { 42 | s += pmatch.rm_so; 43 | if (!(len = pmatch.rm_eo-pmatch.rm_so)) { 44 | s += 1; 45 | continue; 46 | } 47 | if (pflag) 48 | printf("%s: %s: ", file, hdr); 49 | else if (hflag) 50 | printf("%s: ", hdr); 51 | printf("%.*s\n", len, s); 52 | s += len; 53 | matched++; 54 | } 55 | return (matched && matches++); 56 | } else if (vflag ^ (regexec(&pattern, s, 0, 0, 0) == 0)) { 57 | if (qflag) 58 | exit(0); 59 | matches++; 60 | if (!cflag) { 61 | if (vflag || !hflag) 62 | printf("%s", file); 63 | if (pflag && !vflag) 64 | printf(": %s: %s", hdr, s); 65 | else if (hflag && !vflag) 66 | printf("%s: %s", hdr, s); 67 | putchar('\n'); 68 | } 69 | if (mflag && matches >= mflag) 70 | exit(0); 71 | return 1; 72 | } 73 | 74 | return 0; 75 | } 76 | 77 | blaze822_mime_action 78 | match_part(int depth, struct message *msg, char *body, size_t bodylen) 79 | { 80 | (void)depth; 81 | 82 | char *ct = blaze822_hdr(msg, "content-type"); 83 | 84 | blaze822_mime_action r = MIME_CONTINUE; 85 | 86 | if (!ct || strncmp(ct, "text/plain", 10) == 0) { 87 | char *charset = 0, *cs, *cse; 88 | if (blaze822_mime_parameter(ct, "charset", &cs, &cse)) 89 | charset = strndup(cs, cse-cs); 90 | if (!charset || 91 | strcasecmp(charset, "utf-8") == 0 || 92 | strcasecmp(charset, "utf8") == 0 || 93 | strcasecmp(charset, "us-ascii") == 0) { 94 | if ((hflag || pflag) && !cflag && !oflag && !vflag) { 95 | char *s, *p; 96 | for (p = s = body; p < body+bodylen+1; p++) { 97 | if (*p == '\r' || *p == '\n') { 98 | *p = 0; 99 | if (p-s > 1) 100 | match(curfile, "/", s); 101 | s = p+1; 102 | } 103 | } 104 | } else if (match(curfile, "/", body)) { 105 | r = MIME_STOP; 106 | } 107 | } else { 108 | /* XXX decode here */ 109 | } 110 | free(charset); 111 | } 112 | 113 | return r; 114 | } 115 | 116 | void 117 | match_body(char *file) 118 | { 119 | char *filename; 120 | filename = curfile = file; 121 | while (*filename == ' ' || *filename == '\t') 122 | filename++; 123 | 124 | struct message *msg = blaze822_file(filename); 125 | if (!msg) 126 | return; 127 | 128 | blaze822_walk_mime(msg, 0, match_part); 129 | blaze822_free(msg); 130 | } 131 | 132 | int 133 | match_value(char *file, char *h, char *v) 134 | { 135 | if (dflag) { 136 | char d[4096]; 137 | blaze822_decode_rfc2047(d, v, sizeof d, "UTF-8"); 138 | return match(file, h, d); 139 | } else if (aflag) { 140 | char *disp, *addr; 141 | while ((v = blaze822_addr(v, &disp, &addr))) { 142 | if (addr && match(file, h, addr)) 143 | return 1; 144 | } 145 | } else { 146 | return match(file, h, v); 147 | } 148 | return 0; 149 | } 150 | 151 | void 152 | magrep(char *file) 153 | { 154 | if (!*header) { 155 | char *flags = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA); 156 | if (flags) 157 | match(file, "flags", flags+3); 158 | return; 159 | } else if (strcmp(header, "/") == 0) { 160 | match_body(file); 161 | return; 162 | } 163 | 164 | char *filename = file; 165 | while (*filename == ' ' || *filename == '\t') 166 | filename++; 167 | 168 | struct message *msg = blaze822(filename); 169 | if (!msg) 170 | return; 171 | 172 | if (strcmp(header, "*") == 0) { 173 | char *hdr = 0; 174 | while ((hdr = blaze822_next_header(msg, hdr))) { 175 | char *v = strchr(hdr, ':'); 176 | if (v) { 177 | *v = 0; 178 | if (match_value(file, hdr, v + 1 + (v[1] == ' ')) && lflag) 179 | break; 180 | *v = ':'; 181 | } 182 | } 183 | } else { 184 | char *v = blaze822_chdr(msg, header); 185 | if (v) 186 | (void)match_value(file, header, v); 187 | } 188 | 189 | blaze822_free(msg); 190 | } 191 | 192 | int 193 | main(int argc, char *argv[]) 194 | { 195 | setlocale(LC_ALL, ""); // for localized regexp 196 | 197 | int c; 198 | while ((c = getopt(argc, argv, "acdhilm:opqv")) != -1) 199 | switch (c) { 200 | case 'a': aflag = 1; break; 201 | case 'c': cflag = 1; break; 202 | case 'd': dflag = 1; break; 203 | case 'h': hflag = 1; break; 204 | case 'i': iflag = REG_ICASE; break; 205 | case 'l': lflag = 1; break; 206 | case 'm': mflag = atol(optarg); break; 207 | case 'o': oflag = 1; break; 208 | case 'p': pflag = 1; break; 209 | case 'q': qflag = 1; break; 210 | case 'v': vflag = 1; break; 211 | default: 212 | usage: 213 | fprintf(stderr, 214 | "Usage: magrep [-c|-h|-o|-p|-q|-m max] [-v] [-i] [-l] [-a|-d] header:regex [msgs...]\n"); 215 | exit(2); 216 | } 217 | 218 | if (argc == optind) 219 | goto usage; 220 | header = argv[optind++]; 221 | 222 | char *rx = strchr(header, ':'); 223 | if (!rx) 224 | goto usage; 225 | 226 | xpledge("stdio rpath", ""); 227 | 228 | *rx++ = 0; 229 | int r = regcomp(&pattern, rx, REG_EXTENDED | iflag); 230 | if (r != 0) { 231 | char buf[256]; 232 | regerror(r, &pattern, buf, sizeof buf); 233 | fprintf(stderr, "magrep: regex error: %s\n", buf); 234 | exit(2); 235 | } 236 | 237 | if (argc == optind && isatty(0)) 238 | blaze822_loop1(":", magrep); 239 | else 240 | blaze822_loop(argc-optind, argv+optind, magrep); 241 | 242 | if (cflag && !qflag && !mflag) 243 | printf("%ld\n", matches); 244 | 245 | return !matches; 246 | } 247 | -------------------------------------------------------------------------------- /man/maddr.1: -------------------------------------------------------------------------------- 1 | .Dd March 30, 2020 2 | .Dt MADDR 1 3 | .Os 4 | .Sh NAME 5 | .Nm maddr 6 | .Nd extract mail addresses from messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl ad 10 | .Op Fl h Ar headers 11 | .Op Ar msgs\ ... 12 | .Sh DESCRIPTION 13 | .Nm 14 | prints, separated by newlines, all mail addresses found in the 15 | .Ar headers 16 | of the specified 17 | .Ar msgs . 18 | .Po See 19 | .Xr mmsg 7 20 | for the message argument syntax. 21 | .Pc 22 | .Pp 23 | If no 24 | .Ar msgs 25 | are specified 26 | and 27 | .Nm 28 | is used interactively, 29 | .Nm 30 | will operate on the current sequence by default. 31 | Otherwise, 32 | .Nm 33 | will read filenames from the standard input. 34 | .Pp 35 | The options are as follows: 36 | .Bl -tag -width Ds 37 | .It Fl a 38 | Only print the addr-spec address, not the display name. 39 | .It Fl d 40 | Only print the display name. 41 | .It Fl h Ar headers 42 | Only search the colon-separated list of 43 | .Ar headers 44 | for mail addresses. 45 | Default: 46 | .Sq Li from\&:sender\&:reply\&-to\&:to\&:cc\&:bcc\&: 47 | and their respective 48 | .Sq Li resent\&- 49 | variants, if any. 50 | .El 51 | .Sh EXIT STATUS 52 | .Ex -std 53 | .Sh SEE ALSO 54 | .Xr mmsg 7 55 | .Sh AUTHORS 56 | .An Leah Neukirchen Aq Mt leah@vuxu.org 57 | .Sh LICENSE 58 | .Nm 59 | is in the public domain. 60 | .Pp 61 | To the extent possible under law, 62 | the creator of this work 63 | has waived all copyright and related or 64 | neighboring rights to this work. 65 | .Pp 66 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 67 | -------------------------------------------------------------------------------- /man/magrep.1: -------------------------------------------------------------------------------- 1 | .Dd September 10, 2018 2 | .Dt MAGREP 1 3 | .Os 4 | .Sh NAME 5 | .Nm magrep 6 | .Nd search messages matching a pattern 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl c | Fl h | Fl o | Fl p | Fl q | Fl m Ar max 10 | .Op Fl v 11 | .Op Fl i 12 | .Op Fl l 13 | .Op Fl a | Fl d 14 | .Ar header Ns Cm \&: Ns Ar regex 15 | .Op Ar msgs\ ... 16 | .Sh DESCRIPTION 17 | .Nm 18 | prints the names of files from the specified 19 | .Ar msgs 20 | if the value of 21 | .Ar header 22 | matches the POSIX Extended Regular Expression 23 | .Ar regex . 24 | .Po 25 | See 26 | .Xr mmsg 7 27 | for the message argument syntax. 28 | .Pc 29 | .Pp 30 | If 31 | .Ar header 32 | is empty, 33 | .Nm 34 | matches against the maildir flags of 35 | .Ar msgs . 36 | .Pp 37 | If 38 | .Ar header 39 | is 40 | .Sq Cm \&* , 41 | .Nm 42 | searches for the pattern in any header. 43 | .Pp 44 | If 45 | .Ar header 46 | is 47 | .Sq Cm \&/ , 48 | .Nm 49 | searches any plain text parts of the 50 | .Ar msgs 51 | body. 52 | .Pp 53 | If no 54 | .Ar msgs 55 | are specified and 56 | .Nm 57 | is used interactively, 58 | the current sequence will be searched. 59 | .Pp 60 | The options are as follows: 61 | .Bl -tag -width Ds 62 | .It Fl a 63 | Search for 64 | .Ar regex 65 | in RFC 2822 address 66 | .Ar header 67 | parts only. 68 | .It Fl c 69 | Only print a count of matching headers. 70 | If 71 | .Fl v 72 | is specified, 73 | count non-matching headers instead. 74 | .It Fl d 75 | Decode the 76 | .Ar header 77 | according to RFC 2047 prior to searching. 78 | .It Fl h 79 | Like 80 | .Fl p 81 | but do not print the file name. 82 | .It Fl i 83 | Match 84 | .Ar regex 85 | case insensitively. 86 | .It Fl l 87 | Only search a message until a match has been found. 88 | .It Fl m Ar max 89 | Do not show more than 90 | .Ar max 91 | matches. 92 | .It Fl o 93 | Print each match only, 94 | not the entire line. 95 | This option is ignored if 96 | .Fl c , 97 | .Fl q 98 | or 99 | .Fl v 100 | is specified. 101 | .It Fl p 102 | Print the filename, 103 | the header and the matching line 104 | for each of the matched 105 | .Ar msgs . 106 | If 107 | .Fl o 108 | is specified each match is printed, 109 | instead of the matching line. 110 | This option is ignored if 111 | .Fl c , 112 | .Fl q 113 | or 114 | .Fl v 115 | is specified. 116 | .It Fl q 117 | Quiet mode: do not print anything, 118 | quit as soon as possible. 119 | .It Fl v 120 | Invert the match; print (or count) all files where 121 | .Ar regex 122 | does not match. 123 | .El 124 | .Sh EXIT STATUS 125 | The 126 | .Nm 127 | utility exits 0 on success, 1 if no match was found 128 | and >1 if an error occurs. 129 | .Sh SEE ALSO 130 | .Xr grep 1 , 131 | .Xr mmsg 7 , 132 | .Xr regex 7 / 133 | .Xr re_format 7 134 | .Sh AUTHORS 135 | .An Leah Neukirchen Aq Mt leah@vuxu.org 136 | .Sh TRIVIA 137 | .Nm 138 | is not called mgrep because many tools with this name already exist. 139 | .Sh LICENSE 140 | .Nm 141 | is in the public domain. 142 | .Pp 143 | To the extent possible under law, 144 | the creator of this work 145 | has waived all copyright and related or 146 | neighboring rights to this work. 147 | .Pp 148 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 149 | -------------------------------------------------------------------------------- /man/mblaze-profile.5: -------------------------------------------------------------------------------- 1 | .Dd September 11, 2024 2 | .Dt MBLAZE-PROFILE 5 3 | .Os 4 | .Sh NAME 5 | .Nm mblaze-profile 6 | .Nd configuration of the mblaze message system 7 | .Sh DESCRIPTION 8 | The 9 | .Xr mblaze 7 10 | message system can be configured with a 11 | .Pa profile 12 | file, 13 | which is located in the directory 14 | .Pa ~/.mblaze 15 | (or 16 | .Ev MBLAZE 17 | if set.) 18 | .Pp 19 | .Pa profile 20 | consists of lines, similar to RFC 2822 headers, in the form 21 | .Dl Ar key Ns \&: Ar value 22 | .Pp 23 | The key 24 | .Sq Cm \&# 25 | may be used to precede a comment: 26 | .Dl Li "#:" Ar comment 27 | .Pp 28 | Please note that empty lines will halt parsing. 29 | .Pp 30 | The following 31 | .Ar key Ns s 32 | are used by 33 | .Xr mblaze 7 : 34 | .Bl -tag -width Ds 35 | .It Li Local\&-Mailbox\&: 36 | Your primary mail address, used as the default value for 37 | .Li From\&: 38 | in 39 | .Xr mcom 1 , 40 | and in 41 | .Xr mscan 1 42 | to recognize messages sent to you. 43 | .It Li Alternate\&-Mailboxes\&: 44 | A comma-separated list of mail addresses that also belong to you, for 45 | .Xr mscan 1 46 | to recognize messages sent by or directly to you. 47 | .It Li FQDN\&: 48 | The fully qualified domain name used for 49 | .Li Message\&-Id\&: 50 | generation in 51 | .Xr mgenmid 1 . 52 | .It Li Maildir\&: 53 | If set, 54 | .Xr mdirs 1 55 | will use this maildir when no directories are supplied. 56 | .It Li Outbox\&: 57 | If set, 58 | .Xr mcom 1 59 | will save messages in this maildir after sending. 60 | .It Li Drafts\&: 61 | If set, 62 | .Xr mcom 1 63 | will create draft messages in this maildir (defaults to Outbox). 64 | .It Li Reply-From\&: 65 | A comma-separated list of display name and mail address pairs, 66 | formatted like this: 67 | .Dl Li Primary Name , Name v.2 , \[dq]Name, My Third\[dq] , ... 68 | The first of these that appears in the 69 | .Li Delivered-To\&: , 70 | .Li To\&: , 71 | .Li Cc\&: , 72 | or 73 | .Li Bcc\&: 74 | header will be used as the 75 | .Li From\&: 76 | address in a reply. 77 | If not set, 78 | .Li Alternate\&-Mailboxes\&: 79 | will be used as a default. 80 | .It Li Scan\&-Format\&: 81 | The default format string for 82 | .Xr mscan 1 . 83 | .It Li Sendmail\&: 84 | The program that 85 | .Xr mcom 1 86 | will call to send mail. 87 | (Default: 88 | .Sq Li sendmail ) . 89 | .It Li Sendmail\&-Args\&: 90 | Flags to be passed to the 91 | .Li Sendmail\&: 92 | program. 93 | (Default: 94 | .Sq Fl t ) . 95 | .It Li Senthook\&: 96 | If set, 97 | .Xr mcom 1 98 | will call this on the mail file after sending, 99 | and not refile the sent mail to outbox. 100 | .El 101 | .Sh ENVIRONMENT 102 | .Bl -tag -width Ds 103 | .It Ev MBLAZE 104 | Directory containing mblaze configuration files. 105 | (Default: 106 | .Pa $HOME/.mblaze ) 107 | .El 108 | .Sh AUTHORS 109 | .An Leah Neukirchen Aq Mt leah@vuxu.org 110 | .Sh LICENSE 111 | .Nm mblaze 112 | is in the public domain. 113 | .Pp 114 | To the extent possible under law, 115 | the creator of this work 116 | has waived all copyright and related or 117 | neighboring rights to this work. 118 | .Pp 119 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 120 | -------------------------------------------------------------------------------- /man/mbnc.1: -------------------------------------------------------------------------------- 1 | .so man1/mcom.1 2 | -------------------------------------------------------------------------------- /man/mcom.1: -------------------------------------------------------------------------------- 1 | .Dd April 21, 2021 2 | .Dt MCOM 1 3 | .Os 4 | .Sh NAME 5 | .Nm mcom , 6 | .Nm mfwd , 7 | .Nm mbnc , 8 | .Nm mrep 9 | .Nd compose, forward, bounce, reply, send messages 10 | .Sh SYNOPSIS 11 | .Nm mcom 12 | .Op Fl Ar header Ar values\ ... 13 | .Op Ar recipients\ ... 14 | .Nm mcom 15 | .Fl r Op draft 16 | .Nm mrep 17 | .Op Fl noquote 18 | .Op Fl Ar header Ar values\ ... Fl - 19 | .Op Ar msg 20 | .Nm mfwd 21 | .Op Fl r 22 | .Op Fl Ar header Ar values\ ... Fl - 23 | .Op Ar msgs\ ... 24 | .Nm mbnc 25 | .Op Fl Ar header Ar values\ ... Fl - 26 | .Op Ar msg 27 | .Sh DESCRIPTION 28 | .Nm mcom 29 | creates a new draft message and opens it in an editor. 30 | After editing, a loop is started where the user can send, 31 | re-edit or cancel the message. 32 | Use 33 | .Sq Nm Fl r 34 | to resume the editing of a draft; 35 | by default, the last modified draft will be edited. 36 | .Pp 37 | .Nm mrep 38 | creates the draft such that the message will be a reply to 39 | .Ar msg 40 | .Po 41 | or the current message 42 | .Pc . 43 | Unless 44 | .Fl noquote 45 | is passed, it will contain the original mail quoted. 46 | .Pp 47 | .Nm mfwd 48 | creates the draft with a subject and body appropriate 49 | for forwarding the message. 50 | By default, messages are forwarded verbatim as MIME 51 | .Sq Li message/rfc822 52 | attachments. 53 | Use 54 | .Fl r 55 | to forward as 56 | .Xr mshow 1 57 | rendered plain text, using RFC 934 message encapsulation. 58 | .Pp 59 | .Nm mbnc 60 | creates the draft from the original 61 | .Ar msg 62 | .Po 63 | or the current message 64 | .Pc 65 | including headers, 66 | and adds a Resent-To header, to which the message will 67 | be bounced directly. 68 | .Po 69 | See 70 | .Xr mmsg 7 71 | for the message argument syntax. 72 | .Pc 73 | .Pp 74 | All commands can take optional 75 | .Ar header 76 | flags 77 | .Pq which consist of two or more characters 78 | to prefill header fields, 79 | e.g. you can use 80 | .Sq Nm mcom Fl to No merrilyn Fl cc No elea becci Fl subject No 'Party invite' 81 | to create a draft with the To, Cc and Subject fields already set. 82 | The flag 83 | .Fl body Ar file 84 | can be used to prepopulate the draft for 85 | .Nm mcom 86 | and 87 | .Nm mrep 88 | with the contents of 89 | .Ar file . 90 | .Pp 91 | Note that these flags apply to 92 | .Em all 93 | arguments after them 94 | .Po e.g. 95 | .Sq mcom Fl attach No *.c 96 | works 97 | .Pc , 98 | so you 99 | need to use 100 | .Sq Fl - 101 | when you want to use this feature together with 102 | .Nm mrep , 103 | .Nm mfwd , 104 | or 105 | .Nm mbnc . 106 | .Pp 107 | If the flag 108 | .Fl send 109 | is passed, 110 | the message will be sent directly using the data on 111 | the command line, and no editor or edit loop will be run. 112 | .Sh MENU COMMANDS 113 | .Bl -tag -width 2n 114 | .It Ic s 115 | Send the message. 116 | The MIME version will be used when one has been created. 117 | .It Ic c 118 | Cancel sending and quit, after displaying 119 | the filename of the saved draft. 120 | .It Ic m 121 | Run 122 | .Xr mmime 1 123 | on the draft, and print the result of 124 | .Ic mshow -t . 125 | .It Ic e 126 | Re-edit the draft. 127 | .It Ic d 128 | Delete the draft and quit. 129 | .It Ic p 130 | Preview the draft, using 131 | .Xr mshow 1 . 132 | .El 133 | .Sh ENVIRONMENT 134 | .Bl -tag -width Ds 135 | .It Ev EDITOR , Ev VISUAL 136 | Editor used to compose messages. 137 | (Default: 138 | .Xr vi 1 ) 139 | .It Ev MBLAZE 140 | Directory containing mblaze configuration files. 141 | (Default: 142 | .Pa $HOME/.mblaze ) 143 | .El 144 | .Sh FILES 145 | .Bl -tag -width Ds 146 | .It Pa snd.* 147 | Draft messages, kept in current directory. 148 | This can be configured in 149 | .Xr mblaze-profile 5 . 150 | .It Pa ${MBLAZE:-$HOME/.mblaze}/headers 151 | Default headers for each message. 152 | .It Pa ${MBLAZE:-$HOME/.mblaze}/signature No or Pa ~/.signature 153 | Default signature. 154 | .El 155 | .Sh EXIT STATUS 156 | .Ex -std 157 | .Sh SEE ALSO 158 | .Xr mmime 1 , 159 | .Xr mblaze-profile 5 , 160 | .Xr mmsg 7 161 | .Sh AUTHORS 162 | .An Leah Neukirchen Aq Mt leah@vuxu.org 163 | .Sh LICENSE 164 | .Nm 165 | is in the public domain. 166 | .Pp 167 | To the extent possible under law, 168 | the creator of this work 169 | has waived all copyright and related or 170 | neighboring rights to this work. 171 | .Pp 172 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 173 | -------------------------------------------------------------------------------- /man/mdeliver.1: -------------------------------------------------------------------------------- 1 | .Dd February 4, 2021 2 | .Dt MDELIVER 1 3 | .Os 4 | .Sh NAME 5 | .Nm mdeliver 6 | .Nd deliver messages or import mbox file 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl c 10 | .Op Fl v 11 | .Op Fl X Ar flags 12 | .Ar dir 13 | < 14 | .Ar message 15 | .Nm 16 | .Fl M 17 | .Op Fl c 18 | .Op Fl v 19 | .Op Fl X Ar flags 20 | .Ar dir 21 | < 22 | .Ar mbox 23 | .Sh DESCRIPTION 24 | .Nm 25 | adds a message given on the standard input 26 | as a new message in the maildir 27 | .Ar dir . 28 | .Pp 29 | When 30 | .Fl M 31 | is used, 32 | .Nm 33 | will regard the standard input as 34 | an MBOXRD mailbox, split it on 35 | .Dq Li "From " 36 | and deliver each message, 37 | decoding it according to the MBOXRD convention. 38 | In this case, 39 | .Nm 40 | will set the mtime according to the value of 41 | .Sq Li Date\&: 42 | and the maildir flags according to the value of 43 | .Sq Li Status\&: 44 | or 45 | .Sq Li X-Status\&: . 46 | .Pp 47 | The messages are delivered in a reliable way and use the default 48 | .Xr umask 2 . 49 | .Pp 50 | Please note that no syntactical checks are performed 51 | on the messages. 52 | .Pp 53 | The options are as follows: 54 | .Bl -tag -width Ds 55 | .It Fl M 56 | Deliver each message of an mbox. 57 | .It Fl c 58 | Deliver messages into 59 | .Pa cur/ , 60 | not 61 | .Pa new/ 62 | (the default). 63 | .It Fl v 64 | Print each new message filename after delivery. 65 | .It Fl X Ar flg 66 | Override the flags of the new message file to be 67 | .Ar flg . 68 | .El 69 | .Sh EXIT STATUS 70 | .Ex -std 71 | .Sh SEE ALSO 72 | .Xr mexport 1 , 73 | .Xr mrefile 1 , 74 | .Xr maildir 5 , 75 | .Xr mbox 5 76 | .Pp 77 | .Lk http://www.digitalpreservation.gov/formats/fdd/fdd000385.shtml "MBOXRD Email Format" 78 | .Pp 79 | .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" 80 | .Sh AUTHORS 81 | .An Leah Neukirchen Aq Mt leah@vuxu.org 82 | .Sh LICENSE 83 | .Nm 84 | is in the public domain. 85 | .Pp 86 | To the extent possible under law, 87 | the creator of this work 88 | has waived all copyright and related or 89 | neighboring rights to this work. 90 | .Pp 91 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 92 | -------------------------------------------------------------------------------- /man/mdirs.1: -------------------------------------------------------------------------------- 1 | .Dd July 25, 2023 2 | .Dt MDIRS 1 3 | .Os 4 | .Sh NAME 5 | .Nm mdirs 6 | .Nd list maildir folders, recursively 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl 0a 10 | .Ar dirs\ ... 11 | .Sh DESCRIPTION 12 | .Nm 13 | recursively scans the given 14 | .Ar dirs 15 | for maildir 16 | .Pq and maildir++ 17 | folders and prints them, 18 | separated by newlines. 19 | .Pp 20 | If 21 | .Ar dirs 22 | is not present then use 23 | .Sq Li Maildir\&: 24 | from 25 | .Pa "${MBLAZE:-$HOME/.mblaze}/profile" 26 | .Pq if set . 27 | .Pp 28 | To 29 | .Nm , 30 | a maildir folder is a directory containing 31 | the directories 32 | .Pa cur 33 | and 34 | .Pa new . 35 | By the maildir++ convention, nested maildir folder 36 | names must begin with 37 | .Sq Li \&. . 38 | .Pq This can be disabled using Fl a . 39 | .Pp 40 | The options are as follows: 41 | .Bl -tag -width Ds 42 | .It Fl 0 43 | Print folders separated by a NUL character. 44 | .It Fl a 45 | Traverse into all subfolders, without considering the maildir++ name conventions. 46 | .El 47 | .Sh ENVIRONMENT 48 | .Bl -tag -width Ds 49 | .It Ev MBLAZE 50 | Directory containing mblaze configuration. 51 | .Po 52 | Default: 53 | .Pa $HOME/.mblaze 54 | .Pc 55 | .El 56 | .Sh EXIT STATUS 57 | .Ex -std 58 | .Sh SEE ALSO 59 | .Xr find 1 , 60 | .Xr mblaze-profile 5 61 | .Sh AUTHORS 62 | .An Leah Neukirchen Aq Mt leah@vuxu.org 63 | .Sh LICENSE 64 | .Nm 65 | is in the public domain. 66 | .Pp 67 | To the extent possible under law, 68 | the creator of this work 69 | has waived all copyright and related or 70 | neighboring rights to this work. 71 | .Pp 72 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 73 | -------------------------------------------------------------------------------- /man/mexport.1: -------------------------------------------------------------------------------- 1 | .Dd August 19, 2016 2 | .Dt MEXPORT 1 3 | .Os 4 | .Sh NAME 5 | .Nm mexport 6 | .Nd export messages as mbox file 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl S 10 | .Ar msgs\ ... 11 | .Sh DESCRIPTION 12 | .Nm 13 | writes the specified 14 | .Ar msgs 15 | to the standard output 16 | as an MBOXRD file. 17 | .Po 18 | See 19 | .Xr mmsg 7 20 | for the message argument syntax. 21 | .Pc 22 | .Pp 23 | If no 24 | .Ar msgs 25 | are specified, 26 | .Nm 27 | reads filenames from the standard input, 28 | or uses the messages in the current sequence if used interactively. 29 | .Pp 30 | .Nm 31 | uses the 32 | .Sq Li Return\&-Path\&: 33 | (or 34 | .Sq Li X\&-Envelope\&-To\&: ) 35 | and 36 | .Sq Li Date\&: 37 | headers from each message for the mbox 38 | .Sq Li "From " 39 | line. 40 | .Pp 41 | The options are as follows: 42 | .Bl -tag -width Ds 43 | .It Fl S 44 | Add 45 | .Sq Li Status\&: 46 | and 47 | .Sq Li X\&-Status\&: 48 | headers according to the 49 | .Ar msgs 50 | maildir flags. 51 | .El 52 | .Sh EXIT STATUS 53 | .Ex -std 54 | .Sh SEE ALSO 55 | .Xr mdeliver 1 , 56 | .Xr maildir 5 , 57 | .Xr mbox 5 58 | .Pp 59 | .Lk http://www.digitalpreservation.gov/formats/fdd/fdd000385.shtml "MBOXRD Email Format" 60 | .Pp 61 | .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" 62 | .Sh AUTHORS 63 | .An Leah Neukirchen Aq Mt leah@vuxu.org 64 | .Sh LICENSE 65 | .Nm 66 | is in the public domain. 67 | .Pp 68 | To the extent possible under law, 69 | the creator of this work 70 | has waived all copyright and related or 71 | neighboring rights to this work. 72 | .Pp 73 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 74 | -------------------------------------------------------------------------------- /man/mflag.1: -------------------------------------------------------------------------------- 1 | .Dd July 22, 2016 2 | .Dt MFLAG 1 3 | .Os 4 | .Sh NAME 5 | .Nm mflag 6 | .Nd manipulate maildir message flags 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl DFPRST 10 | .Op Fl X Ar str 11 | .br 12 | .Op Fl dfprst 13 | .Op Fl x Ar str 14 | .br 15 | .Op Fl v 16 | .Op Ar msgs\ ... 17 | .Sh DESCRIPTION 18 | .Nm 19 | changes the flags of the specified maildir 20 | .Ar msgs 21 | according to the options supplied. 22 | .Po 23 | See 24 | .Xr mmsg 7 25 | for the message argument syntax. 26 | .Pc 27 | .Pp 28 | The options are as follows: 29 | .Bl -tag -width Ds 30 | .It Fl D 31 | Mark 32 | .Ar msgs 33 | as draft. 34 | .It Fl F 35 | Mark 36 | .Ar msgs 37 | as flagged. 38 | .It Fl P 39 | Mark 40 | .Ar msgs 41 | as passed 42 | .Pq resent/forwarded/bounced . 43 | .It Fl R 44 | Mark 45 | .Ar msgs 46 | as replied. 47 | .It Fl S 48 | Mark 49 | .Ar msgs 50 | as seen. 51 | .It Fl T 52 | Mark 53 | .Ar msgs 54 | as trashed. 55 | .It Fl X Ar str 56 | Mark 57 | .Ar msgs 58 | with the characters in 59 | .Ar str . 60 | .It Fl d 61 | Unmark 62 | .Ar msgs 63 | as draft. 64 | .It Fl f 65 | Unmark 66 | .Ar msgs 67 | as flagged. 68 | .It Fl p 69 | Unmark 70 | .Ar msgs 71 | as passed 72 | .Pq resent/forwarded/bounced . 73 | .It Fl r 74 | Unmark 75 | .Ar msgs 76 | as replied. 77 | .It Fl s 78 | Unmark 79 | .Ar msgs 80 | as seen. 81 | .It Fl t 82 | Unmark 83 | .Ar msgs 84 | as trashed. 85 | .It Fl x Ar str 86 | Unmark 87 | .Ar msgs 88 | with the characters in 89 | .Ar str . 90 | .Pq Remember to use uppercase characters. 91 | .It Fl v 92 | Read messages from the standard input 93 | .Pq or use the whole current sequence, if used interactively 94 | and print the transformed list of filenames. 95 | This can be used to keep the sequence intact in the case of renames. 96 | .El 97 | .Pp 98 | Note that the link to 99 | .Pa ${MBLAZE:-$HOME/.mblaze}/cur 100 | is updated in all cases, when affected. 101 | .Sh EXIT STATUS 102 | .Ex -std 103 | .Sh SEE ALSO 104 | .Xr mmsg 7 105 | .Pp 106 | .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" 107 | .Sh AUTHORS 108 | .An Leah Neukirchen Aq Mt leah@vuxu.org 109 | .Sh LICENSE 110 | .Nm 111 | is in the public domain. 112 | .Pp 113 | To the extent possible under law, 114 | the creator of this work 115 | has waived all copyright and related or 116 | neighboring rights to this work. 117 | .Pp 118 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 119 | -------------------------------------------------------------------------------- /man/mflow.1: -------------------------------------------------------------------------------- 1 | .Dd September 6, 2017 2 | .Dt MFLOW 1 3 | .Os 4 | .Sh NAME 5 | .Nm mflow 6 | .Nd reflow format=flowed plain text messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl f 10 | .Op Fl q 11 | .Op Fl w Ar width 12 | \&< 13 | .Ar file 14 | .Sh DESCRIPTION 15 | .Nm 16 | reformats the standard input according to the rules 17 | of RFC 3676. 18 | .Ev PIPE_CONTENTTYPE 19 | is inspected, making this a suitable filter 20 | for 21 | .Sq text/plain 22 | messages for 23 | .Xr mshow 1 . 24 | Messages not specified as 25 | .Sq format=flowed 26 | are output unchanged. 27 | .Pp 28 | Text is reflowed (where allowed) to 29 | fit the width given in the environment variable 30 | .Ev COLUMNS , 31 | the terminal width, or 80 characters by default. 32 | .Pp 33 | If defined, 34 | the environment variable 35 | .Ev MAXCOLUMNS 36 | specifies the maximum line length. 37 | .Pp 38 | The options are as follows: 39 | .Bl -tag -width Ds 40 | .It Fl f 41 | Force wrapping of long lines. 42 | .It Fl q 43 | Prefix lines with 44 | .Sq Li \&> . 45 | Can be used multiple times. 46 | .It Fl w Ar width 47 | Set maximum line length to 48 | .Ar width . 49 | .El 50 | .Sh EXIT STATUS 51 | .Ex -std 52 | .Sh SEE ALSO 53 | .Xr mshow 1 54 | .Rs 55 | .%A R. Gellens 56 | .%D February 2004 57 | .%R RFC 3676 58 | .%T The Text/Plain Format and DelSp Parameters 59 | .Re 60 | .Sh AUTHORS 61 | .An Leah Neukirchen Aq Mt leah@vuxu.org 62 | .Sh LICENSE 63 | .Nm 64 | is in the public domain. 65 | .Pp 66 | To the extent possible under law, 67 | the creator of this work 68 | has waived all copyright and related or 69 | neighboring rights to this work. 70 | .Pp 71 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 72 | -------------------------------------------------------------------------------- /man/mfwd.1: -------------------------------------------------------------------------------- 1 | .so man1/mcom.1 2 | -------------------------------------------------------------------------------- /man/mgenmid.1: -------------------------------------------------------------------------------- 1 | .Dd December 25, 2018 2 | .Dt MGENMID 1 3 | .Os 4 | .Sh NAME 5 | .Nm mgenmid 6 | .Nd generate a Message-ID 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Sh DESCRIPTION 10 | .Nm 11 | generates and prints a unique Message-ID. 12 | The Message-ID consists of an encrypted timestamp, 13 | a random value, 14 | and a fully qualified domain name. 15 | .Pp 16 | The fully qualified domain name is arrived at by: 17 | .Bl -enum 18 | .It 19 | Using 20 | .Sq Li FQDN\&: 21 | from 22 | .Pa "${MBLAZE:-$HOME/.mblaze}/profile" 23 | .Pq if set . 24 | .It 25 | Resolving the current hostname. 26 | .It 27 | Using the domain component of the mail address in 28 | .Sq Li Local\&-Mailbox\&: 29 | from 30 | .Pa "${MBLAZE:-$HOME/.mblaze}/profile" 31 | .Pq if set . 32 | .El 33 | .Pp 34 | If these steps don't result in a fully qualified domain name, 35 | .Nm 36 | fails. 37 | .Sh EXIT STATUS 38 | .Ex -std 39 | .Sh SEE ALSO 40 | .Xr mblaze-profile 5 41 | .Rs 42 | .%A M. Curtin 43 | .%A J. Zawinski 44 | .%D July 1998 45 | .%R draft-ietf-usefor-message-id-01.txt 46 | .%T Recommendations for generating Message IDs 47 | .Re 48 | .Sh AUTHORS 49 | .An Leah Neukirchen Aq Mt leah@vuxu.org 50 | .Sh LICENSE 51 | .Nm 52 | is in the public domain. 53 | .Pp 54 | To the extent possible under law, 55 | the creator of this work 56 | has waived all copyright and related or 57 | neighboring rights to this work. 58 | .Pp 59 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 60 | -------------------------------------------------------------------------------- /man/mhdr.1: -------------------------------------------------------------------------------- 1 | .Dd August 17, 2016 2 | .Dt MHDR 1 3 | .Os 4 | .Sh NAME 5 | .Nm mhdr 6 | .Nd print message headers 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl h Ar hdrs Op Fl p Ar parameter 10 | .Op Fl d 11 | .Op Fl H 12 | .Op Fl M 13 | .Op Fl A | Fl D 14 | .Op Ar msgs\ ... 15 | .Sh DESCRIPTION 16 | .Nm 17 | prints the headers of the specified 18 | .Ar msgs . 19 | .Po 20 | See 21 | .Xr mmsg 7 22 | for the message argument syntax. 23 | .Pc 24 | .Pp 25 | If no 26 | .Ar msgs 27 | are specified, 28 | .Nm 29 | will default to the current message. 30 | .Pp 31 | If no 32 | .Ar hdrs 33 | are specified, 34 | .Nm 35 | will print all headers. 36 | .Pp 37 | The options are as follows: 38 | .Bl -tag -width Ds 39 | .It Fl h Ar hdrs 40 | Only print the values of headers in the colon-separated list 41 | .Ar hdrs . 42 | .It Fl p Ar parameter 43 | Extract a particular RFC 2045 44 | .Ar parameter 45 | from the specified 46 | .Ar hdrs . 47 | .It Fl d 48 | Decode the 49 | .Ar hdrs 50 | according to RFC 2047. 51 | .It Fl H 52 | Prefix output lines with the filename of the message, 53 | followed by a tab. 54 | .It Fl M 55 | Search for all occurrences of the 56 | .Ar hdrs 57 | .Pq default: only the first . 58 | .It Fl A 59 | Scan for RFC 5322 addresses in the 60 | .Ar hdrs 61 | and print them, one per line. 62 | .It Fl D 63 | Assume header contains RFC 5322 date and print as Unix timestamp. 64 | .El 65 | .Sh EXIT STATUS 66 | The 67 | .Nm 68 | utility exits 0 on success, 69 | 1 if no header was printed, 70 | and >1 if an error occurs. 71 | .Sh SEE ALSO 72 | .Xr mmsg 7 73 | .Rs 74 | .%A N. Freed 75 | .%A N. Borenstein 76 | .%R RFC 2045 77 | .%D November 1996 78 | .%T Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies 79 | .Re 80 | .Rs 81 | .%A N. Freed 82 | .%A N. Borenstein 83 | .%D November 1996 84 | .%R RFC 2047 85 | .%T MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text 86 | .Re 87 | .Rs 88 | .%A P. Resnick (ed.) 89 | .%D October 2008 90 | .%R RFC 5322 91 | .%T Internet Message Format 92 | .Re 93 | .Sh AUTHORS 94 | .An Leah Neukirchen Aq Mt leah@vuxu.org 95 | .Sh LICENSE 96 | .Nm 97 | is in the public domain. 98 | .Pp 99 | To the extent possible under law, 100 | the creator of this work 101 | has waived all copyright and related or 102 | neighboring rights to this work. 103 | .Pp 104 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 105 | -------------------------------------------------------------------------------- /man/minc.1: -------------------------------------------------------------------------------- 1 | .Dd July 22, 2016 2 | .Dt MINC 1 3 | .Os 4 | .Sh NAME 5 | .Nm minc 6 | .Nd incorporate new messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl q 10 | .Ar dirs\ ... 11 | .Sh DESCRIPTION 12 | .Nm 13 | incorporates new messages in the maildir folders 14 | .Ar dirs 15 | by moving them from 16 | .Pa new 17 | to 18 | .Pa cur , 19 | and adjusting the filenames. 20 | If used non-interactively with no specified folders, 21 | .Nm 22 | reads directory names from the standard input. 23 | .Pp 24 | By default, the new filenames are printed, 25 | separated by newlines. 26 | .Pp 27 | The options are as follows: 28 | .Bl -tag -width Ds 29 | .It Fl q 30 | Quiet: don't print new filenames. 31 | .El 32 | .Sh EXIT STATUS 33 | .Ex -std 34 | .Sh SEE ALSO 35 | .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" 36 | .Sh AUTHORS 37 | .An Leah Neukirchen Aq Mt leah@vuxu.org 38 | .Sh LICENSE 39 | .Nm 40 | is in the public domain. 41 | .Pp 42 | To the extent possible under law, 43 | the creator of this work 44 | has waived all copyright and related or 45 | neighboring rights to this work. 46 | .Pp 47 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 48 | -------------------------------------------------------------------------------- /man/mless.1: -------------------------------------------------------------------------------- 1 | .Dd July 22, 2016 2 | .Dt MLESS 1 3 | .Os 4 | .Sh NAME 5 | .Nm mless 6 | .Nd conveniently read messages in less(1) 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Ar msg 10 | .Sh DESCRIPTION 11 | .Nm 12 | runs 13 | .Xr less 1 14 | on the output of 15 | .Xr mshow 1 16 | for the current sequence. 17 | Display starts with the current message, or 18 | .Ar msg 19 | if passed. 20 | .Po 21 | See 22 | .Xr mmsg 7 23 | for the message argument syntax. 24 | .Pc 25 | .Pp 26 | When no messages are passed and the standard input is from a pipe, 27 | .Ql mseq -S 28 | is called to set the current sequence; 29 | .Nm 30 | is then run on the resulting sequence. 31 | The first "file" of the less instance will be the output of 32 | .Xr mscan 1 . 33 | This makes 34 | .Nm 35 | convenient for use at the end of a pipeline. 36 | .Pp 37 | .Nm 38 | will start 39 | .Xr less 1 40 | and display the current message, if possible. 41 | Use 42 | .Sq Ic ":n" 43 | and 44 | .Sq Ic ":p" 45 | to read the next (resp. previous) message. 46 | .Sh FILES 47 | .Bl -tag -width Ds 48 | .It Pa ${MBLAZE:-$HOME/.mblaze}/mlesskey , Pa $HOME/.mlesskey 49 | Additional keybindings loaded for convenience. 50 | .It Pa ${MBLAZE:-$HOME/.mblaze}/mless , Pa $HOME/.mless 51 | Additional compiled keybindings loaded for convenience. 52 | (Only needed for less earlier than version 590.) 53 | .El 54 | .Sh EXIT STATUS 55 | .Ex -std 56 | .Sh SEE ALSO 57 | .Xr less 1 , 58 | .Xr mscan 1 , 59 | .Xr mseq 1 , 60 | .Xr mshow 1 , 61 | .Xr mmsg 7 62 | .Sh AUTHORS 63 | .An Leah Neukirchen Aq Mt leah@vuxu.org 64 | .Sh LICENSE 65 | .Nm 66 | is in the public domain. 67 | .Pp 68 | To the extent possible under law, 69 | the creator of this work 70 | has waived all copyright and related or 71 | neighboring rights to this work. 72 | .Pp 73 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 74 | -------------------------------------------------------------------------------- /man/mlist.1: -------------------------------------------------------------------------------- 1 | .Dd April 30, 2020 2 | .Dt MLIST 1 3 | .Os 4 | .Sh NAME 5 | .Nm mlist 6 | .Nd list and filter messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl DFPRST 10 | .Op Fl X Ar str 11 | .br 12 | .Op Fl dfprst 13 | .Op Fl x Ar str 14 | .br 15 | .Op Fl N | Fl n | Fl C | Fl c 16 | .br 17 | .Op Fl i 18 | .Op Ar dirs\ ... 19 | .Sh DESCRIPTION 20 | .Nm 21 | lists messages in the specified maildir folders 22 | .Ar dirs , 23 | separated by newlines. 24 | If used non-interactively with no specified folders, 25 | .Nm 26 | reads directory names from the standard input. 27 | .Pp 28 | The options are as follows: 29 | .Bl -tag -width Ds 30 | .It Fl D 31 | Only list messages marked as draft. 32 | .It Fl F 33 | Only list messages marked as flagged. 34 | .It Fl P 35 | Only list messages marked as passed 36 | .Pq resent/forwarded/bounced . 37 | .It Fl R 38 | Only list messages marked as replied-to. 39 | .It Fl S 40 | Only list messages marked as seen. 41 | .It Fl T 42 | Only list messages marked as trashed. 43 | .It Fl X Ar str 44 | Only list messages marked with the characters in 45 | .Ar str . 46 | .It Fl d 47 | Don't list messages marked as draft. 48 | .It Fl f 49 | Don't list messages marked as flagged. 50 | .It Fl p 51 | Don't list messages marked as passed 52 | .Pq resent/forwarded/bounced . 53 | .It Fl r 54 | Don't list messages marked as replied-to. 55 | .It Fl s 56 | Don't list messages marked as seen. 57 | .It Fl t 58 | Don't list messages marked as trashed. 59 | .It Fl x Ar str 60 | Don't list messages marked with the characters in 61 | .Ar str . 62 | .Pq Remember to use uppercase characters. 63 | .It Fl C 64 | Only list messages in 65 | .Pa cur . 66 | .It Fl N 67 | Only list messages in 68 | .Pa new . 69 | .It Fl c 70 | Don't list messages in 71 | .Pa cur . 72 | .It Fl n 73 | Don't list messages in 74 | .Pa new . 75 | .It Fl i 76 | Don't print filenames. 77 | Instead, print a one-line summary for each folder, 78 | showing the number of unseen, flagged and total messages, 79 | along with the folder name. 80 | If any of the message selection flags are used, 81 | the number of matching messages is printed as well. 82 | If two or more folders are specified, 83 | a total for all folders will also be printed at the end. 84 | .El 85 | .Pp 86 | Multiple options are regarded as a conjunction. 87 | .Sh EXIT STATUS 88 | .Ex -std 89 | .Sh SEE ALSO 90 | .Xr find 1 91 | .Pp 92 | .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" 93 | .Sh AUTHORS 94 | .An Leah Neukirchen Aq Mt leah@vuxu.org 95 | .Sh LICENSE 96 | .Nm 97 | is in the public domain. 98 | .Pp 99 | To the extent possible under law, 100 | the creator of this work 101 | has waived all copyright and related or 102 | neighboring rights to this work. 103 | .Pp 104 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 105 | -------------------------------------------------------------------------------- /man/mmime.1: -------------------------------------------------------------------------------- 1 | .Dd July 22, 2016 2 | .Dt MMIME 1 3 | .Os 4 | .Sh NAME 5 | .Nm mmime 6 | .Nd create MIME messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl c | Fl r 10 | .Op Fl t Ar content-type 11 | < 12 | .Ar message 13 | .Sh DESCRIPTION 14 | .Nm 15 | generates a 16 | .Sq Li multipart/mixed 17 | message from the standard input, 18 | extending, wrapping, and encoding the header as necessary, 19 | and replacing lines in the message body of the form 20 | .Pp 21 | .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 22 | .Pp 23 | with a MIME part having Content-Type 24 | .Ar content/type , 25 | consisting of the contents of the file found at 26 | .Pa path . 27 | .Ar content-disposition 28 | is optional and defaults to 29 | .Sq attachment . 30 | .Ar filename 31 | is optional and defaults to the basename of 32 | .Ar path . 33 | .Pp 34 | The options are as follows: 35 | .Bl -tag -width Ds 36 | .It Fl c 37 | Check mode: don't output anything, 38 | exit with status 1 if MIME-encoding the message is required, 39 | or else exit with status 0. 40 | .It Fl r 41 | Raw mode: don't expand MIME parts in the body, generate a 42 | .Sq Li text/plain 43 | message. 44 | .It Fl t Ar content-type 45 | Override Content-Type for the toplevel part. 46 | Defaults to 47 | .Sq Li multipart/mixed . 48 | .El 49 | .Sh ENVIRONMENT 50 | .Bl -tag -width Ds 51 | .It Ev MBLAZE_RELAXED_MIME 52 | If set, 53 | .Nm Fl c 54 | will relax the body line length check and only consider bodies with lines 55 | longer than 998 characters need to be MIME formatted. 56 | .El 57 | .Sh EXIT STATUS 58 | .Ex -std 59 | .Sh SEE ALSO 60 | .Xr makemime 1 , 61 | .Xr mhbuild 1 62 | .Rs 63 | .%A N. Freed 64 | .%A N. Borenstein 65 | .%D November 1996 66 | .%R RFC 2045 67 | .%T Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies 68 | .Re 69 | .Rs 70 | .%A N. Freed 71 | .%A N. Borenstein 72 | .%D November 1996 73 | .%R RFC 2046 74 | .%T Multipurpose Internet Mail Extensions (MIME) Part Two: Media Types 75 | .Re 76 | .Rs 77 | .%A K. Moore 78 | .%D November 1996 79 | .%R RFC 2047 80 | .%T MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text 81 | .Re 82 | .Rs 83 | .%A N. Freed 84 | .%A K. Moore 85 | .%D November 1997 86 | .%R RFC 2231 87 | .%T MIME Parameter Value and Encoded Word Extensions: Character Sets, Languages, and Continuations 88 | .Re 89 | .Rs 90 | .%A P. Resnick (ed.) 91 | .%D October 2008 92 | .%R RFC 5322 93 | .%T Internet Message Format 94 | .Re 95 | .Sh AUTHORS 96 | .An Leah Neukirchen Aq Mt leah@vuxu.org 97 | .Sh LICENSE 98 | .Nm 99 | is in the public domain. 100 | .Pp 101 | To the extent possible under law, 102 | the creator of this work 103 | has waived all copyright and related or 104 | neighboring rights to this work. 105 | .Pp 106 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 107 | -------------------------------------------------------------------------------- /man/mmkdir.1: -------------------------------------------------------------------------------- 1 | .Dd June 20, 2017 2 | .Dt MMKDIR 1 3 | .Os 4 | .Sh NAME 5 | .Nm mmkdir 6 | .Nd create new maildir folders 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Ar dir\ ... 10 | .Sh DESCRIPTION 11 | .Nm 12 | creates new maildir folders. 13 | .Pp 14 | Parent directories are created as needed, 15 | and attempting to create an existing 16 | maildir is not an error. 17 | .Pp 18 | New maildir folders are created with mode 0700, i.e. 19 | readable and writable by the owner only. 20 | .Sh EXIT STATUS 21 | .Ex -std 22 | .Sh SEE ALSO 23 | .Xr mkdir 1 24 | .Pp 25 | .Lk https://cr.yp.to/proto/maildir.html "Using maildir format" 26 | .Sh AUTHORS 27 | .An Leah Neukirchen Aq Mt leah@vuxu.org 28 | .Sh CAVEATS 29 | .Nm 30 | does not support maildir++, 31 | you need to transform nested maildir++ folder names yourself. 32 | .Sh LICENSE 33 | .Nm 34 | is in the public domain. 35 | .Pp 36 | To the extent possible under law, 37 | the creator of this work 38 | has waived all copyright and related or 39 | neighboring rights to this work. 40 | .Pp 41 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 42 | -------------------------------------------------------------------------------- /man/mmsg.7: -------------------------------------------------------------------------------- 1 | .Dd July 3, 2020 2 | .Dt MMSG 7 3 | .Os 4 | .Sh NAME 5 | .Nm mmsg 6 | .Nd mblaze message argument syntax 7 | .Sh DESCRIPTION 8 | This document outlines the message syntax used by many 9 | of the utilities in the 10 | .Xr mblaze 7 11 | message system. 12 | .Pp 13 | In general, you can always specify a filename as a message, 14 | if it contains a 15 | .Sq Li \&/ 16 | character. 17 | .Po 18 | Use 19 | .Sq Li \&./ 20 | to prefix messages in the current directory. 21 | .Pc 22 | You can also specify a maildir folder, which will be expanded 23 | to all messages in the 24 | .Pa cur/ 25 | directory. 26 | .Pp 27 | Ranges have the format 28 | .Sq Ar start Ns Cm \&: Ns Ar stop , 29 | where 30 | .Ar start 31 | and 32 | .Ar stop 33 | are one-based indexes into the sequence. 34 | Negative numbers count from the end. 35 | If 36 | .Ar start 37 | is the empty string, 38 | .Li 1 39 | will be used instead. 40 | If 41 | .Ar stop 42 | is the empty string, 43 | .Li \&-1 44 | will be used instead. 45 | Thus, 46 | .Sq Cm \&: 47 | represents the whole sequence. 48 | If the range does not contain a 49 | .Sq Cm \&: , 50 | it is considered to be a single message, equivalent to the range 51 | .Sq Ar start Ns Cm \&: Ns Ar start 52 | of size one. 53 | The special notation 54 | .Sq Ar start Ns Cm \&:+ Ns Ar n , 55 | selects 56 | .Ar start 57 | and the next 58 | .Ar n 59 | messages. 60 | .Pp 61 | If the sequence is threaded, the following 62 | syntax may be used: 63 | .Sq Ar msg Ns Cm \&= 64 | refers to the whole thread that contains 65 | .Ar msg . 66 | .Sq Ar msg Ns Cm \&^ 67 | refers to the parent of the message 68 | .Ar msg 69 | and may be repeated to refer to grandparents. 70 | .Sq Ar msg Ns Cm \&_ 71 | refers to the subthread headed by 72 | .Ar msg 73 | .Po 74 | i.e. all messages below 75 | .Ar msg , 76 | with more indentation 77 | .Pc . 78 | .Pp 79 | The following special shortcuts may be used: 80 | .Bl -tag -width 4n 81 | .It Sq Li \&- 82 | refers to the message read directly from the standard input. 83 | .It Sq Li \&. 84 | refers to the current message. 85 | Additionally, the syntax 86 | .Sq Li \&.+ Ns Ar N 87 | and 88 | .Sq Li \&.- Ns Ar N 89 | can be used to refer to messages relative to the current message. 90 | .It Sq Li \&+ 91 | refers to the next message 92 | .Po 93 | like 94 | .Sq Li \&.+1 95 | .Pc 96 | .It Sq Li \&.- 97 | refers to the previous message 98 | .Po 99 | like 100 | .Sq Li \&.-1 101 | .Pc 102 | .It Sq Li \&$ 103 | refers to the last message 104 | .Po 105 | like 106 | .Sq Li -1 107 | .Pc 108 | .It Sq Li \&^ 109 | refers to the current parent message 110 | .Po 111 | like 112 | .Sq Li \&.^ 113 | .Pc 114 | .It Sq Li \&= 115 | refers to the current thread 116 | .Po 117 | like 118 | .Sq Li \&.= 119 | .Pc 120 | .It Sq Li \&_ 121 | refers to the current subthread 122 | .Po 123 | like 124 | .Sq Li \&._ 125 | .Pc 126 | .El 127 | .Sh SEE ALSO 128 | .Xr mblaze 7 129 | .Sh AUTHORS 130 | .An Leah Neukirchen Aq Mt leah@vuxu.org 131 | -------------------------------------------------------------------------------- /man/mpick.1: -------------------------------------------------------------------------------- 1 | .Dd July 30, 2020 2 | .Dt MPICK 1 3 | .Os 4 | .Sh NAME 5 | .Nm mpick 6 | .Nd advanced message filter 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl F Ar file 10 | .Op Fl T 11 | .Op Fl v 12 | .Op Fl t Ar test 13 | .Op Ar msgs\ ... 14 | .Sh DESCRIPTION 15 | .Nm 16 | prints all matching messages. 17 | .Pp 18 | If used interactively, 19 | .Nm 20 | will default to the current sequence. 21 | Otherwise, 22 | .Nm 23 | will read filenames from the standard input. 24 | .Pp 25 | The options are as follows: 26 | .Bl -tag -width Ds 27 | .It Fl F Ar file 28 | Read expression from 29 | .Ar file 30 | and only show matching messages, see 31 | .Sx TESTS . 32 | .It Fl T 33 | Include whole thread. 34 | .It Fl t Ar test 35 | Only show messages matching the expression 36 | .Ar test , 37 | see 38 | .Sx TESTS . 39 | .It Fl v 40 | Print how many messages were tested and picked to standard error. 41 | .El 42 | .Sh TESTS 43 | .Nm 44 | tests are given by the following EBNF: 45 | .Bd -literal 46 | ::= ? : -- ternary operator 47 | | || -- disjunction 48 | | && -- conjunction 49 | | ! -- negation 50 | | ( ) 51 | | "|" -- pipe current mail to command 52 | | ">>" -- append current mail to file 53 | | ">" -- write current mail to file 54 | | 55 | | 56 | | 57 | | 58 | | 59 | | prune -- do not match further messages in thread 60 | | print -- always true value 61 | | skip -- always false value 62 | | 63 | | 64 | 65 | ::= child | draft | flagged | info | new | parent | passed 66 | | replied | seen | selected | trashed 67 | 68 | ::= atime | ctime | mtime | date 69 | 70 | ::= depth | kept | replies | index | size | total 71 | 72 | ::= <= | < | >= | > | == | = | != 73 | 74 | ::= "./path" -- mtime of relative path 75 | | "/path" -- mtime of absolute path 76 | | "YYYY-MM-DD HH:MM:SS" 77 | | "YYYY-MM-DD" -- at midnight 78 | | "HH:MM:SS" -- today 79 | | "HH:MM" -- today 80 | | "-[0-9]+d" -- n days ago at midnight 81 | | "-[0-9]+h" -- n hours before now 82 | | "-[0-9]+m" -- n minutes before now 83 | | "-[0-9]+s" -- n seconds before now 84 | | [0-9]+ -- absolute epoch time 85 | 86 | ::= [0-9]+ ( c -- *1 87 | | b -- *512 88 | | k -- *1024 89 | | M -- *1024*1024 90 | | G -- *1024*1024*1024 91 | | T )? -- *1024*1024*1024*1024 92 | | cur -- index of cur message 93 | 94 | ::= from | to | subject | 95 | 96 | ::= . addr -- match address parts 97 | | . disp -- match address display parts 98 | | -- empty matches raw headers 99 | 100 | ::= path 101 | 102 | ::= == | = | != -- string (in)equality 103 | | === | !=== -- case insensitive string (in)equality 104 | | ~~ | !~~ -- glob (fnmatch) 105 | | ~~~ | !~~~ -- case insensitive glob (fnmatch) 106 | | =~ | !=~ | !~ -- POSIX Extended Regular Expressions 107 | | =~~ | !=~~ -- case insensitive POSIX Extended Regular Expressions 108 | 109 | ::= " ([^"] | "")+ " -- use "" for a single " inside " 110 | | $[A-Za-z0-9_]+ -- environment variable 111 | 112 | -- let expressions evaluate the expression following the `in` keyword, 113 | -- the bindings are lazily evaluated. 114 | ::= { let = } in 115 | 116 | -- Inside the scope previously defined idents are replaced with expressions 117 | ::= 118 | 119 | ::= [A-Za-z_][A-Za-z0-9_]+ 120 | .Ed 121 | .Sh EXIT STATUS 122 | .Ex -std 123 | .Sh EXAMPLES 124 | You can pick mails to move them into another 125 | .Dv maildir . 126 | .Pp 127 | .Dl mv $(mlist ./INBOX | mpick -t 'from =~ \&"@github\&"') ./github/cur 128 | .Pp 129 | Or you can use 130 | .Nm 131 | to pick mails from the current sequence. 132 | .Pp 133 | .Dl mpick -t 'subject =~~ \&"mblaze\&"' | mscan 134 | .Pp 135 | A more advanced 136 | .Nm 137 | expression to pick mails in a certain time span, 138 | which are flagged as replied or not seen. 139 | .Bd -literal -offset indent 140 | mpick -t 'date >= \&"2016-01-01\&" && date < \&"2017-01-01\&" && (replied || !seen)' 141 | .Ed 142 | .Pp 143 | And to find other mblaze users. 144 | .Pp 145 | .Dl mpick -t '"User-Agent" =~~ \&"mblaze\&"' | mscan 146 | .Sh SEE ALSO 147 | .Xr lr 1 , 148 | .Xr mailx 1 149 | .Sh AUTHORS 150 | .An Leah Neukirchen Aq Mt leah@vuxu.org 151 | .An Duncan Overbruck Aq Mt mail@duncano.de 152 | .Sh LICENSE 153 | .Nm 154 | is in the public domain. 155 | .Pp 156 | To the extent possible under law, 157 | the creator of this work 158 | has waived all copyright and related or 159 | neighboring rights to this work. 160 | .Pp 161 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 162 | -------------------------------------------------------------------------------- /man/mrefile.1: -------------------------------------------------------------------------------- 1 | .Dd April 19, 2018 2 | .Dt MREFILE 1 3 | .Os 4 | .Sh NAME 5 | .Nm mrefile 6 | .Nd move or copy messages between maildir folders 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl k 10 | .Op Fl v 11 | .Op Ar msgs\ ... 12 | .Ar dir 13 | .Sh DESCRIPTION 14 | .Nm 15 | moves the 16 | .Ar msgs 17 | given on the command line 18 | .Pq or the standard input 19 | into the maildir 20 | .Ar dir , 21 | assigning new filenames but keeping any message flags. 22 | Messages are moved directly into the 23 | .Pa cur/ 24 | directory. 25 | .Po 26 | See 27 | .Xr mmsg 7 28 | for the message argument syntax. 29 | .Pc 30 | .Pp 31 | The options are as follows: 32 | .Bl -tag -width Ds 33 | .It Fl k 34 | keep the messages in their respective folders, 35 | i.e. copy into 36 | .Ar dir . 37 | .It Fl v 38 | Print each resulting message filename. 39 | .El 40 | .Sh EXIT STATUS 41 | .Ex -std 42 | .Sh SEE ALSO 43 | .Xr mdeliver 1 44 | .Sh AUTHORS 45 | .An Leah Neukirchen Aq Mt leah@vuxu.org 46 | .Sh LICENSE 47 | .Nm 48 | is in the public domain. 49 | .Pp 50 | To the extent possible under law, 51 | the creator of this work 52 | has waived all copyright and related or 53 | neighboring rights to this work. 54 | .Pp 55 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 56 | -------------------------------------------------------------------------------- /man/mrep.1: -------------------------------------------------------------------------------- 1 | .so man1/mcom.1 2 | -------------------------------------------------------------------------------- /man/mscan.1: -------------------------------------------------------------------------------- 1 | .Dd January 2, 2020 2 | .Dt MSCAN 1 3 | .Os 4 | .Sh NAME 5 | .Nm mscan 6 | .Nd generate one-line message summaries 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl I 10 | .Op Fl n 11 | .Op Fl v 12 | .Op Fl f Ar format 13 | .Ar msgs\ ... 14 | .Sh DESCRIPTION 15 | .Nm 16 | prints a one line summary for each message. 17 | .Po 18 | See 19 | .Xr mmsg 7 20 | for the message argument syntax. 21 | .Pc 22 | .Pp 23 | If no 24 | .Ar msgs 25 | are specified, 26 | .Nm 27 | reads filenames from the standard input, 28 | or scans the mails in the current sequence if used interactively. 29 | .Pp 30 | By default, 31 | .Nm 32 | will spawn a pager on its output when used interactively. 33 | See 34 | .Sx ENVIRONMENT 35 | for details on how to control this behaviour. 36 | .Pp 37 | The default 38 | .Ar format 39 | is 40 | .Pp 41 | .Dl %c%u%r %-3n %10d %17f %t %2i%s 42 | .Pp 43 | that is, for each message, 44 | .Nm 45 | prints relevant flags, 46 | the sequence number 47 | .Pq if applicable , 48 | the date, 49 | the originator, 50 | and the subject of the message 51 | .Pq possibly indented . 52 | A default 53 | .Nm 54 | format may be specified in the user's 55 | .Xr mblaze-profile 5 . 56 | .Pp 57 | The options are as follows: 58 | .Bl -tag -width Ds 59 | .It Fl I 60 | Force ISO date output, 61 | even for 62 | .Sq Cm "%d" . 63 | .It Fl n 64 | Only print message numbers 65 | .Pq or filenames, if the message is not in the current sequence . 66 | .It Fl v 67 | Print how many messages were scanned to standard error. 68 | .It Fl f Ar format 69 | Format according to the string 70 | .Ar format , 71 | inspired by 72 | .Xr printf 3 . 73 | .Pp 74 | The following formatting codes may be used 75 | .Po 76 | .Ar wd 77 | overrides the default width 78 | .Pc : 79 | .Bl -tag -width Ds 80 | .It Cm \en 81 | Newline. 82 | .It Cm \et 83 | Tab. 84 | .It Cm \&%% 85 | A plain 86 | .Sq Li \&% . 87 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "b" 88 | Human-readable size of the message 89 | .Pq in kilobytes . 90 | .It Cm "%c" 91 | A 92 | .Sq Li > 93 | on the current message, 94 | otherwise a blank. 95 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "d" 96 | Adaptive date of the message. 97 | When 98 | .Ar wd 99 | is greater or equal to 19, 100 | the timestamp is shown in dottime format 101 | .Pq Lk https://dotti.me/ . 102 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "D" 103 | ISO date of the message 104 | .Pq year, month, day . 105 | When 106 | .Ar wd 107 | is greater or equal to 16, 108 | the hour-minute timestamp will also be shown. 109 | When 110 | .Ar wd 111 | is greater or equal to 19, 112 | seconds will also be shown. 113 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "f" 114 | The 115 | .Sq Li From\&: 116 | .Po 117 | or 118 | .Sq Li To\&: , 119 | if the message is from us 120 | .Pc . 121 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "F" 122 | The maildir folder the message resides in. 123 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "i" 124 | .Ar wd 125 | .Pq default: 1 126 | spaces per indentation depth in the thread tree. 127 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "I" 128 | The 129 | .Sq Li Message\&-ID\&: 130 | of the message. 131 | .It Cm "%M" 132 | The raw maildir flags of the message. 133 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "n" 134 | The number of the message in the current sequence. 135 | .It Cm "%r" 136 | A 137 | .Sq Li \&- 138 | on a replied-to message, or a 139 | .Sq Li \&: 140 | on a forwarded message, or a blank. 141 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "R" 142 | The filename of the message. 143 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "s" 144 | The subject of the message 145 | .Pq defaults to remaining width . 146 | .It Cm "%" Ns Oo Ar wd Oc Ns Cm "S" 147 | The subject of the message 148 | .Pq defaults to remaining width , 149 | with leading 150 | .Sq Li Re\&: , 151 | .Sq Li Fwd\&: 152 | etc. stripped. 153 | .It Cm "%t" 154 | A 155 | .Sq Li \&> 156 | if you are in 157 | .Sq Li To\&: , 158 | a 159 | .Sq Li \&+ 160 | if you are in 161 | .Sq Li Cc\&: , 162 | a 163 | .Sq Li \&: 164 | if you are in 165 | .Sq Li Resent\&-To\&: , 166 | or a blank. 167 | .It Cm "%u" 168 | An 169 | .Sq Li \&* 170 | on a flagged message, a 171 | .Sq Li \&. 172 | on an unseen message, an 173 | .Sq Li x 174 | on a trashed message, or a blank. 175 | .El 176 | .El 177 | .Sh MESSAGE FLAGS 178 | .Bl -tag -width 2n -compact 179 | .It Li \&> 180 | The current message 181 | .It Li \&. 182 | An unseen message 183 | .It Li x 184 | A trashed message 185 | .It Li \&* 186 | A flagged message 187 | .It Li \&- 188 | A replied-to message 189 | .El 190 | .Sh SUBJECT FLAGS 191 | .Bl -tag -width 2n -compact 192 | .It Li \&> 193 | You are in 194 | .Sq Li To\&: 195 | .It Li \&+ 196 | You are in 197 | .Sq Li Cc\&: 198 | .It Li \&: 199 | You are in 200 | .Sq Li Resent\&-To\&: 201 | .El 202 | .Sh ENVIRONMENT 203 | .Bl -tag -width MBLAZE_PAGER 204 | .It Ev MBLAZE_PAGER 205 | Any non-empty value of the environment variable 206 | .Ev MBLAZE_PAGER 207 | is used instead of the standard pagination program, specified in 208 | .Ev PAGER . 209 | When empty, or set to 210 | .Sq Ic cat , 211 | no pager is spawned. 212 | .El 213 | .Sh EXIT STATUS 214 | .Ex -std 215 | .Sh SEE ALSO 216 | .Xr mblaze-profile 5 , 217 | .Xr mmsg 7 218 | .Sh AUTHORS 219 | .An Leah Neukirchen Aq Mt leah@vuxu.org 220 | .Sh LICENSE 221 | .Nm 222 | is in the public domain. 223 | .Pp 224 | To the extent possible under law, 225 | the creator of this work 226 | has waived all copyright and related or 227 | neighboring rights to this work. 228 | .Pp 229 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 230 | -------------------------------------------------------------------------------- /man/msed.1: -------------------------------------------------------------------------------- 1 | .Dd August 1, 2016 2 | .Dt MSED 1 3 | .Os 4 | .Sh NAME 5 | .Nm msed 6 | .Nd manipulate message headers 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Ar script 10 | .Op Ar msgs\ ... 11 | .Sh DESCRIPTION 12 | .Nm 13 | prints the messages 14 | .Ar msgs 15 | with message headers transformed by the commands in 16 | .Ar script . 17 | .Po 18 | See 19 | .Xr mmsg 7 20 | for the message argument syntax. 21 | .Pc 22 | If no 23 | .Ar msgs 24 | are passed, 25 | .Nm 26 | will default to the current message. 27 | .Pp 28 | .Nm 29 | scripts are akin to a subset of 30 | .Xr sed 1 31 | scripts, but optimized for modifying messages. 32 | Note that 33 | .Nm 34 | unfolds and normalizes message headers, 35 | so they may need to be passed through 36 | .Xr mmime 7 37 | to ensure RFC 5322 conformance. 38 | The message body is not affected. 39 | .Pp 40 | .Nm 41 | supports the following commands. 42 | The separators 43 | .Em after 44 | the command letter may be substituted with an arbitrary symbol, just as in 45 | .Xr sed 1 . 46 | Multiple commands can be separated by 47 | .Sq Cm \&; . 48 | .Bl -tag -width Ds 49 | .It Cm \&/ Ns Ar header Ns Cm \&/ Ns Ic a Ns Cm \&/ Ns Ar value Ns Cm \&/ 50 | If the header 51 | .Sq Ar header Ns Cm \&: 52 | is not set in the message, add it with the given 53 | .Ar value . 54 | .It Cm \&/ Ns Ar headers Ns Cm \&/ Ns Ic c Ns Cm \&/ Ns Ar value Ns Cm \&/ 55 | Change colon-separated headers matching the regular expression 56 | .Ar headers , 57 | with implicit anchoring to the header name, 58 | to the value given in 59 | .Ar value . 60 | .It Cm \&/ Ns Ar headers Ns Cm \&/ Ns Ic d 61 | Delete colon-separated headers matching the regular expression 62 | .Ar headers , 63 | with implicit anchoring to the header name. 64 | Use explicit 65 | .Sq Li \&.* 66 | to match arbitrary strings at the beginning or end of the headers. 67 | .Pp 68 | For example, 69 | .Sq Li "/x-.*/d" 70 | will delete all headers starting with 71 | .Sq Li "X-" 72 | .Pq always case insensitive , 73 | and 74 | .Sq Li "/from:to:cc/d" 75 | will delete the headers 76 | .Sq Li From\&: , 77 | .Sq Li To\&: , 78 | and 79 | .Sq Li Cc\&: . 80 | .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 81 | Substitute matches of the POSIX Basic Regular Expression 82 | .Ar regex 83 | in headers matching the POSIX Basic Regular Expression 84 | .Ar headers , 85 | with implicit anchoring to the header name 86 | .Pq or all headers, if omitted , 87 | with the string 88 | .Ar replacement , 89 | expanding 90 | .Sq Cm \&& 91 | to the matched string, 92 | and 93 | .Sq Cm \e Ns Ar N 94 | to the 95 | .Ar N Ns 96 | th 97 | sub-expression, 98 | where 99 | .Ar N 100 | is between 1 and 9. 101 | .Pp 102 | If 103 | .Ar flags 104 | contains the letter 105 | .Sq Cm d , 106 | the header is removed if 107 | .Ar regex 108 | matched. 109 | .Pp 110 | By default, only the first match is replaced, unless 111 | .Ar flags 112 | contains the letter 113 | .Sq Cm g . 114 | .Pp 115 | By default, 116 | .Ar regex 117 | is matched case sensitively, unless 118 | .Ar flags 119 | contains the letter 120 | .Sq Cm i . 121 | .El 122 | .Sh EXIT STATUS 123 | .Ex -std 124 | .Sh SEE ALSO 125 | .Xr sed 1 , 126 | .Xr mhdr 1 , 127 | .Xr mmsg 7 , 128 | .Xr regex 7 / 129 | .Xr re_format 7 130 | .Sh AUTHORS 131 | .An Leah Neukirchen Aq Mt leah@vuxu.org 132 | .Sh LICENSE 133 | .Nm 134 | is in the public domain. 135 | .Pp 136 | To the extent possible under law, 137 | the creator of this work 138 | has waived all copyright and related or 139 | neighboring rights to this work. 140 | .Pp 141 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 142 | -------------------------------------------------------------------------------- /man/mseq.1: -------------------------------------------------------------------------------- 1 | .Dd July 22, 2016 2 | .Dt MSEQ 1 3 | .Os 4 | .Sh NAME 5 | .Nm mseq 6 | .Nd manipulate message sequences 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl fr 10 | .Op Fl c Ar msg 11 | .Ar msgs\ ... 12 | .Nm 13 | .Fl S 14 | .Op Fl fr 15 | < 16 | .Ar sequence 17 | .Nm 18 | .Fl A 19 | .Op Fl fr 20 | < 21 | .Ar sequence 22 | .Nm 23 | .Fl C Ar msg 24 | .Sh DESCRIPTION 25 | .Nm 26 | prints, fixes and sets the message sequence. 27 | .Po 28 | See 29 | .Xr mmsg 7 30 | for the message argument syntax. 31 | .Pc 32 | .Pp 33 | If no 34 | .Ar msgs 35 | are passed and 36 | .Nm 37 | is used interactively, 38 | .Nm 39 | prints all messages in the sequence. 40 | .Pp 41 | With 42 | .Fl S 43 | or 44 | .Fl A , 45 | .Nm 46 | will replace 47 | .Pq resp. append 48 | the default sequence. 49 | If the standard output is not a terminal, 50 | the new sequence is also printed. 51 | .Pp 52 | The options are as follows: 53 | .Bl -tag -width Ds 54 | .It Fl c Ar msg 55 | Behave as if 56 | .Ar msg 57 | was the current message. 58 | .It Fl f 59 | Fix non-existing filenames by searching for a message with the same 60 | maildir id 61 | .Pq but different flags . 62 | .It Fl r 63 | Remove leading indentation from the filenames. 64 | .It Fl S 65 | Set the message sequence to the filenames passed on standard input. 66 | .It Fl A 67 | Like 68 | .Fl S , 69 | but append to the message sequence instead of replacing it. 70 | .It Fl C Ar msg 71 | Set the current message to 72 | .Ar msg 73 | and exit. 74 | .El 75 | .Sh ENVIRONMENT 76 | .Bl -tag -width Ds 77 | .It Ev MBLAZE 78 | Directory containing mblaze configuration. 79 | .Po 80 | Default: 81 | .Pa $HOME/.mblaze 82 | .Pc 83 | .It Ev MAILCUR 84 | Symbolic link referring to the current message. 85 | .Po 86 | Default: 87 | .Pa ${MBLAZE:-$HOME/.mblaze}/cur 88 | .Pc 89 | .It Ev MAILDOT 90 | When set to a filename, overrides the current message. 91 | .Po 92 | Prefer using 93 | .Fl c 94 | instead. 95 | .Pc 96 | .It Ev MAILSEQ 97 | File were the sequence is stored. 98 | .Po 99 | Default: 100 | .Pa ${MBLAZE:-$HOME/.mblaze}/seq 101 | .Pc 102 | .El 103 | .Sh EXIT STATUS 104 | .Ex -std 105 | .Sh SEE ALSO 106 | .Xr mmsg 7 107 | .Sh AUTHORS 108 | .An Leah Neukirchen Aq Mt leah@vuxu.org 109 | .Sh LICENSE 110 | .Nm 111 | is in the public domain. 112 | .Pp 113 | To the extent possible under law, 114 | the creator of this work 115 | has waived all copyright and related or 116 | neighboring rights to this work. 117 | .Pp 118 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 119 | -------------------------------------------------------------------------------- /man/mshow.1: -------------------------------------------------------------------------------- 1 | .Dd January 17, 2021 2 | .Dt MSHOW 1 3 | .Os 4 | .Sh NAME 5 | .Nm mshow 6 | .Nd render messages and extract MIME parts 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl h Ar headers 10 | .Op Fl A Ar mimetypes 11 | .Op Fl nqrBFHLN 12 | .Op Ar msgs\ ... 13 | .Nm 14 | .Fl x Ar msg 15 | .Ar parts\ ... 16 | .Nm 17 | .Fl O Ar msg 18 | .Ar parts\ ... 19 | .Nm 20 | .Fl t 21 | .Ar msgs\ ... 22 | .Nm 23 | .Fl R 24 | .Ar msg 25 | .Sh DESCRIPTION 26 | .Nm 27 | renders the specified 28 | .Ar msgs 29 | to the standard output, by default. 30 | .Po 31 | See 32 | .Xr mmsg 7 33 | for the message argument syntax. 34 | .Pc 35 | If used interactively and no 36 | .Ar msgs 37 | are specified, 38 | .Nm 39 | displays the current message using colorization and a pager. 40 | .Pp 41 | The options are as follows: 42 | .Bl -tag -width Ds 43 | .It Fl h Ar headers 44 | Display the headers in the colon-separated list 45 | .Ar headers , 46 | instead of the default headers 47 | .Sq Li from\&:subject\&:to\&:cc\&:date\&:reply\&-to\&: . 48 | .It Fl A Ar mimetypes 49 | Define 50 | .Sq Li "multipart/alternative" 51 | preference. 52 | .Ar mimetypes 53 | is a colon-separated list of 54 | MIME types which will be preferred, 55 | in the order given, 56 | when rendering 57 | .Sq Li "multipart/alternative" 58 | parts. 59 | If no MIME type matches, the last MIME part will be rendered. 60 | .Pp 61 | When 62 | .Ar mimetypes 63 | is 64 | .Sq Li all , 65 | .Nm 66 | will render all parts of a 67 | .Sq Li "multipart/alternative" 68 | part. 69 | .Pp 70 | Defaults to 71 | .Sq Li "text/plain:text/html" . 72 | .It Fl n 73 | Don't update the current message link. 74 | .It Fl q 75 | Don't render the body, stop after header output. 76 | .It Fl r 77 | Don't render the body, print raw body. 78 | This may be dangerous to use on a tty. 79 | .It Fl B 80 | Decode encoded-words also in MIME parameters in direct violation 81 | of RFC 2047. 82 | This is useful if the attachment names look like 83 | .Sq Li =?UTF-8?Q?stuff?= . 84 | .It Fl F 85 | Don't apply filters to MIME parts. 86 | .It Fl H 87 | Don't decode the headers, print all raw headers. 88 | This may be dangerous to use on a tty. 89 | .It Fl L 90 | Don't filter the headers, print all decoded headers. 91 | .It Fl N 92 | Don't show MIME structure markers. 93 | .It Fl x Ar msg 94 | Switch to extraction mode: extract 95 | .Ar parts 96 | from the message 97 | .Ar msg 98 | into files. 99 | .Ar parts 100 | can be specified by number, filename or 101 | .Xr fnmatch 3 102 | pattern. 103 | If no 104 | .Ar parts 105 | are specified, extracts all attachments with a filename. 106 | .It Fl O Ar msg 107 | Like 108 | .Fl x 109 | but write to standard output. 110 | This may be dangerous to use on a tty. 111 | When used together with 112 | .Fl r , 113 | the whole part is raw, 114 | that is, 115 | un-decoded and including MIME part headers. 116 | .It Fl t 117 | Switch to list mode: list all MIME parts 118 | of each 119 | .Ar msg . 120 | .It Fl R Ar msg 121 | Render the text parts from 122 | .Ar msg , 123 | suitable for use in a reply. 124 | .El 125 | .Sh FILTERS 126 | .Nm , 127 | by default, decodes all 128 | .Sq Li text/* , 129 | .Sq Li message/rfc822 130 | and 131 | .Sq Li multipart/* 132 | parts, 133 | and re-encodes them into UTF-8 if necessary. 134 | .Pp 135 | Other filters can be specified in the file 136 | .Pa ${MBLAZE:-$HOME/.mblaze}/filter 137 | .Po 138 | or via 139 | .Ev MAILFILTER 140 | .Pc , 141 | in the format: 142 | .Pp 143 | .D1 Ar type/subtype Ns Li \&: Ar command 144 | or 145 | .D1 Ar type Ns Li \&: Ar command 146 | .Pp 147 | .Nm 148 | will then spawn a pipe to 149 | .Ar command , 150 | write the MIME part 151 | to standard input 152 | and display the output. 153 | The environment variable 154 | .Ev PIPE_CHARSET 155 | will be set to the charset declared in the MIME part, 156 | if known. 157 | .Pp 158 | Filters can communicate with 159 | .Nm 160 | using their exit status: 161 | .Bl -tag -compact -width 8n 162 | .It 0 163 | The output is printed as plain text. 164 | .It 62 165 | The output is printed raw, without escaping. 166 | .It 63 167 | Behave as if the filter never ran. 168 | .It 64 169 | The output is an RFC 5322 message that should be rendered again. 170 | .It 65 to 80 171 | Render the 172 | .Va n Ns \&- Ns 64th 173 | part of this text/multipart part. 174 | .El 175 | All other exit statuses are regarded as errors. 176 | .Sh ENVIRONMENT 177 | .Bl -tag -width MBLAZE_NOCOLOR 178 | .It Ev MAILFILTER 179 | Path to an alternative 180 | .Pa filter 181 | file. 182 | .It Ev MBLAZE_NOCOLOR 183 | If non-empty, 184 | .Nm 185 | will not spawn a colorization filter. 186 | .It Ev MBLAZE_PAGER 187 | Any non-empty value of the environment variable 188 | .Ev MBLAZE_PAGER 189 | is used instead of the standard pagination program, specified in 190 | .Ev PAGER . 191 | When empty or set to 192 | .Sq Ic cat , 193 | no pager is spawned. 194 | .El 195 | .Sh EXIT STATUS 196 | .Ex -std 197 | .Sh SEE ALSO 198 | .Xr mmsg 7 199 | .Sh AUTHORS 200 | .An Leah Neukirchen Aq Mt leah@vuxu.org 201 | .Sh LICENSE 202 | .Nm 203 | is in the public domain. 204 | .Pp 205 | To the extent possible under law, 206 | the creator of this work 207 | has waived all copyright and related or 208 | neighboring rights to this work. 209 | .Pp 210 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 211 | -------------------------------------------------------------------------------- /man/msort.1: -------------------------------------------------------------------------------- 1 | .Dd July 22, 2016 2 | .Dt MSORT 1 3 | .Os 4 | .Sh NAME 5 | .Nm msort 6 | .Nd sort messages 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl r 10 | .Op Fl f | Fl d | Fl s | Fl F | Fl M | Fl S | Fl U | Fl I 11 | .Op Ar msgs\ ... 12 | .Sh DESCRIPTION 13 | .Nm 14 | sorts messages according to various orders, 15 | and prints, separated by newlines, the resulting message names. 16 | .Po 17 | See 18 | .Xr mmsg 7 19 | for the message argument syntax. 20 | .Pc 21 | .Pp 22 | If no messages are specified, 23 | .Nm 24 | will read filenames from the standard input, 25 | or use the default sequence if used interactively. 26 | .Pp 27 | The options are as follows: 28 | .Bl -tag -width Ds 29 | .It Fl r 30 | Reverse order. 31 | .It Fl f 32 | Sort by 33 | .Sq Li From\&: . 34 | .It Fl d 35 | Sort by date. 36 | .It Fl s 37 | Sort by 38 | .Sq Li Subject\&: 39 | (modulo various variants of 40 | .Sq Li Re\&: ) . 41 | .It Fl F 42 | Sort by filename, using proper order for numbers in filenames. 43 | .It Fl M 44 | Sort by message file modification time. 45 | .It Fl S 46 | Sort by message file size. 47 | .It Fl U 48 | Sort unread messages after read messages. 49 | .It Fl I 50 | Sort flagged messages before unflagged messages. 51 | .El 52 | .Sh EXIT STATUS 53 | .Ex -std 54 | .Sh SEE ALSO 55 | .Xr mmsg 7 56 | .Sh AUTHORS 57 | .An Leah Neukirchen Aq Mt leah@vuxu.org 58 | .Sh LICENSE 59 | .Nm 60 | is in the public domain. 61 | .Pp 62 | To the extent possible under law, 63 | the creator of this work 64 | has waived all copyright and related or 65 | neighboring rights to this work. 66 | .Pp 67 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 68 | -------------------------------------------------------------------------------- /man/mthread.1: -------------------------------------------------------------------------------- 1 | .Dd May 4, 2020 2 | .Dt MTHREAD 1 3 | .Os 4 | .Sh NAME 5 | .Nm mthread 6 | .Nd arrange messages into discussions 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Op Fl vpr 10 | .Op Fl S Ar msg 11 | .Op Ar msgs\ ... 12 | .Sh DESCRIPTION 13 | .Nm 14 | groups messages together in parent/child relationships, based on 15 | which messages are replies to which others. 16 | .Po 17 | See 18 | .Xr mmsg 7 19 | for the message argument syntax. 20 | .Pc 21 | .Pp 22 | If no messages are specified, 23 | .Nm 24 | will read filenames from the standard input, 25 | or use the default sequence if used interactively. 26 | .Pp 27 | .Nm 28 | prints the threaded messages separated by newlines and 29 | indented according to their depth in the message tree. 30 | Unresolved Message-IDs are printed as-is. 31 | .Pp 32 | The options are as follows: 33 | .Bl -tag -width Ds 34 | .It Fl v 35 | Do not prune unresolved Message-IDs at the top-level. 36 | .It Fl p 37 | With 38 | .Fl S , 39 | only add parents, not unrelated subthreads. 40 | .It Fl r 41 | Sort the top-level threads in reverse order (newest threads first). 42 | .It Fl S Ar msg 43 | Treat 44 | .Ar msg 45 | as optional message(s) that will be added to threads only if they 46 | are referenced. 47 | Threads where all messages are optional are suppressed. 48 | You can use 49 | .Fl S 50 | to add an outbox folder, for example, 51 | completing threads where your replies were missing. 52 | .El 53 | .Sh EXIT STATUS 54 | .Ex -std 55 | .Sh SEE ALSO 56 | .Xr mmsg 7 57 | .Pp 58 | .Lk https://www.jwz.org/doc/threading.html "Message threading" 59 | .Sh AUTHORS 60 | .An Leah Neukirchen Aq Mt leah@vuxu.org 61 | .Sh LICENSE 62 | .Nm 63 | is in the public domain. 64 | .Pp 65 | To the extent possible under law, 66 | the creator of this work 67 | has waived all copyright and related or 68 | neighboring rights to this work. 69 | .Pp 70 | .Lk http://creativecommons.org/publicdomain/zero/1.0/ 71 | -------------------------------------------------------------------------------- /mbnc: -------------------------------------------------------------------------------- 1 | mcom -------------------------------------------------------------------------------- /mcolor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/awk -f 2 | # mcolor - colorize rendered mail 3 | 4 | function co(n, c) { e = ENVIRON["MCOLOR_" n]; return e ? e : c } 5 | function fg(c, s) { return sprintf("\033[38;5;%03dm%s\033[0m", c, s) } 6 | function so(s) { return sprintf("\033[1m%s\033[0m", s) } 7 | BEGIN { hdr = 1; if ("NO_COLOR" in ENVIRON || match(ENVIRON["TERM"], "^(dumb|network|9term)")) no_color = 1 } 8 | no_color { print; next } 9 | /\r$/ { sub(/\r$/, "") } 10 | /^\014$/ { nextmail = 1; print(fg(co("FF",232), $0)); next } 11 | /^$/ { hdr = 0; diff = 0 } 12 | /^-- $/ { ftr = 1 } 13 | /^diff -/ { diff = 1 } 14 | /^--- .* ---/ { print fg(co("SEP",242), $0); ftr = 0; sig = 0; next } 15 | /^-----BEGIN .* SIGNATURE-----/ { sig = 1 } 16 | nextmail && /^From:/ { hdr = 1 } 17 | hdr && /^From:/ { print so(fg(co("FROM",119), $0)); next } 18 | hdr { print fg(co("HEADER",120), $0); next } 19 | ftr { print fg(co("FOOTER",244), $0); next } 20 | diff && /^-/ { print fg(co("DIFF_D",160), $0); next } 21 | diff && /^\+/ { print fg(co("DIFF_I",40), $0); next } 22 | diff && /^@/ { print fg(co("DIFF_R",226), $0); next } 23 | /^-----BEGIN .* MESSAGE-----/ || 24 | /^-----END .* SIGNATURE-----/ { print fg(co("SIG",244), $0); sig = 0; next } 25 | sig { print fg(co("SIG",244), $0); next } 26 | /^> *> *>/ { print fg(co("QQQUOTE",152), $0); next } 27 | /^> *>/ { print fg(co("QQUOTE",149), $0); next } 28 | /^>/ { print fg(co("QUOTE",151), $0); next } 29 | { nextmail = 0; print } 30 | -------------------------------------------------------------------------------- /mdate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "xpledge.h" 5 | 6 | int 7 | main() 8 | { 9 | char buf[64]; 10 | time_t now; 11 | 12 | xpledge("stdio", ""); 13 | 14 | now = time(0); 15 | 16 | ssize_t l = strftime(buf, sizeof buf, 17 | "%a, %d %b %Y %T %z\n", localtime(&now)); 18 | if (write(1, buf, l) == l) 19 | return 0; 20 | 21 | return 1; 22 | } 23 | -------------------------------------------------------------------------------- /mdirs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "blaze822.h" 13 | #include "blaze822_priv.h" 14 | #include "xpledge.h" 15 | 16 | static char sep = '\n'; 17 | int aflag; 18 | 19 | void 20 | pwd() 21 | { 22 | char cwd[PATH_MAX]; 23 | if (getcwd(cwd, sizeof cwd)) 24 | printf("%s%c", cwd, sep); 25 | } 26 | 27 | void 28 | mdirs(char *fpath) 29 | { 30 | DIR *dir; 31 | struct dirent *d; 32 | struct stat st; 33 | 34 | dir = opendir(fpath); 35 | if (!dir) 36 | return; 37 | 38 | if (chdir(fpath) < 0) { 39 | closedir(dir); 40 | return; 41 | } 42 | 43 | int dotonly = 0; 44 | 45 | if (stat("cur", &st) == 0 && 46 | S_ISDIR(st.st_mode) && 47 | stat("new", &st) == 0 && 48 | S_ISDIR(st.st_mode)) { 49 | pwd(); 50 | dotonly = 1; // Maildir++ 51 | } 52 | 53 | while ((d = readdir(dir))) { 54 | if (!DIR_DT(d->d_type)) 55 | continue; 56 | 57 | if (d->d_name[0] == '.' && 58 | d->d_name[1] == 0) 59 | continue; 60 | if (d->d_name[0] == '.' && 61 | d->d_name[1] == '.' && 62 | d->d_name[2] == 0) 63 | continue; 64 | 65 | if (!aflag && dotonly && d->d_name[0] != '.') 66 | continue; 67 | 68 | mdirs(d->d_name); 69 | } 70 | 71 | if (chdir("..") < 0) 72 | exit(-1); 73 | 74 | closedir(dir); 75 | } 76 | 77 | char * 78 | profile_maildir() 79 | { 80 | char *f = blaze822_home_file("profile"); 81 | struct message *config = blaze822(f); 82 | char *maildir; 83 | static char path[PATH_MAX]; 84 | 85 | if (!config) 86 | return 0; 87 | 88 | if (!(maildir = blaze822_hdr(config, "maildir"))) 89 | return 0; 90 | 91 | if (strncmp(maildir, "~/", 2) == 0) { 92 | const char *home = getenv("HOME"); 93 | if (!home) { 94 | struct passwd *pw = getpwuid(getuid()); 95 | home = pw ? pw->pw_dir : "/dev/null/homeless"; 96 | } 97 | snprintf(path, sizeof path, "%s/%s", home, maildir+2); 98 | maildir = path; 99 | } 100 | 101 | return maildir; 102 | } 103 | 104 | int 105 | main(int argc, char *argv[]) 106 | { 107 | int c, i; 108 | while ((c = getopt(argc, argv, "0a")) != -1) 109 | switch (c) { 110 | case '0': sep = '\0'; break; 111 | case 'a': aflag = 1; break; 112 | default: 113 | usage: 114 | fprintf(stderr, "Usage: mdirs [-0a] dirs...\n"); 115 | exit(1); 116 | } 117 | 118 | xpledge("stdio rpath", ""); 119 | 120 | if (argc == optind) { 121 | char *maildir = profile_maildir(); 122 | if (maildir) { 123 | mdirs(maildir); 124 | return 0; 125 | } 126 | goto usage; 127 | } 128 | 129 | char toplevel[PATH_MAX]; 130 | if (!getcwd(toplevel, sizeof toplevel)) { 131 | perror("mdirs: getcwd"); 132 | exit(-1); 133 | } 134 | 135 | for (i = 0; i < argc; i++) { 136 | mdirs(argv[i]); 137 | if (chdir(toplevel) < 0) { 138 | perror("mdirs: chdir"); 139 | exit(-1); 140 | } 141 | } 142 | 143 | return 0; 144 | } 145 | -------------------------------------------------------------------------------- /mexport.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "blaze822.h" 13 | #include "xpledge.h" 14 | 15 | static int Sflag; 16 | 17 | static int status; 18 | 19 | void 20 | export(char *file) 21 | { 22 | struct message *msg; 23 | 24 | while (*file == ' ' || *file == '\t') 25 | file++; 26 | 27 | FILE *infile = fopen(file, "r"); 28 | if (!infile) { 29 | status = 1; 30 | fprintf(stderr, "mexport: error opening '%s': %s\n", 31 | file, strerror(errno)); 32 | return; 33 | } 34 | 35 | char from[1024] = "nobody"; 36 | time_t date = 0; 37 | 38 | char *line = 0; 39 | size_t linelen = 0; 40 | 41 | if (fseek(infile, 0L, SEEK_SET) && errno == ESPIPE) { 42 | date = time(0); 43 | memcpy(from, "stdin", 6); 44 | } else { 45 | msg = blaze822(file); 46 | if (!msg) 47 | goto fail; 48 | 49 | char *v; 50 | if ((v = blaze822_hdr(msg, "return-path")) || 51 | (v = blaze822_hdr(msg, "x-envelope-from"))) { 52 | char *s = strchr(v, '<'); 53 | if (s) { 54 | char *e = strchr(s, '>'); 55 | if (e) { 56 | s++; 57 | snprintf(from, sizeof from, "%.*s", 58 | (int)(e-s), s); 59 | } 60 | } else { // return-path without <> 61 | snprintf(from, sizeof from, "%s", v); 62 | } 63 | } 64 | 65 | if ((v = blaze822_hdr(msg, "date"))) { 66 | date = blaze822_date(v); 67 | } 68 | blaze822_free(msg); 69 | } 70 | 71 | printf("From %s %s", from, asctime(gmtime(&date))); 72 | 73 | int in_header = 1; 74 | int final_nl = 0; 75 | 76 | while (1) { 77 | errno = 0; 78 | ssize_t rd = getdelim(&line, &linelen, '\n', infile); 79 | if (rd == -1) { 80 | if (errno == 0) 81 | break; 82 | fprintf(stderr, "mexport: error reading '%s': %s\n", 83 | file, strerror(errno)); 84 | status = 1; 85 | goto fail; 86 | } 87 | 88 | if (in_header && line[0] == '\n' && !line[1]) { 89 | if (Sflag) { 90 | char *flags = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA); 91 | if (!flags) 92 | flags = ""; 93 | 94 | fputs("Status: ", stdout); 95 | if (strchr(flags, 'S')) 96 | putchar('R'); 97 | char *ee = strrchr(file, '/'); 98 | if (!ee || 99 | !(ee >= file + 3 && ee[-3] == 'n' && ee[-2] == 'e' && ee[-1] == 'w')) 100 | putchar('O'); 101 | putchar('\n'); 102 | 103 | fputs("X-Status: ", stdout); 104 | if (strchr(flags, 'R')) putchar('A'); 105 | if (strchr(flags, 'T')) putchar('D'); 106 | if (strchr(flags, 'F')) putchar('F'); 107 | putchar('\n'); 108 | } 109 | 110 | in_header = 0; 111 | } 112 | 113 | // MBOXRD: add first > to >>..>>From 114 | char *s = line; 115 | while (*s == '>') 116 | s++; 117 | if (strncmp("From ", s, 5) == 0) 118 | putchar('>'); 119 | 120 | fputs(line, stdout); 121 | final_nl = (line[rd-1] == '\n'); 122 | } 123 | 124 | // ensure trailing newline 125 | if (!final_nl) 126 | putchar('\n'); 127 | 128 | // ensure empty line at end of message 129 | putchar('\n'); 130 | 131 | fail: 132 | free(line); 133 | fclose(infile); 134 | } 135 | 136 | int 137 | main(int argc, char *argv[]) 138 | { 139 | int c; 140 | while ((c = getopt(argc, argv, "S")) != -1) 141 | switch (c) { 142 | case 'S': Sflag = 1; break; 143 | default: 144 | fprintf(stderr, "Usage: mexport [-S] [msgs...]\n"); 145 | exit(2); 146 | } 147 | 148 | status = 0; 149 | 150 | xpledge("stdio rpath", ""); 151 | 152 | if (argc == optind && isatty(0)) 153 | blaze822_loop1(":", export); 154 | else 155 | blaze822_loop(argc-optind, argv+optind, export); 156 | 157 | return status; 158 | } 159 | -------------------------------------------------------------------------------- /mflag.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "blaze822.h" 15 | #include "blaze822_priv.h" 16 | #include "xpledge.h" 17 | 18 | static int8_t flags[255]; 19 | static int vflag = 0; 20 | 21 | char **args; 22 | ssize_t argsalloc = 256; 23 | int idx = 0; 24 | char *curfile; 25 | 26 | void 27 | add(char *file) 28 | { 29 | if (idx >= argsalloc) { 30 | argsalloc *= 2; 31 | if (argsalloc < 0) 32 | exit(-1); 33 | args = realloc(args, sizeof (char *) * argsalloc); 34 | } 35 | if (!args) 36 | exit(-1); 37 | while (*file == ' ' || *file == '\t') 38 | file++; 39 | args[idx] = strdup(file); 40 | idx++; 41 | } 42 | 43 | void 44 | flag(char *file) 45 | { 46 | int indent = 0; 47 | while (file[indent] == ' ' || file[indent] == '\t') 48 | indent++; 49 | 50 | char *f = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA); 51 | if (!f) 52 | goto skip; 53 | 54 | if (args) { 55 | int i; 56 | for (i = 0; i < idx; i++) 57 | if (strcmp(file+indent, args[i]) == 0) 58 | goto doit; 59 | goto skip; 60 | } 61 | 62 | doit: 63 | ; 64 | int8_t myflags[255] = { 0 }; 65 | char *s; 66 | for (s = f+3; *s; s++) 67 | myflags[(unsigned int)*s] = 1; 68 | 69 | int changed = 0; 70 | unsigned int i; 71 | for (i = 0; i < sizeof myflags; i++) { 72 | int z = myflags[i]; 73 | myflags[i] += flags[i]; 74 | if ((z <= 0 && myflags[i] > 0) || 75 | (z > 0 && myflags[i] <= 0)) 76 | changed = 1; 77 | } 78 | if (changed) { 79 | char dst[PATH_MAX]; 80 | char *s = file; 81 | char *t = dst; 82 | while (s < f+3 && t < dst + sizeof dst - 1) 83 | *t++ = *s++; 84 | for (i = 0; i < sizeof myflags && t < dst + sizeof dst - 1; i++) 85 | if (myflags[i] > 0) 86 | *t++ = i; 87 | *t = 0; 88 | 89 | if (rename(file+indent, dst+indent) < 0) { 90 | fprintf(stderr, "mflag: can't rename '%s' to '%s': %s\n", 91 | file+indent, dst+indent, strerror(errno)); 92 | goto skip; 93 | } 94 | 95 | if (curfile && strcmp(file+indent, curfile) == 0) 96 | blaze822_seq_setcur(dst+indent); 97 | 98 | printf("%s\n", dst); 99 | 100 | return; 101 | } 102 | 103 | skip: 104 | if (vflag) 105 | printf("%s\n", file); 106 | } 107 | 108 | int 109 | main(int argc, char *argv[]) 110 | { 111 | int c; 112 | while ((c = getopt(argc, argv, "PRSTDFprstdfX:x:v")) != -1) 113 | switch (c) { 114 | case 'P': case 'R': case 'S': case 'T': case 'D': case 'F': 115 | flags[(unsigned int)c] = 1; 116 | break; 117 | case 'p': case 'r': case 's': case 't': case 'd': case 'f': 118 | flags[(unsigned int)uc(c)] = -1; 119 | break; 120 | case 'X': 121 | while (*optarg) 122 | flags[(unsigned int)*optarg++] = 1; 123 | break; 124 | case 'x': 125 | while (*optarg) 126 | flags[(unsigned int)*optarg++] = -1; 127 | break; 128 | case 'v': vflag = 1; break; 129 | default: 130 | fprintf(stderr, 131 | "Usage: mflag [-DFPRST] [-X str]\n" 132 | " [-dfprst] [-x str]\n" 133 | " [-v] [msgs...]\n" 134 | ); 135 | exit(1); 136 | } 137 | 138 | xpledge("stdio rpath cpath", ""); 139 | 140 | curfile = blaze822_seq_cur(); 141 | 142 | if (vflag) { 143 | if (argc == optind && !isatty(0)) { 144 | blaze822_loop(0, 0, flag); // read from stdin 145 | return 0; 146 | } 147 | 148 | args = calloc(argsalloc, sizeof (char *)); 149 | if (!args) 150 | exit(-1); 151 | 152 | if (argc == optind) 153 | blaze822_loop1(".", add); 154 | else 155 | blaze822_loop(argc-optind, argv+optind, add); 156 | 157 | if (isatty(0)) 158 | blaze822_loop1(":", flag); 159 | else 160 | blaze822_loop(0, 0, flag); 161 | 162 | return 0; 163 | } 164 | 165 | if (argc == optind && isatty(0)) 166 | blaze822_loop1(".", flag); 167 | else 168 | blaze822_loop(argc-optind, argv+optind, flag); 169 | 170 | return 0; 171 | } 172 | -------------------------------------------------------------------------------- /mflow.c: -------------------------------------------------------------------------------- 1 | #ifdef __sun 2 | #define __EXTENSIONS__ /* to get TIOCGWINSZ */ 3 | #endif 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "blaze822.h" 19 | #include "xpledge.h" 20 | 21 | int column = 0; 22 | int maxcolumn = 80; 23 | 24 | void 25 | chgquote(int quotes) 26 | { 27 | static int oquotes; 28 | 29 | if (quotes != oquotes) { 30 | if (column) 31 | putchar('\n'); 32 | column = 0; 33 | oquotes = quotes; 34 | } 35 | } 36 | 37 | void 38 | flowed(int quotes, char *line, ssize_t linelen) 39 | { 40 | chgquote(quotes); 41 | int done = 0; 42 | 43 | while (!done) { 44 | if (column == 0) { 45 | for (; column < quotes; column++) 46 | putchar('>'); 47 | column++; 48 | if (quotes && *line != ' ') 49 | putchar(' '); 50 | } 51 | 52 | char *eow; 53 | if (*line == ' ') 54 | eow = memchr(line + 1, ' ', linelen - 1); 55 | else 56 | eow = memchr(line, ' ', linelen); 57 | 58 | if (!eow) { 59 | eow = line + linelen; 60 | done = 1; 61 | } 62 | 63 | if (column + (eow - line) > maxcolumn && 64 | eow - line < maxcolumn && 65 | column - quotes > 1) { 66 | putchar('\n'); 67 | column = 0; 68 | done = 0; 69 | if (*line == ' ') { 70 | line++; 71 | linelen--; 72 | } 73 | } else { 74 | fwrite(line, 1, eow - line, stdout); 75 | column += eow - line; 76 | linelen -= eow - line; 77 | line = eow; 78 | } 79 | } 80 | } 81 | 82 | void 83 | fixed(int quotes, char *line, size_t linelen) 84 | { 85 | flowed(quotes, line, linelen); 86 | 87 | putchar('\n'); 88 | column = 0; 89 | } 90 | 91 | int 92 | main(int argc, char *argv[]) 93 | { 94 | char *linebuf = 0; 95 | char *line; 96 | size_t linelen = 0; 97 | int outer_quotes = 0; 98 | int quotes; 99 | 100 | int reflow = 1; // re-evaluated on $PIPE_CONTENTTYPE 101 | int force = 0; 102 | int delsp = 0; 103 | 104 | xpledge("stdio rpath tty", ""); 105 | 106 | char *ct = getenv("PIPE_CONTENTTYPE"); 107 | if (ct) { 108 | char *s, *se; 109 | reflow = 0; 110 | if (blaze822_mime_parameter(ct, "format", &s, &se) && s) 111 | reflow = (strncasecmp(s, "flowed", 6) == 0); 112 | if (blaze822_mime_parameter(ct, "delsp", &s, &se) && s) 113 | delsp = (strncasecmp(s, "yes", 3) == 0); 114 | } 115 | 116 | char *cols = getenv("COLUMNS"); 117 | if (cols && isdigit(*cols)) { 118 | maxcolumn = atoi(cols); 119 | } else { 120 | struct winsize w; 121 | int fd = open("/dev/tty", O_RDONLY | O_NOCTTY); 122 | if (fd >= 0) { 123 | if (ioctl(fd, TIOCGWINSZ, &w) == 0) 124 | maxcolumn = w.ws_col; 125 | close(fd); 126 | } 127 | } 128 | 129 | xpledge("stdio", ""); 130 | 131 | char *maxcols = getenv("MAXCOLUMNS"); 132 | if (maxcols && isdigit(*maxcols)) { 133 | int m = atoi(maxcols); 134 | if (maxcolumn > m) 135 | maxcolumn = m; 136 | } 137 | 138 | int c; 139 | while ((c = getopt(argc, argv, "fqw:")) != -1) 140 | switch (c) { 141 | case 'f': force = 1; break; 142 | case 'q': outer_quotes++; break; 143 | case 'w': maxcolumn = atoi(optarg); break; 144 | default: 145 | fprintf(stderr, "Usage: mflow [-f] [-q] [-w MAXCOLUMNS]\n"); 146 | exit(2); 147 | } 148 | 149 | while (1) { 150 | errno = 0; 151 | ssize_t rd = getdelim(&linebuf, &linelen, '\n', stdin); 152 | if (rd == -1) { 153 | if (errno == 0) 154 | break; 155 | fprintf(stderr, "mflow: error reading: %s\n", 156 | strerror(errno)); 157 | exit(1); 158 | } 159 | 160 | line = linebuf; 161 | 162 | if (!reflow && !force) { 163 | fwrite(line, 1, rd, stdout); 164 | continue; 165 | } 166 | 167 | if (rd > 0 && line[rd-1] == '\n') 168 | line[--rd] = 0; 169 | if (rd > 0 && line[rd-1] == '\r') 170 | line[--rd] = 0; 171 | 172 | quotes = outer_quotes; 173 | while (*line == '>') { // measure quote depth 174 | line++; 175 | quotes++; 176 | rd--; 177 | } 178 | 179 | if (reflow && *line == ' ') { // space stuffing 180 | line++; 181 | rd--; 182 | } 183 | 184 | if (strcmp(line, "-- ") == 0) { // usenet signature convention 185 | if (column) 186 | fixed(quotes, "", 0); // flush paragraph 187 | fixed(quotes, line, rd); 188 | continue; 189 | } 190 | 191 | if (reflow && rd > 0 && line[rd-1] == ' ') { // flowed line 192 | if (delsp) 193 | line[--rd] = 0; 194 | flowed(quotes, line, rd); 195 | } else if (rd == 0) { // empty line is fixed 196 | if (column > 0) 197 | putchar('\n'); 198 | putchar('\n'); 199 | column = 0; 200 | } else { 201 | if (force && rd > maxcolumn) { 202 | flowed(quotes, line, rd); 203 | fixed(quotes, "", 0); 204 | } else { 205 | fixed(quotes, line, rd); 206 | } 207 | } 208 | } 209 | 210 | if (reflow && column != 0) 211 | putchar('\n'); 212 | } 213 | -------------------------------------------------------------------------------- /mgenmid.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "blaze822.h" 16 | #include "xpledge.h" 17 | 18 | void 19 | printb36(uint64_t x) 20 | { 21 | static char const base36[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 22 | 23 | char outbuf[16]; 24 | char *o = outbuf + sizeof outbuf; 25 | 26 | *--o = 0; 27 | do { *--o = base36[x % 36]; } while (x /= 36); 28 | 29 | fputs(o, stdout); 30 | } 31 | 32 | int main() 33 | { 34 | char hostbuf[1024]; 35 | char *host = 0; 36 | 37 | char *f = blaze822_home_file("profile"); 38 | struct message *config = blaze822(f); 39 | 40 | xpledge("stdio rpath dns", ""); 41 | 42 | if (config) // try FQDN: first 43 | host = blaze822_hdr(config, "fqdn"); 44 | 45 | if (!host && gethostname(hostbuf, sizeof hostbuf) == 0) { 46 | // termination not posix guaranteed 47 | hostbuf[sizeof hostbuf - 1] = 0; 48 | 49 | struct addrinfo hints = { .ai_family = AF_UNSPEC, 50 | .ai_socktype = SOCK_STREAM, 51 | .ai_flags = AI_CANONNAME }; 52 | struct addrinfo *info; 53 | if (getaddrinfo(hostbuf, 0, &hints, &info) == 0) { 54 | // sanity checks: no (null), at least one dot, 55 | // doesn't start with localhost. 56 | 57 | if (info && 58 | info->ai_canonname && 59 | strchr(info->ai_canonname, '.') && 60 | strncmp(info->ai_canonname, "localhost.", 10) != 0) 61 | host = info->ai_canonname; 62 | } 63 | } 64 | 65 | if (!host && config) { 66 | // get address part of Local-Mailbox: 67 | char *disp, *addr; 68 | char *from = blaze822_hdr(config, "local-mailbox"); 69 | while (from && (from = blaze822_addr(from, &disp, &addr))) 70 | if (addr) { 71 | host = strchr(addr, '@'); 72 | if (host) { 73 | host++; 74 | break; 75 | } 76 | } 77 | } 78 | 79 | if (!host) { 80 | fprintf(stderr, 81 | "mgenmid: failed to find a FQDN for the Message-ID.\n" 82 | " Define 'FQDN:' or 'Local-Mailbox:' in" 83 | " ${MBLAZE:-$HOME/.mblaze}/profile\n" 84 | " or add a FQDN to /etc/hosts.\n"); 85 | exit(1); 86 | } 87 | 88 | struct timeval tp; 89 | gettimeofday(&tp, (struct timezone *)0); 90 | 91 | uint64_t rnd1, rnd2; 92 | 93 | int rndfd = open("/dev/urandom", O_RDONLY); 94 | if (rndfd >= 0) { 95 | unsigned char rndb[16]; 96 | if (read(rndfd, rndb, sizeof rndb) != sizeof rndb) 97 | goto fallback; 98 | close(rndfd); 99 | 100 | int i; 101 | for (i = 0, rnd1 = 0; i < 8; i++) 102 | rnd1 = rnd1*256 + rndb[i]; 103 | for (i = 0, rnd2 = 0; i < 8; i++) 104 | rnd2 = rnd2*256 + rndb[i+8]; 105 | } else { 106 | fallback: 107 | srand48(tp.tv_sec ^ tp.tv_usec ^ getpid()); 108 | rnd1 = ((uint64_t)lrand48() << 32) + lrand48(); 109 | rnd2 = ((uint64_t)lrand48() << 32) + lrand48(); 110 | } 111 | 112 | rnd1 ^= ((uint64_t)tp.tv_sec * 1000000LL + tp.tv_usec); 113 | rnd1 |= (1ULL << 63); // set highest bit to force full width 114 | rnd2 |= (1ULL << 63); // set highest bit to force full width 115 | 116 | putchar('<'); 117 | printb36(rnd1); 118 | putchar('.'); 119 | printb36(rnd2); 120 | putchar('@'); 121 | fputs(host, stdout); 122 | putchar('>'); 123 | putchar('\n'); 124 | 125 | return 0; 126 | } 127 | -------------------------------------------------------------------------------- /mhdr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "blaze822.h" 14 | #include "xpledge.h" 15 | 16 | static char *hflag; 17 | static char *pflag; 18 | static int Aflag; 19 | static int Dflag; 20 | static int Hflag; 21 | static int Mflag; 22 | static int dflag; 23 | 24 | static char *curfile; 25 | static int status; 26 | 27 | static void 28 | printhdr(char *hdr) 29 | { 30 | int uc = 1; 31 | 32 | if (Hflag) 33 | printf("%s\t", curfile); 34 | 35 | while (*hdr && *hdr != ':') { 36 | putc(uc ? toupper(*hdr) : *hdr, stdout); 37 | uc = (*hdr == '-'); 38 | hdr++; 39 | } 40 | fputs(hdr, stdout); 41 | fputc('\n', stdout); 42 | 43 | status = 0; 44 | } 45 | 46 | void 47 | headerall(struct message *msg) 48 | { 49 | char *h = 0; 50 | while ((h = blaze822_next_header(msg, h))) { 51 | if (dflag) { 52 | char d[4096]; 53 | blaze822_decode_rfc2047(d, h, sizeof d, "UTF-8"); 54 | printhdr(d); 55 | } else { 56 | printhdr(h); 57 | } 58 | } 59 | 60 | blaze822_free(msg); 61 | } 62 | 63 | void 64 | print_quoted(char *s) 65 | { 66 | char *t; 67 | 68 | for (t = s; *t; t++) 69 | if ((unsigned char)*t < 32 || strchr("()<>[]:;@\\,.\"", *t)) 70 | goto quote; 71 | 72 | printf("%s", s); 73 | return; 74 | 75 | quote: 76 | putchar('"'); 77 | for (t = s; *t; t++) { 78 | if (*t == '"' || *t == '\\') 79 | putchar('\\'); 80 | putchar(*t); 81 | } 82 | putchar('"'); 83 | 84 | } 85 | 86 | void 87 | print_addresses(char *s) 88 | { 89 | char *disp, *addr; 90 | char ddec[4096]; 91 | 92 | while ((s = blaze822_addr(s, &disp, &addr))) { 93 | if (Hflag && addr) 94 | printf("%s\t", curfile); 95 | 96 | if (disp && addr) { 97 | if (dflag) { 98 | blaze822_decode_rfc2047(ddec, disp, sizeof ddec, "UTF-8"); 99 | ddec[sizeof ddec - 1] = 0; 100 | disp = ddec; 101 | } 102 | 103 | print_quoted(disp); 104 | printf(" <%s>\n", addr); 105 | } else if (addr) { 106 | printf("%s\n", addr); 107 | } 108 | } 109 | } 110 | 111 | void 112 | print_date(char *s) 113 | { 114 | time_t t = blaze822_date(s); 115 | if (t == -1) 116 | return; 117 | printf("%ld\n", (long)t); 118 | } 119 | 120 | void 121 | print_decode_header(char *s) 122 | { 123 | char d[4096]; 124 | blaze822_decode_rfc2047(d, s, sizeof d, "UTF-8"); 125 | printf("%s\n", d); 126 | } 127 | 128 | void 129 | print_header(char *v) 130 | { 131 | if (pflag) { 132 | char *s, *se; 133 | if (blaze822_mime_parameter(v, pflag, &s, &se)) { 134 | *se = 0; 135 | v = s; 136 | } else { 137 | return; 138 | } 139 | } 140 | 141 | status = 0; 142 | 143 | if (Hflag && !Aflag) 144 | printf("%s\t", curfile); 145 | 146 | if (Aflag) 147 | print_addresses(v); 148 | else if (Dflag) 149 | print_date(v); 150 | else if (dflag) 151 | print_decode_header(v); 152 | else 153 | printf("%s\n", v); 154 | } 155 | 156 | void 157 | headermany(struct message *msg) 158 | { 159 | char *hdr = 0; 160 | while ((hdr = blaze822_next_header(msg, hdr))) { 161 | char *h = hflag; 162 | while (*h) { 163 | char *n = strchr(h, ':'); 164 | if (n) 165 | *n = 0; 166 | 167 | size_t l = strlen(h); 168 | if (strncasecmp(hdr, h, l) == 0 && hdr[l] == ':') { 169 | hdr += l + 1; 170 | while (*hdr == ' ' || *hdr == '\t') 171 | hdr++; 172 | print_header(hdr); 173 | } 174 | 175 | if (n) { 176 | *n = ':'; 177 | h = n + 1; 178 | } else { 179 | break; 180 | } 181 | } 182 | } 183 | 184 | blaze822_free(msg); 185 | } 186 | 187 | void 188 | header(char *file) 189 | { 190 | struct message *msg; 191 | 192 | while (*file == ' ' || *file == '\t') 193 | file++; 194 | 195 | curfile = file; 196 | 197 | msg = blaze822(file); 198 | if (!msg) 199 | return; 200 | 201 | if (!hflag) { 202 | headerall(msg); 203 | return; 204 | } 205 | if (Mflag) { 206 | headermany(msg); 207 | return; 208 | } 209 | 210 | char *h = hflag; 211 | while (*h) { 212 | char *n = strchr(h, ':'); 213 | if (n) 214 | *n = 0; 215 | char *v = blaze822_chdr(msg, h); 216 | if (v) 217 | print_header(v); 218 | if (n) { 219 | *n = ':'; 220 | h = n + 1; 221 | } else { 222 | break; 223 | } 224 | } 225 | 226 | blaze822_free(msg); 227 | } 228 | 229 | int 230 | main(int argc, char *argv[]) 231 | { 232 | int c; 233 | while ((c = getopt(argc, argv, "h:p:ADHMd")) != -1) 234 | switch (c) { 235 | case 'h': hflag = optarg; break; 236 | case 'p': pflag = optarg; break; 237 | case 'A': Aflag = 1; break; 238 | case 'D': Dflag = 1; break; 239 | case 'H': Hflag = 1; break; 240 | case 'M': Mflag = 1; break; 241 | case 'd': dflag = 1; break; 242 | default: 243 | fprintf(stderr, 244 | "Usage: mhdr [-h header [-p parameter]] [-d] [-H] [-M] [-A|-D] [msgs...]\n"); 245 | exit(2); 246 | } 247 | 248 | status = 1; 249 | 250 | xpledge("stdio rpath", ""); 251 | 252 | if (argc == optind && isatty(0)) 253 | blaze822_loop1(".", header); 254 | else 255 | blaze822_loop(argc-optind, argv+optind, header); 256 | 257 | return status; 258 | } 259 | -------------------------------------------------------------------------------- /minc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "blaze822.h" 14 | #include "blaze822_priv.h" 15 | #include "xpledge.h" 16 | 17 | static int qflag; 18 | static int status; 19 | 20 | void 21 | inc(char *dir) 22 | { 23 | DIR *fd; 24 | struct dirent *d; 25 | char src[PATH_MAX]; 26 | char dst[PATH_MAX]; 27 | 28 | squeeze_slash(dir); 29 | 30 | snprintf(src, sizeof src, "%s/new", dir); 31 | fd = opendir(src); 32 | if (!fd) { 33 | fprintf(stderr, "minc: can't open maildir '%s': %s\n", 34 | src, strerror(errno)); 35 | status = 2; 36 | return; 37 | } 38 | 39 | while ((d = readdir(fd))) { 40 | if (!MAIL_DT(d->d_type)) 41 | continue; 42 | 43 | if (d->d_name[0] == '.') 44 | continue; 45 | 46 | snprintf(src, sizeof src, "%s/new/%s", 47 | dir, d->d_name); 48 | snprintf(dst, sizeof dst, "%s/cur/%s%s", 49 | dir, d->d_name, 50 | strstr(d->d_name, MAILDIR_COLON_SPEC_VER_COMMA) ? "" : MAILDIR_COLON_SPEC_VER_COMMA); 51 | if (rename(src, dst) < 0) { 52 | fprintf(stderr, "minc: can't rename '%s' to '%s': %s\n", 53 | src, dst, strerror(errno)); 54 | status = 3; 55 | continue; 56 | } 57 | 58 | if (!qflag) 59 | printf("%s\n", dst); 60 | } 61 | 62 | closedir(fd); 63 | } 64 | 65 | int 66 | main(int argc, char *argv[]) 67 | { 68 | int c, i; 69 | while ((c = getopt(argc, argv, "q")) != -1) 70 | switch (c) { 71 | case 'q': qflag = 1; break; 72 | default: 73 | usage: 74 | fprintf(stderr, "Usage: minc [-q] dirs...\n"); 75 | exit(1); 76 | } 77 | 78 | xpledge("stdio rpath cpath", ""); 79 | 80 | status = 0; 81 | if (optind == argc) { 82 | if (isatty(0)) 83 | goto usage; 84 | blaze822_loop(0, 0, inc); 85 | } else { 86 | for (i = optind; i < argc; i++) 87 | inc(argv[i]); 88 | } 89 | 90 | return status; 91 | } 92 | -------------------------------------------------------------------------------- /mless: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mless [MSG] - less(1)-wrapper around mshow 3 | 4 | colorscan() { 5 | awk ' 6 | function co(n, c) { e = ENVIRON["MCOLOR_" n]; return e ? e : c } 7 | function fg(c, s) { return sprintf("\033[38;5;%03dm%s\033[0m", c, s) } 8 | function so(s) { return sprintf("\033[1m%s\033[0m", s) } 9 | /^>/ { print so(fg(co("CUR",119), $0)); next } 10 | /^ *\\_/ { print fg(co("MISS",242), $0); next } 11 | { print }' 12 | } 13 | 14 | if [ -n "${NO_COLOR+set}" ]; then 15 | colorscan() { cat -; } 16 | fi 17 | 18 | if [ "$1" = --filter ]; then 19 | if [ "$2" = //scan ]; then 20 | mscan : 2>/dev/null | colorscan 21 | exit $? 22 | fi 23 | 24 | mseq -C "$2" 25 | 26 | total=$(mscan -n -- -1) 27 | case $2 in 28 | 1) mscan .-0:.+5 ;; 29 | 2) mscan .-1:.+4 ;; 30 | $((total - 2))) mscan .-3:.+2 ;; 31 | $((total - 1))) mscan .-4:.+1 ;; 32 | $total) mscan .-5:.+0 ;; 33 | *) mscan .-2:.+3 ;; 34 | esac 2>/dev/null | colorscan 35 | echo 36 | 37 | if ! [ -f "$(mseq -r "$2")" ]; then 38 | mseq "$2" 39 | exit 40 | fi 41 | 42 | if [ $MLESS_RAW -eq 0 ]; then 43 | if [ $MLESS_HTML -eq 1 ]; then 44 | mshow -A text/html "$2" 45 | else 46 | mshow "$2" 47 | fi | mcolor 48 | else 49 | mseq -r "$2" 50 | echo 51 | cat "$(mseq -r "$2")" 52 | fi 53 | exit $? 54 | fi 55 | 56 | if [ "$#" -eq 0 ] && ! [ -t 0 ]; then 57 | mseq -S >/dev/null 58 | set -- : 59 | fi 60 | 61 | if ! [ -t 1 ]; then 62 | exec mseq : 63 | fi 64 | 65 | [ "$#" -eq 1 ] && set -- ${1:-.} 66 | 67 | if [ "$#" -ge 1 ]; then 68 | mseq -C "$1" 69 | fi 70 | 71 | nl=" 72 | " 73 | export MLESS_RAW=0 74 | export MLESS_HTML=0 75 | if [ -f "$MBLAZE/mlesskey" ]; then 76 | export LESSKEYIN="$MBLAZE/mlesskey" 77 | elif [ -f "$HOME/.mblaze/mlesskey" ]; then 78 | export LESSKEYIN="$HOME/.mblaze/mlesskey" 79 | elif [ -f "$HOME/.mlesskey" ]; then 80 | export LESSKEYIN="$HOME/.mlesskey" 81 | elif [ -f "$MBLAZE/mless" ]; then 82 | export LESSKEY="$MBLAZE/mless" 83 | elif [ -f "$HOME/.mblaze/mless" ]; then 84 | export LESSKEY="$HOME/.mblaze/mless" 85 | elif [ -f "$HOME/.mless" ]; then 86 | export LESSKEY="$HOME/.mless" 87 | fi 88 | while :; do 89 | LESSOPEN="|$0 --filter %s" \ 90 | less -Ps"mless %f?m (message %i of %m).." -R \ 91 | "+:e $(mscan -n .)$nl" //scan $(mscan -n :) 92 | case "$?" in 93 | 0|1) exit $?;; 94 | 36) # $ goto end 95 | mseq -C '$' 2>/dev/null 96 | ;; 97 | 78) # N go to next unseen message 98 | nu=$(magrep -v -m1 :S .:) && mseq -C "$nu" 99 | ;; 100 | 107) # k next thread 101 | mseq -C "$(mseq .+1: | sed -n '/^[^ <]/{p;q;}')" 102 | ;; 103 | 100) # d mark read 104 | mflag -S . 105 | mseq -f : | mseq -S 106 | mseq -C + 107 | ;; 108 | 82) # R toggle raw mode 109 | MLESS_RAW=$((1-$MLESS_RAW)) 110 | ;; 111 | 72) # H toggle HTML mode 112 | MLESS_HTML=$((1-$MLESS_HTML)) 113 | ;; 114 | 94) # ^ goto parent 115 | mseq -C '.^' 2>/dev/null 116 | ;; 117 | esac 118 | done 119 | -------------------------------------------------------------------------------- /mlesskey.example: -------------------------------------------------------------------------------- 1 | # mless(1) keybindings 2 | # When using less <590: 3 | # compile with lesskey -o .mless mlesskey and install as ~/.mless 4 | # When using less >=590: copy this file to ~/.mlesskey 5 | Q quit \1 6 | :cq quit \1 7 | [ prev-file 8 | ] next-file 9 | { noaction E1\n 10 | } quit $ 11 | $ quit $ 12 | S noaction E//scan\n 13 | ` noaction E\#\n 14 | H quit H 15 | N quit N 16 | R quit R 17 | k quit k 18 | d quit d 19 | \^ quit \^ 20 | -------------------------------------------------------------------------------- /mlist.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "blaze822.h" 15 | #include "blaze822_priv.h" 16 | #include "xpledge.h" 17 | 18 | /* 19 | 20 | PRSTDF 21 | prstdf 22 | 23 | -N new 24 | -n not new 25 | 26 | -C cur 27 | -c not cur 28 | 29 | -m age 30 | 31 | -r recursive? 32 | 33 | */ 34 | 35 | static int8_t flags[255]; 36 | static int flagsum; 37 | static int flagset; 38 | static int Nflag; 39 | static int Cflag; 40 | static int iflag; 41 | 42 | static long icount; 43 | static long iunseen; 44 | static long iflagged; 45 | static long imatched; 46 | 47 | static long tdirs; 48 | static long tunseen; 49 | static long tflagged; 50 | static long tcount; 51 | static long tmatched; 52 | 53 | void 54 | list(char *prefix, char *file) 55 | { 56 | char *f = 0; 57 | 58 | if (flagset || iflag) { 59 | size_t prefixlen; 60 | 61 | f = strstr(file, MAILDIR_COLON_SPEC_VER_COMMA); 62 | 63 | if (!f && 64 | prefix && 65 | (prefixlen = strlen(prefix)) && 66 | prefixlen >= 4 && 67 | strcmp(prefix + prefixlen - 4, "/new") == 0) 68 | f = MAILDIR_COLON_SPEC_VER_COMMA; 69 | } 70 | 71 | if (flagset) { 72 | int sum = 0; 73 | if (!f) 74 | return; 75 | icount++; 76 | tcount++; 77 | char *g; 78 | for (g = f + 3; *g; g++) 79 | if (flags[(unsigned int)*g] == -1) 80 | return; 81 | else if (flags[(unsigned int)*g] == 1) 82 | sum++; 83 | if (sum != flagsum) 84 | return; 85 | } 86 | 87 | if (iflag) { 88 | if (!f) 89 | return; 90 | imatched++; 91 | tmatched++; 92 | if (!flagset) 93 | icount++, tcount++; 94 | if (!strchr(f, 'S')) 95 | iunseen++, tunseen++; 96 | if (strchr(f, 'F')) 97 | iflagged++, tflagged++; 98 | return; 99 | } 100 | 101 | if (prefix) { 102 | fputs(prefix, stdout); 103 | putc('/', stdout); 104 | } 105 | puts(file); 106 | } 107 | 108 | #ifdef __linux__ 109 | // faster implementation of readdir using a bigger getdents buffer 110 | 111 | #include 112 | 113 | struct linux_dirent64 { 114 | uint64_t d_ino; /* 64-bit inode number */ 115 | int64_t d_off; /* 64-bit offset to next structure */ 116 | unsigned short d_reclen; /* Size of this dirent */ 117 | unsigned char d_type; /* File type */ 118 | char d_name[]; /* Filename (null-terminated) */ 119 | }; 120 | #define BUF_SIZE 1024000 121 | char buf[BUF_SIZE]; 122 | 123 | void 124 | listdir(char *dir) 125 | { 126 | int fd; 127 | ssize_t nread; 128 | ssize_t bpos; 129 | struct linux_dirent64 *d; 130 | 131 | fd = open(dir, O_RDONLY | O_DIRECTORY); 132 | if (fd == -1) { 133 | perror("open"); 134 | return; 135 | } 136 | 137 | while (1) { 138 | nread = syscall(SYS_getdents64, fd, buf, BUF_SIZE); 139 | if (nread == -1) { 140 | perror("getdents64"); 141 | break; 142 | } 143 | 144 | if (nread == 0) 145 | break; 146 | 147 | for (bpos = 0; bpos < nread; bpos += d->d_reclen) { 148 | d = (struct linux_dirent64 *)(buf + bpos); 149 | if (!MAIL_DT(d->d_type)) 150 | continue; 151 | if (d->d_name[0] == '.') 152 | continue; 153 | list(dir, d->d_name); 154 | } 155 | } 156 | 157 | close(fd); 158 | } 159 | #else 160 | void 161 | listdir(char *dir) 162 | { 163 | DIR *fd; 164 | struct dirent *d; 165 | 166 | fd = opendir(dir); 167 | if (!fd) 168 | return; 169 | while ((d = readdir(fd))) { 170 | if (!MAIL_DT(d->d_type)) 171 | continue; 172 | if (d->d_name[0] == '.') 173 | continue; 174 | list(dir, d->d_name); 175 | } 176 | closedir(fd); 177 | } 178 | #endif 179 | 180 | void 181 | listarg(char *arg) 182 | { 183 | squeeze_slash(arg); 184 | 185 | struct stat st; 186 | if (stat(arg, &st) < 0) 187 | return; 188 | if (S_ISDIR(st.st_mode)) { 189 | char subdir[PATH_MAX]; 190 | struct stat st2; 191 | int maildir = 0; 192 | 193 | long gcount = icount; 194 | long gunseen = iunseen; 195 | long gflagged = iflagged; 196 | long gmatched = imatched; 197 | 198 | icount = 0; 199 | iunseen = 0; 200 | iflagged = 0; 201 | 202 | snprintf(subdir, sizeof subdir, "%s/cur", arg); 203 | if (stat(subdir, &st2) == 0) { 204 | maildir = 1; 205 | if (Cflag >= 0 && Nflag <= 0) 206 | listdir(subdir); 207 | } 208 | 209 | snprintf(subdir, sizeof subdir, "%s/new", arg); 210 | if (stat(subdir, &st2) == 0) { 211 | maildir = 1; 212 | if (Nflag >= 0 && Cflag <= 0) 213 | listdir(subdir); 214 | } 215 | 216 | if (!maildir) 217 | listdir(arg); 218 | 219 | if (iflag && (imatched || (maildir && !flagset))) { 220 | tdirs++; 221 | if (flagset && imatched) 222 | printf("%6ld matched %6ld unseen %3ld flagged %6ld msg %s\n", 223 | imatched, iunseen, iflagged, icount, arg); 224 | else 225 | printf("%6ld unseen %3ld flagged %6ld msg %s\n", 226 | iunseen, iflagged, icount, arg); 227 | } 228 | 229 | icount = gcount; 230 | iunseen = gunseen; 231 | iflagged = gflagged; 232 | imatched = gmatched; 233 | } else if (S_ISREG(st.st_mode)) { 234 | list(0, arg); 235 | } 236 | } 237 | 238 | int 239 | main(int argc, char *argv[]) 240 | { 241 | int c; 242 | while ((c = getopt(argc, argv, "PRSTDFprstdfX:x:NnCci")) != -1) 243 | switch (c) { 244 | case 'P': case 'R': case 'S': case 'T': case 'D': case 'F': 245 | flags[(unsigned int)c] = 1; 246 | break; 247 | case 'p': case 'r': case 's': case 't': case 'd': case 'f': 248 | flags[(unsigned int)uc(c)] = -1; 249 | break; 250 | case 'X': 251 | while (*optarg) 252 | flags[(unsigned int)*optarg++] = 1; 253 | break; 254 | case 'x': 255 | while (*optarg) 256 | flags[(unsigned int)*optarg++] = -1; 257 | break; 258 | case 'N': Nflag = 1; break; 259 | case 'n': Nflag = -1; break; 260 | case 'C': Cflag = 1; break; 261 | case 'c': Cflag = -1; break; 262 | case 'i': iflag = 1; break; 263 | default: 264 | usage: 265 | fprintf(stderr, 266 | "Usage: mlist [-DFPRST] [-X str]\n" 267 | " [-dfprst] [-x str]\n" 268 | " [-N | -n | -C | -c]\n" 269 | " [-i] [dirs...]\n" 270 | ); 271 | exit(1); 272 | } 273 | 274 | int i; 275 | 276 | xpledge("stdio rpath", ""); 277 | 278 | for (i = 0, flagsum = 0, flagset = 0; (size_t)i < sizeof flags; i++) { 279 | if (flags[i] != 0) 280 | flagset++; 281 | if (flags[i] == 1) 282 | flagsum++; 283 | } 284 | 285 | if (optind == argc) { 286 | if (isatty(0)) 287 | goto usage; 288 | blaze822_loop(0, 0, listarg); 289 | } else { 290 | for (i = optind; i < argc; i++) 291 | listarg(argv[i]); 292 | } 293 | 294 | if (iflag && tdirs > 1) { 295 | if (flagset) 296 | printf("%6ld matched %6ld unseen %3ld flagged %6ld msg\n", 297 | tmatched, tunseen, tflagged, tcount); 298 | else 299 | printf("%6ld unseen %3ld flagged %6ld msg\n", 300 | tunseen, tflagged, tcount); 301 | } 302 | 303 | return 0; 304 | } 305 | -------------------------------------------------------------------------------- /mmkdir: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mmkdir DIRS... - create new maildirs 3 | 4 | umask 077 5 | 6 | r=0 7 | for dir; do 8 | mkdir -p "$dir"/tmp "$dir"/new "$dir"/cur || r=1 9 | done 10 | 11 | exit $r 12 | -------------------------------------------------------------------------------- /mquote: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # mquote MSG - format MSG as a quotation 3 | 4 | : ${from:=$(mhdr -d -h x-original-from "$1")} 5 | : ${from:=$(mhdr -d -h from "$1")} 6 | : ${from:=Someone} 7 | 8 | printf '%s wrote:\n' "$from" 9 | { mshow -R "$1" || mshow -h '' -N "$1"; } | 10 | sed -n '/^-- $/,$!p' | # strip signature 11 | sed -e :a -e '/^\n*$/{$d;N;ba' -e '}' | # strip empty lines 12 | sed 's/^/> /' # prefix with > 13 | -------------------------------------------------------------------------------- /mrep: -------------------------------------------------------------------------------- 1 | mcom -------------------------------------------------------------------------------- /mymemmem.c: -------------------------------------------------------------------------------- 1 | // taken straight from musl@c718f9fc 2 | // incooperates fix from <20170629213533.18744-1-amonakov@ispras.ru> 3 | 4 | /* 5 | Copyright © 2005-2014 Rich Felker, et al. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) 31 | { 32 | uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1]; 33 | for (h+=2, k-=2; k; k--, hw = hw<<8 | *h++) 34 | if (hw == nw) return (char *)h-2; 35 | return hw == nw ? (char *)h-2 : 0; 36 | } 37 | 38 | static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) 39 | { 40 | uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8; 41 | uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8; 42 | for (h+=3, k-=3; k; k--, hw = (hw|*h++)<<8) 43 | if (hw == nw) return (char *)h-3; 44 | return hw == nw ? (char *)h-3 : 0; 45 | } 46 | 47 | static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n) 48 | { 49 | uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3]; 50 | uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3]; 51 | for (h+=4, k-=4; k; k--, hw = hw<<8 | *h++) 52 | if (hw == nw) return (char *)h-4; 53 | return hw == nw ? (char *)h-4 : 0; 54 | } 55 | 56 | #define MAX(a,b) ((a)>(b)?(a):(b)) 57 | #define MIN(a,b) ((a)<(b)?(a):(b)) 58 | 59 | #define BITOP(a,b,op) \ 60 | ((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a)))) 61 | 62 | static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l) 63 | { 64 | size_t i, ip, jp, k, p, ms, p0, mem, mem0; 65 | size_t byteset[32 / sizeof(size_t)] = { 0 }; 66 | size_t shift[256]; 67 | 68 | /* Computing length of needle and fill shift table */ 69 | for (i=0; i n[jp+k]) { 81 | jp += k; 82 | k = 1; 83 | p = jp - ip; 84 | } else { 85 | ip = jp++; 86 | k = p = 1; 87 | } 88 | } 89 | ms = ip; 90 | p0 = p; 91 | 92 | /* And with the opposite comparison */ 93 | ip = -1; jp = 0; k = p = 1; 94 | while (jp+k ms+1) ms = ip; 110 | else p = p0; 111 | 112 | /* Periodic needle? */ 113 | if (memcmp(n, n+p, ms+1)) { 114 | mem0 = 0; 115 | p = MAX(ms, l-ms-1) + 1; 116 | } else mem0 = l-p; 117 | mem = 0; 118 | 119 | /* Search loop */ 120 | for (;;) { 121 | /* If remainder of haystack is shorter than needle, done */ 122 | if ((size_t)(z-h) < l) return 0; 123 | 124 | /* Check last byte first; advance by shift on mismatch */ 125 | if (BITOP(byteset, h[l-1], &)) { 126 | k = l-shift[h[l-1]]; 127 | if (k) { 128 | if (mem0 && mem && k < p) k = l-p; 129 | h += k; 130 | mem = 0; 131 | continue; 132 | } 133 | } else { 134 | h += l; 135 | mem = 0; 136 | continue; 137 | } 138 | 139 | /* Compare right half */ 140 | for (k=MAX(ms+1,mem); kmem && n[k-1] == h[k-1]; k--); 148 | if (k <= mem) return (char *)h; 149 | h += p; 150 | mem = mem0; 151 | } 152 | } 153 | 154 | void *mymemmem(const void *h0, size_t k, const void *n0, size_t l) 155 | { 156 | const unsigned char *h = h0, *n = n0; 157 | 158 | /* Return immediately on empty needle */ 159 | if (!l) return (void *)h; 160 | 161 | /* Return immediately when needle is longer than haystack */ 162 | if (k 27 | #include 28 | 29 | int mystrverscmp(const char *l0, const char *r0) 30 | { 31 | const unsigned char *l = (const void *)l0; 32 | const unsigned char *r = (const void *)r0; 33 | size_t i, dp, j; 34 | int z = 1; 35 | 36 | /* Find maximal matching prefix and track its maximal digit 37 | * suffix and whether those digits are all zeros. */ 38 | for (dp=i=0; l[i]==r[i]; i++) { 39 | int c = l[i]; 40 | if (!c) return 0; 41 | if (!isdigit(c)) dp=i+1, z=1; 42 | else if (c!='0') z=0; 43 | } 44 | 45 | if (l[dp]!='0' && r[dp]!='0') { 46 | /* If we're not looking at a digit sequence that began 47 | * with a zero, longest digit string is greater. */ 48 | for (j=i; isdigit(l[j]); j++) 49 | if (!isdigit(r[j])) return 1; 50 | if (isdigit(r[j])) return -1; 51 | } else if (z && dp 2 | 3 | time_t 4 | mytimegm(struct tm *tm) 5 | { 6 | int mon = tm->tm_mon + 1 - 2; /* put March first, Feb last */ 7 | long long year = tm->tm_year + 1900; 8 | 9 | if (mon <= 0 || mon >= 12) { 10 | int adj = mon / 12; 11 | mon %= 12; 12 | if (mon <= 0) { 13 | adj--; 14 | mon += 12; 15 | } 16 | year += adj; 17 | } 18 | 19 | time_t t = 0; 20 | t += tm->tm_sec; 21 | t += 60 * tm->tm_min; 22 | t += 60*60 * tm->tm_hour; 23 | t += 24*60*60 * (tm->tm_mday - 1); 24 | t += 24*60*60 * (367*mon/12); 25 | t += 24*60*60 * (year/4 - year/100 + year/400); 26 | t += 24*60*60 * (365*year - 719498L); 27 | return t; 28 | } 29 | -------------------------------------------------------------------------------- /pipeto.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | pid_t 12 | pipeto(const char *cmdline) 13 | { 14 | int pipe0[2]; // stdout -> stdin 15 | int pipe1[2]; // child errno -> parent 16 | pid_t pid; 17 | 18 | if (pipe(pipe0) < 0) 19 | return -1; 20 | if (pipe(pipe1) < 0) 21 | return -1; 22 | 23 | pid = fork(); 24 | if (pid < 0) { 25 | return -1; 26 | } else if (pid == 0) { // in child 27 | close(pipe1[0]); 28 | // close errno pipe on successful exec 29 | fcntl(pipe1[1], F_SETFD, FD_CLOEXEC); 30 | 31 | if (dup2(pipe0[0], 0) < 0) 32 | exit(111); 33 | 34 | close(pipe0[0]); 35 | close(pipe0[1]); 36 | 37 | // split cmdline, just on spaces 38 | char *argv[16]; 39 | int argc = 0; 40 | char *cp = strdup(cmdline); 41 | if (!cp) 42 | exit(111); 43 | while (argc < 16 && *cp) { 44 | argv[argc++] = cp; 45 | cp = strchr(cp, ' '); 46 | if (!cp) 47 | break; 48 | *cp++ = 0; 49 | while (*cp == ' ') 50 | cp++; 51 | } 52 | argv[argc] = 0; 53 | 54 | if (argv[0]) 55 | execvp(argv[0], argv); 56 | else 57 | errno = EINVAL; 58 | 59 | // execvp failed, write errno to parent 60 | int e = errno; 61 | (void)! write(pipe1[1], &e, sizeof e); 62 | exit(111); 63 | } else { // in parent 64 | close(pipe1[1]); 65 | 66 | int e; 67 | ssize_t n = read(pipe1[0], &e, sizeof e); 68 | if (n < 0) 69 | e = errno; 70 | close(pipe1[0]); 71 | 72 | if (n == 0) { 73 | // child executed successfully, redirect stdout to it 74 | if (dup2(pipe0[1], 1) < 0) 75 | return -1; 76 | 77 | close(pipe0[0]); 78 | close(pipe0[1]); 79 | 80 | return pid; 81 | } else { 82 | errno = e; 83 | return -1; 84 | } 85 | } 86 | 87 | // return pid; 88 | } 89 | 90 | int 91 | pipeclose(pid_t pid) 92 | { 93 | int s; 94 | 95 | fflush(0); 96 | close(1); 97 | waitpid(pid, &s, 0); 98 | 99 | return s; 100 | } 101 | -------------------------------------------------------------------------------- /rfc2045.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "blaze822.h" 8 | #include "blaze822_priv.h" 9 | 10 | // needs to be writable 11 | char textplain[] = "text/plain; charset=US-ASCII"; 12 | 13 | int 14 | blaze822_check_mime(struct message *msg) 15 | { 16 | char *v = blaze822_hdr(msg, "mime-version"); 17 | if (v && 18 | v[0] && v[0] == '1' && 19 | v[1] && v[1] == '.' && 20 | v[2] && v[2] == '0' && 21 | (!v[3] || iswsp(v[3]))) 22 | return 1; 23 | v = blaze822_hdr(msg, "content-transfer-encoding"); 24 | if (v) 25 | return 1; 26 | return 0; 27 | } 28 | 29 | int 30 | blaze822_mime_body(struct message *msg, 31 | char **cto, char **bodyo, size_t *bodyleno, char **bodychunko) 32 | { 33 | if (!msg->body || !msg->bodyend) { 34 | *bodyo = 0; 35 | *bodyleno = 0; 36 | *bodychunko = 0; 37 | return 0; 38 | } 39 | 40 | char *ct = blaze822_hdr(msg, "content-type"); 41 | char *cte = blaze822_hdr(msg, "content-transfer-encoding"); 42 | 43 | if (!ct) 44 | ct = textplain; 45 | 46 | char *s = ct; 47 | while (*s && *s != ';') { 48 | *s = lc(*s); 49 | s++; 50 | } 51 | 52 | *cto = ct; 53 | 54 | if (cte) { 55 | if (strncasecmp(cte, "quoted-printable", 16) == 0) { 56 | blaze822_decode_qp(msg->body, msg->bodyend, bodyo, bodyleno, 0); 57 | *bodychunko = *bodyo; 58 | } else if (strncasecmp(cte, "base64", 6) == 0) { 59 | blaze822_decode_b64(msg->body, msg->bodyend, bodyo, bodyleno); 60 | *bodychunko = *bodyo; 61 | } else { 62 | cte = 0; 63 | } 64 | } 65 | if (!cte) { 66 | *bodyo = msg->body; 67 | *bodyleno = msg->bodyend - msg->body; 68 | *bodychunko = 0; 69 | } 70 | 71 | return 1; 72 | } 73 | 74 | int 75 | blaze822_mime_parameter(char *s, char *name, char **starto, char **stopo) 76 | { 77 | if (!s) 78 | return 0; 79 | s = strchr(s, ';'); 80 | if (!s) 81 | return 0; 82 | s++; 83 | 84 | size_t namelen = strlen(name); 85 | 86 | while (*s) { 87 | while (iswsp(*s)) 88 | s++; 89 | if (!*s) 90 | return 0; 91 | if (strncasecmp(s, name, namelen) == 0 && s[namelen] == '=') { 92 | s += namelen + 1; 93 | break; 94 | } 95 | s = strchr(s+1, ';'); 96 | if (!s) 97 | return 0; 98 | s++; 99 | } 100 | if (!s || !*s) 101 | return 0; 102 | char *e; 103 | if (*s == '"') { 104 | s++; 105 | e = strchr(s, '"'); 106 | if (!e) 107 | return 0; 108 | } else { 109 | e = s; 110 | while (*e && !iswsp(*e) && *e != ';') 111 | e++; 112 | } 113 | 114 | *starto = s; 115 | *stopo = e; 116 | return 1; 117 | } 118 | 119 | // like mymemmem but check the match is followed by \r, \n or -. 120 | static char * 121 | mymemmemnl(const char *h0, size_t k, const char *n0, size_t l) 122 | { 123 | char *r; 124 | 125 | while (k && (r = mymemmem(h0, k, n0, l))) { 126 | if (r + l < h0 + k && // check if r[l] safe to access 127 | (r[l] == '\r' || r[l] == '\n' || r[l] == '-')) 128 | return r; 129 | else { 130 | // skip over this match 131 | k -= (r - h0) + 1; 132 | h0 = r + 1; 133 | } 134 | } 135 | 136 | return 0; 137 | } 138 | 139 | int 140 | blaze822_multipart(struct message *msg, struct message **imsg) 141 | { 142 | char *s = blaze822_hdr(msg, "content-type"); 143 | if (!s) 144 | return 0; 145 | while (*s && *s != ';') 146 | s++; 147 | if (!*s) 148 | return 0; 149 | 150 | char *boundary, *boundaryend; 151 | if (!blaze822_mime_parameter(s, "boundary", &boundary, &boundaryend)) 152 | return 0; 153 | char mboundary[256]; 154 | size_t boundarylen = boundaryend-boundary+2; 155 | 156 | if (boundarylen >= 256) 157 | return 0; 158 | mboundary[0] = '-'; 159 | mboundary[1] = '-'; 160 | memcpy(mboundary+2, boundary, boundarylen-2); 161 | mboundary[boundarylen] = 0; 162 | 163 | char *prevpart; 164 | if (*imsg) 165 | prevpart = (*imsg)->bodyend; 166 | else 167 | prevpart = msg->body; 168 | 169 | char *part = mymemmemnl(prevpart, msg->bodyend - prevpart, mboundary, boundarylen); 170 | if (!part) 171 | return 0; 172 | 173 | part += boundarylen; 174 | if (*part == '\r') 175 | part++; 176 | if (*part == '\n') 177 | part++; 178 | else if (*part == '-' && part < msg->bodyend && *(part+1) == '-') 179 | return 0; 180 | else 181 | return 0; // XXX error condition? 182 | 183 | char *nextpart = mymemmemnl(part, msg->bodyend - part, mboundary, boundarylen); 184 | if (!nextpart) 185 | nextpart = msg->bodyend; // no boundary found, take all 186 | else if (nextpart == part) // invalid empty MIME part 187 | return 0; // XXX error condition 188 | 189 | if (*(nextpart-1) == '\n') 190 | nextpart--; 191 | if (*(nextpart-1) == '\r') 192 | nextpart--; 193 | 194 | *imsg = blaze822_mem(part, nextpart-part); 195 | 196 | return 1; 197 | } 198 | 199 | blaze822_mime_action 200 | blaze822_walk_mime(struct message *msg, int depth, blaze822_mime_callback visit) 201 | { 202 | char *ct, *body, *bodychunk; 203 | size_t bodylen; 204 | 205 | blaze822_mime_action r = MIME_CONTINUE; 206 | 207 | if (depth > 64) 208 | return MIME_PRUNE; 209 | 210 | if (blaze822_mime_body(msg, &ct, &body, &bodylen, &bodychunk)) { 211 | 212 | r = visit(depth, msg, body, bodylen); 213 | 214 | if (r == MIME_CONTINUE) { 215 | if (strncmp(ct, "multipart/", 10) == 0) { 216 | struct message *imsg = 0; 217 | while (blaze822_multipart(msg, &imsg)) { 218 | r = blaze822_walk_mime(imsg, depth+1, visit); 219 | if (r == MIME_STOP) 220 | break; 221 | } 222 | } else if (strncmp(ct, "message/rfc822", 14) == 0) { 223 | struct message *imsg = blaze822_mem(body, bodylen); 224 | if (imsg) 225 | blaze822_walk_mime(imsg, depth+1, visit); 226 | } 227 | } 228 | 229 | free(bodychunk); 230 | } 231 | 232 | return r; 233 | } 234 | -------------------------------------------------------------------------------- /rfc2231.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "blaze822.h" 7 | 8 | int 9 | blaze822_mime2231_parameter(char *s, char *name, 10 | char *dst, size_t dlen, char *tgtenc) 11 | { 12 | static signed char hex[] = { 13 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 14 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 15 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 16 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, 17 | -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, 18 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, 19 | -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, 20 | -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 21 | }; 22 | 23 | int i = 0; 24 | char namenum[64]; 25 | 26 | char *srcenc = 0; 27 | 28 | char *dststart = dst; 29 | char *dstend = dst + dlen; 30 | 31 | char *sbuf, *ebuf; 32 | 33 | snprintf(namenum, sizeof namenum, "%s*", name); 34 | if (blaze822_mime_parameter(s, namenum, &sbuf, &ebuf)) { 35 | i = 100; 36 | goto found_extended; 37 | } 38 | if (blaze822_mime_parameter(s, name, &sbuf, &ebuf)) { 39 | i = 100; 40 | goto found_plain; 41 | } 42 | 43 | while (i < 100) { 44 | snprintf(namenum, sizeof namenum, "%s*%d*", name, i); 45 | if (blaze822_mime_parameter(s, namenum, &sbuf, &ebuf)) { 46 | found_extended: 47 | // decode extended 48 | if (i == 0 || i == 100) { // extended-initial-value 49 | char *encstart = sbuf; 50 | sbuf = strchr(sbuf, '\''); 51 | if (!sbuf) 52 | return 0; 53 | srcenc = strndup(encstart, sbuf - encstart); 54 | if (!srcenc) 55 | return 0; 56 | sbuf = strchr(sbuf+1, '\''); 57 | if (!sbuf) { 58 | free(srcenc); 59 | return 0; 60 | } 61 | sbuf++; 62 | } 63 | while (sbuf < ebuf && dst < dstend) { 64 | if (sbuf[0] == '%') { 65 | unsigned char c1 = sbuf[1]; 66 | unsigned char c2 = sbuf[2]; 67 | if (c1 < 127 && c2 < 127 && 68 | hex[c1] > -1 && hex[c2] > -1) { 69 | *dst++ = (hex[c1] << 4) | hex[c2]; 70 | sbuf += 3; 71 | } else { 72 | *dst++ = *sbuf++; 73 | } 74 | } else { 75 | *dst++ = *sbuf++; 76 | } 77 | } 78 | *dst = 0; 79 | } else { 80 | namenum[strlen(namenum) - 1] = 0; // strip last * 81 | if (blaze822_mime_parameter(s, namenum, &sbuf, &ebuf)) { 82 | found_plain: 83 | // copy plain 84 | if (ebuf - sbuf < dstend - dst) { 85 | memcpy(dst, sbuf, ebuf - sbuf); 86 | dst += ebuf - sbuf; 87 | } 88 | *dst = 0; 89 | } else { 90 | break; 91 | } 92 | } 93 | i++; 94 | } 95 | 96 | if (i <= 0) 97 | return 0; 98 | 99 | if (!srcenc) 100 | return 1; 101 | 102 | iconv_t ic = iconv_open(tgtenc, srcenc); 103 | free(srcenc); 104 | if (ic == (iconv_t)-1) 105 | return 1; 106 | 107 | size_t tmplen = dlen; 108 | char *tmp = malloc(tmplen); 109 | if (!tmp) 110 | return 1; 111 | char *tmpend = tmp; 112 | 113 | size_t dstlen = dst - dststart; 114 | dst = dststart; 115 | 116 | size_t r = iconv(ic, &dst, &dstlen, &tmpend, &tmplen); 117 | if (r == (size_t)-1) { 118 | free(tmp); 119 | return 1; 120 | } 121 | 122 | iconv_close(ic); 123 | 124 | // copy back 125 | memcpy(dststart, tmp, tmpend - tmp); 126 | dststart[tmpend - tmp] = 0; 127 | free(tmp); 128 | 129 | return 1; 130 | } 131 | -------------------------------------------------------------------------------- /safe_u8putstr.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "u8decode.h" 5 | 6 | void 7 | safe_u8putstr(char *s0, size_t l, int oneline, FILE *stream) 8 | { 9 | // tty-safe output of s, with relaxed utf-8 semantics: 10 | // - C0 and C1 are displayed as escape sequences 11 | // - valid utf-8 is printed as is 12 | // - rest is assumed to be latin-1, and translated into utf-8 13 | // - translate CRLF to CR 14 | 15 | unsigned char *s = (unsigned char *)s0; 16 | unsigned char *e = s + l; 17 | uint32_t c; 18 | 19 | while (s < e) { 20 | int l = u8decode((char *)s, &c); 21 | if (l == -1) { 22 | l = 1; 23 | if (*s <= 0x9fu) { 24 | // C1 25 | fputc(0xe2, stream); 26 | fputc(0x90, stream); 27 | fputc(0x80+0x1b, stream); 28 | 29 | fputc(0xe2, stream); 30 | fputc(0x90, stream); 31 | fputc(*s, stream); 32 | } else { 33 | /* invalid utf-8, assume it was latin-1 */ 34 | fputc(0xc0 | (*s >> 6), stream); 35 | fputc(0x80 | (*s & 0x3f), stream); 36 | } 37 | } else if (c < 32 && 38 | *s != ' ' && *s != '\t' && 39 | (oneline || (*s != '\n' && *s != '\r'))) { 40 | // NUL 41 | if (l == 0) 42 | l = 1; 43 | // C0 44 | fputc(0xe2, stream); 45 | fputc(0x90, stream); 46 | fputc(0x80+*s, stream); 47 | } else if (c == 127) { 48 | // DEL 49 | fputc(0xe2, stream); 50 | fputc(0x90, stream); 51 | fputc(0xa1, stream); 52 | } else if (c == '\r') { 53 | if (e - s > 1 && s[1] == '\n') 54 | s++; 55 | fputc(*s, stream); 56 | } else { 57 | fwrite(s, 1, l, stream); 58 | } 59 | s += l; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /slurp.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int 10 | slurp(char *filename, char **bufo, off_t *leno) 11 | { 12 | int fd; 13 | struct stat st; 14 | ssize_t nread = 0; 15 | ssize_t n; 16 | int r = 0; 17 | 18 | fd = open(filename, O_RDONLY); 19 | if (fd < 0) { 20 | r = errno; 21 | goto out; 22 | } 23 | if (fstat(fd, &st) < 0) { 24 | r = errno; 25 | goto out; 26 | } 27 | *bufo = malloc(st.st_size + 1); 28 | if (!*bufo) { 29 | r = ENOMEM; 30 | goto out; 31 | } 32 | 33 | do { 34 | if ((n = read(fd, *bufo + nread, st.st_size - nread)) < 0) { 35 | if (errno == EINTR) { 36 | continue; 37 | } else { 38 | r = errno; 39 | goto out; 40 | } 41 | } 42 | if (!n) 43 | break; 44 | nread += n; 45 | } while (nread < st.st_size); 46 | 47 | *leno = nread; 48 | (*bufo)[st.st_size] = 0; 49 | 50 | out: 51 | close(fd); 52 | return r; 53 | } 54 | -------------------------------------------------------------------------------- /squeeze_slash.c: -------------------------------------------------------------------------------- 1 | void 2 | squeeze_slash(char *arg) { 3 | char *s, *t; 4 | 5 | // squeeze slashes 6 | s = t = arg; 7 | while ((*s++ = *t)) 8 | while (*t++ == '/' && *t == '/') 9 | ; 10 | 11 | // remove trailing slashes 12 | s--; 13 | while (*--s == '/') 14 | *s = 0; 15 | } 16 | -------------------------------------------------------------------------------- /t/1000-mmime.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | 5 | plan 21 6 | 7 | cat <tmp 8 | References: 9 | 10 | Body 11 | EOF 12 | 13 | # https://github.com/leahneukirchen/mblaze/issues/20 14 | 15 | check 'mime -r runs' 'mmime -r tmp2' 16 | check 'no overlong lines' 'awk "{if(length(\$0)>=80)exit 1}" tmp2 21 | Subject: inclusion test 22 | 23 | #message/rfc822 $PWD/tmp 24 | EOF 25 | 26 | check 'include works' 'mmime tmp2 31 | Subject: inclusion test no filename 32 | 33 | #message/rfc822 $PWD/tmp> 34 | EOF 35 | 36 | check 'include works, overriding filename' 'mmime tmp2 40 | Subject: inclusion test with other disposition 41 | 42 | #message/rfc822#inline $PWD/tmp> 43 | EOF 44 | 45 | check 'include works, overriding filename' 'mmime tmp2 49 | Subject: message with content-type 50 | Content-Type: text/plain; format=flowed 51 | 52 | This message has format-flowed. 53 | EOF 54 | 55 | check 'content-type is respected if found in input' 'mmime -r tmp2 58 | Subject: message with content-transfer-encoding 59 | Content-Transfer-Encoding: quoted-printable 60 | 61 | This message has already encoded. f=C3=B6=C3=B6. 62 | EOF 63 | 64 | 65 | check 'content-transfer-encoding is respected if found in input' 'mmime -r tmp2 68 | Subject: message with content-type 69 | Content-Type: text/plain; format=flowed 70 | 71 | This message has format-flowed. 72 | 73 | #message/rfc822 $PWD/tmp 74 | 75 | This part too. 76 | EOF 77 | 78 | 79 | check 'content-type is respected if found in input, for multipart/mixed' 'mmime tmp2 82 | Subject: message with content-transfer-encoding 83 | Content-Transfer-Encoding: Quoted-Printable 84 | 85 | This message has already encoded. f=C3=B6=C3=B6. 86 | 87 | #message/rfc822 $PWD/tmp 88 | 89 | This part too. 90 | EOF 91 | 92 | check 'content-transfer-encoding is respected if found in input, for multipart/mixed' 'mmime tmp2 95 | From: Kerstin Krüger 96 | 97 | Body. 98 | EOF 99 | 100 | check 'non-ASCII is encoded as UTF-8' 'mmime tmp2 103 | From: "Krüger, Kerstin" 104 | 105 | Body. 106 | EOF 107 | 108 | check 'non-ASCII quoted-strings are encoded as one encoded-word' 'mmime tmp2 113 | From: "kerstin krueger"@example.com 114 | 115 | Body. 116 | EOF 117 | 118 | check 'non-encoded quoted-strings are kept correctly' 'mmime tmp2 121 | Subject: inclusion without further content 122 | 123 | #message/rfc822#inline $PWD/tmp 124 | EOF 125 | 126 | check 'no empty parts are generated after inclusion lines' '! mmime tmp2 129 | Subject: Strict mode 130 | 131 | Body with lines longer than 78 characters 132 | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 133 | EOF 134 | 135 | check 'body lines longer than 78 characters needs MIME formatting' '! mmime -c tmp2 139 | DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1710013705; 140 | s=s1; d=tuta.io; 141 | h=From:From:To:To:Subject:Subject:Content-Description:Content-ID:Content-Type:Content-Type:Content-Transfer-Encoding:Cc:Date:Date:In-Reply-To:MIME-Version:MIME-Version:Message-ID:Message-ID:Reply-To:References:Sender; bh=Jr8DQlZ7RwdJv94m7ZT/v+cv/WFsgjxpMRsHvnNfgGY=; 142 | b=NXRl0YxYtVsWrR8v7tVKnvsnCSrBqqaf2h3m8OVGlzG0OqMqGcWg7fVk6x4nTYV+ 143 | +05afZrGfIwcfFwIe/LLvT0d3/12t4+cs/FQvmEcFUN+n2buQwt5sn8f76UUlvNMrGz 144 | Xbq8HAdwhA364yWABa7DrF1EGysC8bEDJcCtSs/Wz3TL2A/MEeItEF+VijtgWUwoOwn 145 | rFKkCg5Df+IOd4gEBS/KYLbzcMB1dvqy+ut2LA2+NZpzJQPgbJzWAYieT9KYgoS+hKS 146 | 5FfknNT+hKZz18IBEWH1UWbI+CcLRR8Sr80x2DZUKq8ryC5RmV5/uAc5Up03b/KZGRU 147 | NsiBAQCx3w== 148 | EOF 149 | 150 | check 'header words longer then 78 characters do not cause empty lines (#257)' 'mmime < tmp2 | awk "NR < 5 && length == 0 { exit 1 }"' 151 | 152 | check 'header words longer then 78 characters are printed on their own line' 'mmime < tmp2 |grep "^[ ]*h=From.*Sender;$"' 153 | -------------------------------------------------------------------------------- /t/1100-mhdr.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | 5 | plan 9 6 | 7 | cat <tmp 8 | Header: foo 9 | Header2: bar 10 | Header-Three: quux 11 | Header_Four: ding 12 | 13 | Body 14 | EOF 15 | 16 | check_same 'Header' 'mhdr -h Header ./tmp' 'echo foo' 17 | check_same 'Header2' 'mhdr -h Header2 ./tmp' 'echo bar' 18 | check_same 'Header-Three' 'mhdr -h Header-Three ./tmp' 'echo quux' 19 | check_same 'Header_Four' 'mhdr -h Header_Four ./tmp' 'echo ding' 20 | 21 | check_same 'header' 'mhdr -h header ./tmp' 'echo foo' 22 | check_same 'header2' 'mhdr -h header2 ./tmp' 'echo bar' 23 | check_same 'header-Three' 'mhdr -h header-Three ./tmp' 'echo quux' 24 | check_same 'header_Four' 'mhdr -h header_Four ./tmp' 'echo ding' 25 | 26 | check 'issue 235' 'mhdr ./tmp |grep -i header_four' 27 | -------------------------------------------------------------------------------- /t/1500-maddr.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 11 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | mkdir -p "inbox/cur" 12 | 13 | cat <"inbox/cur/1:2," 14 | From: Rajwinder Kaur 15 | Obs-Test: Rajwinder Kaur <@example.org:rajwinder@example.com> 16 | Subject: namaste 17 | Date: Thu, 30 Mar 2017 15:42:05 +0200 18 | Message-Id: 19 | 20 | ! 21 | 22 | cat <"inbox/cur/2:2," 23 | From: имярек , Rajwinder Kaur 24 | Subject: Здравствуйте 25 | Date: Thu, 30 Mar 2017 15:42:10 +0200 26 | Message-Id: 27 | 28 | ! 29 | 30 | cat <"inbox/cur/3:2," 31 | From: rajwinder@example.com 32 | Subject: Здраво 33 | Date: Thu, 30 Mar 2017 15:40:32 +0200 34 | Message-Id: 35 | 36 | ! 37 | 38 | cat <"inbox/cur/4:2," 39 | From: Perico de los palotes 40 | Subject: Hola 41 | Date: Thu, 30 Mar 2017 16:20:11 +0200 42 | Message-Id: 43 | Foo: Perico de los palotes 44 | Long: heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelloooooooooooooooooo@woooooooooooooooooooooooooooooooooooooorld.com 45 | 46 | ! 47 | 48 | # from rfc2047.c 49 | cat <"inbox/cur/5:2," 50 | DecodeISO8859: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= 51 | DecodeLongISO8859: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= 52 | =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= z 53 | =?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?= 54 | DecodeUTF8: =?UTF-8?Q?z=E2=80?= =?UTF-8?Q?=99z?= 55 | 56 | ! 57 | 58 | cat <seq 59 | inbox/cur/1:2, 60 | inbox/cur/2:2, 61 | inbox/cur/3:2, 62 | inbox/cur/4:2, 63 | inbox/cur/5:2, 64 | ! 65 | 66 | export MAILSEQ=seq 67 | 68 | check_same 'from one' 'maddr 1' 'echo "Rajwinder Kaur "' 69 | check_same 'from address' 'maddr -a 1' 'echo "rajwinder@example.com"' 70 | check_same 'from one' 'maddr -h obs-test 1' 'echo "Rajwinder Kaur "' 71 | 72 | cat <expect 73 | имярек 74 | Rajwinder Kaur 75 | ! 76 | check_same 'from two' 'maddr 2' 'cat expect' 77 | 78 | check_same 'from addr only' 'maddr 3' 'echo "rajwinder@example.com"' 79 | check_test 'from name only' -eq 0 'maddr 4 | wc -l' 80 | check_same 'specific header' 'maddr -h foo 4' 'echo "Perico de los palotes "' 81 | check_same 'long addr' 'maddr -h long 4' 'echo "heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelloooooooooooooooooo@woooooooooooooooooooooooooooooooooooooorld.com"' 82 | check_same 'decode iso8859' 'maddr -h DecodeISO8859 5' 'echo "Keld Jørn Simonsen "' 83 | check_same 'decode long iso8859' 'maddr -h DecodeLongISO8859 5' 'echo "\"If you can read this you understand the example. z a b\" "' 84 | check_same 'decode utf8' 'maddr -h DecodeUTF8 5' 'echo "z’z "' 85 | 86 | ) 87 | -------------------------------------------------------------------------------- /t/1501-maddr-regress.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 33 5 | 6 | check_addr() { 7 | printf "From: %s\n" "$1" | check_test "parse $1" = "$2" "maddr -" 8 | } 9 | 10 | check_addr 'foo@example.org' 'foo@example.org' 11 | check_addr '' 'foo@example.org' 12 | check_addr 'bar ' 'bar ' 13 | check_addr 'bar quux ' 'bar quux ' 14 | check_addr 'bar quux ' 'bar quux ' 15 | check_addr ' (bar)' 'bar ' 16 | check_addr '"Real Name" ' 'Real Name ' 17 | check_addr '"Dr. Real Name" ' '"Dr. Real Name" ' 18 | check_addr '"Real Name" (show this) ' '"Real Name (show this)" ' 19 | check_addr '"Real Name" (ignore this)' '"Real Name (ignore this)" ' 20 | 21 | check_addr '(nested (comments mail@here ) heh) "yep (yap)" ' '"yep (yap)" ' 22 | 23 | check_addr 'ignore-this: ' 'foo@example.org' 24 | check_addr 'ignore-this : ' 'foo@example.org' 25 | check_addr '"foo"@example.org' 'foo@example.org' 26 | check_addr '"Barqux" "foo"@example.org' 'Barqux ' 27 | check_addr 'Space Man <"space man"@example.org>' 'Space Man <"space man"@example.org>' 28 | check_addr 'Space Man <"space man"@example.org>' 'Space Man <"space man"@example.org>' 29 | check_addr '' 'spaceman@example.org' 30 | check_addr '< spaceman@example.org >' 'spaceman@example.org' 31 | check_addr '' 'spaceman@example.org' 32 | check_addr 'space\man@example.org' 'spaceman@example.org' 33 | 34 | check_addr 'what is <"thisit"@example.org>' 'what is <"thisit"@example.org>' 35 | 36 | check_addr 'foo@example.org ' 'foo@example.org' 37 | 38 | check_addr 'foo@[::1] (ipv6)' 'ipv6 ' 39 | 40 | # invalid addresses 41 | check_addr '' 'foobar@qux.com' 42 | check_addr '"abc@def"@ghi' '' 43 | check_addr '"foo@" ' '"foo@" ' 44 | 45 | check_addr 'test."test"@example.org' 'test.test@example.org' 46 | check_addr '' 'test.test@example.org' 47 | check_addr 'test"test"@example.org' 'testtest@example.org' 48 | check_addr '' 'testtest@example.org' 49 | 50 | check_addr 'foo' 'foo ' 51 | check_addr 'xxxxxxxxx a"test"@example.org' "xxxxxxxxx " 52 | -------------------------------------------------------------------------------- /t/1700-mshow.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 2 5 | 6 | # Nested MIME where the outer boundary is a prefix of the inner boundary 7 | cat <tmp 8 | MIME-Version: 1.0 9 | Content-type: multipart/mixed; charset=iso-8859-1; 10 | boundary="_xec5AqfRYxfhARmklHx" 11 | 12 | 13 | --_xec5AqfRYxfhARmklHx 14 | Content-type: Multipart/alternative; charset=iso-8859-1; 15 | boundary="_xec5AqfRYxfhARmklHx8" 16 | 17 | 18 | --_xec5AqfRYxfhARmklHx8 19 | Content-type: text/plain; charset=iso-8859-1 20 | Content-Transfer-Encoding: 7bit 21 | Content-Disposition: inline 22 | 23 | foo 24 | --_xec5AqfRYxfhARmklHx8 25 | Content-type: text/html; charset=iso-8859-1 26 | Content-Transfer-Encoding: Quoted-printable 27 | Content-Disposition: inline 28 | 29 | bar 30 | --_xec5AqfRYxfhARmklHx8-- 31 | 32 | --_xec5AqfRYxfhARmklHx 33 | Content-Type: application/zip 34 | Content-Transfer-Encoding: Base64 35 | 36 | quux 37 | --_xec5AqfRYxfhARmklHx-- 38 | EOF 39 | 40 | check 'nested mail has 5 attachments' 'mshow -t ./tmp | wc -l | grep 6' 41 | check 'nested mail has text/html attachment' 'mshow -t ./tmp | grep text/html' 42 | -------------------------------------------------------------------------------- /t/1701-mshow-regress.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 3 5 | 6 | # Mail with \n\n and \r\n\r\n 7 | cr=$(printf '\r') 8 | cat <tmp 9 | Content-Type: multipart/form-data; boundary=------------------------55a586f81559face$cr 10 | $cr 11 | --------------------------55a586f81559face$cr 12 | Content-Disposition: form-data; name="a"; filename="foo"$cr 13 | Content-Type: application/octet-stream$cr 14 | $cr 15 | foo$cr 16 | 17 | previously there are two NL$cr 18 | $cr 19 | --------------------------55a586f81559face$cr 20 | Content-Disposition: form-data; name="a"; filename="bar"$cr 21 | Content-Type: application/octet-stream$cr 22 | $cr 23 | bar$cr 24 | $cr 25 | --------------------------55a586f81559face--$cr 26 | EOF 27 | 28 | check 'mail has 3 attachments' 'mshow -t ./tmp | wc -l | grep 4' 29 | check 'mail attachment foo has size 35' 'mshow -t ./tmp | grep size=35.*name=\"foo\"' 30 | 31 | # Mail with linebreaks in base64 quartets 32 | cat <tmp 33 | Subject: base64 34 | MIME-Version: 1.0 35 | Content-Type: multipart/mixed; boundary="----_=_2f8f1e2243b55f8618eaf0d9_=_" 36 | 37 | This is a multipart message in MIME format. 38 | 39 | ------_=_2f8f1e2243b55f8618eaf0d9_=_ 40 | Content-Disposition: attachment; filename=base64 41 | Content-Type: application/binary 42 | Content-Transfer-Encoding: base64 43 | 44 | dGhp 45 | cyBpc 46 | yBzb21 47 | lIGJhc2 48 | U2NAo= 49 | 50 | ------_=_2f8f1e2243b55f8618eaf0d9_=_-- 51 | EOF 52 | 53 | check 'mail decodes correctly' 'mshow -O ./tmp 2 | grep -q "this is some base64"' 54 | -------------------------------------------------------------------------------- /t/1702-mshow-attachments.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 2 5 | 6 | # Different naming scenarios for named parts 7 | cat <tmp 8 | Content-Type: multipart/mixed; boundary=----_NextPart_000_00DE_01D6A2E8.A7446C80 9 | 10 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 11 | 12 | no header, part is not attachment 13 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 14 | Content-Type: text/plain; charset=UTF-8 15 | 16 | CT w/o name, part is not attachment 17 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 18 | Content-Type: text/plain; charset=UTF-8; name="ctn.txt" 19 | 20 | CT with name, part is attachment 21 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 22 | Content-Disposition: attachment 23 | 24 | CD w/o filename, part is not attachment 25 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 26 | Content-Type: text/plain; charset=UTF-8 27 | Content-Disposition: attachment 28 | 29 | CD w/o filename, CT w/o name, part is not attachment 30 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 31 | Content-Type: text/plain; charset=UTF-8; name="cd_ctn.txt" 32 | Content-Disposition: attachment 33 | 34 | CD w/o filename, CT with name, part is attachment 35 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 36 | Content-Disposition: attachment; filename="cdf.txt" 37 | 38 | CD with filename, part is attachment 39 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 40 | Content-Type: text/plain; charset=UTF-8; name="cdf_ct.txt" 41 | Content-Disposition: attachment 42 | 43 | CD with filename, CT w/o name, part is attachment 44 | ------_NextPart_000_00DE_01D6A2E8.A7446C80 45 | Content-Type: text/plain; charset=UTF-8; name="cdf_ctn.txt" 46 | Content-Disposition: attachment; filename="cdf_ctn.txt" 47 | 48 | CD with filename, CT with name, part is attachment 49 | ------_NextPart_000_00DE_01D6A2E8.A7446C80-- 50 | EOF 51 | 52 | check 'mail has 10 parts' 'mshow -t ./tmp | wc -l | grep 11' 53 | check 'mail has 5 named parts/attachments' 'mshow -t ./tmp | grep .*name=\".*\" | wc -l | grep 5' 54 | -------------------------------------------------------------------------------- /t/1800-mexport.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 2 5 | 6 | cat <tmp.1 7 | Subject: message 1 8 | 9 | This is message 1. 10 | EOF 11 | 12 | cat <tmp.2 13 | Subject: message 2 14 | 15 | This is message 2. It has a trailing empty line. 16 | 17 | EOF 18 | 19 | printf >tmp.3 'Subject: message 3 20 | 21 | This is message 3. It has a no trailing newline, oops.' 22 | 23 | mexport ./tmp.1 ./tmp.2 ./tmp.3 >./tmp.mbox 24 | 25 | check 'generated mbox has 16 lines' 'cat ./tmp.mbox | wc -l | grep 16' 26 | check 'generated mbox has 7 empty lines' 'grep -c "^$" ./tmp.mbox | grep 7' 27 | -------------------------------------------------------------------------------- /t/1900-mdeliver.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 9 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | cd test.dir 9 | 10 | mmkdir inbox 11 | 12 | cat <tmp.1 13 | Subject: message 1 14 | 15 | This is message 1. 16 | EOF 17 | 18 | cat <tmp.2 19 | Subject: message 2 20 | 21 | This is message 2. It has a trailing empty line. 22 | 23 | EOF 24 | 25 | printf >tmp.3 'Subject: message 3 26 | 27 | This is message 3. It has a no trailing newline, oops.' 28 | 29 | cat <tmp.4 30 | Subject: message 4 31 | 32 | 33 | 34 | This is message 4. It has multiple trailing empty lines. 35 | 36 | 37 | EOF 38 | 39 | mexport ./tmp.1 | mdeliver -M inbox/ 40 | check 'message 1 is delivered verbatim via mbox' 'cmp ./tmp.1 ./inbox/new/*:2,' 41 | rm -f ./inbox/new/* 42 | 43 | mexport ./tmp.2 | mdeliver -M inbox/ 44 | check 'message 2 is delivered verbatim via mbox' 'cmp ./tmp.2 ./inbox/new/*:2,' 45 | rm -f ./inbox/new/* 46 | 47 | mexport ./tmp.3 | mdeliver -M inbox/ 48 | check 'message 3 gets a newline via mbox' 'awk 1 ./tmp.3 | cmp - ./inbox/new/*:2,' 49 | rm -f ./inbox/new/* 50 | 51 | mexport ./tmp.4 | mdeliver -M inbox/ 52 | check 'message 4 gets delivered verbatim via mbox' 'cmp ./tmp.4 ./inbox/new/*:2,' 53 | rm -f ./inbox/new/* 54 | 55 | mdeliver inbox/ <./tmp.1 56 | check 'message 1 is delivered verbatim via stdin' 'cmp ./tmp.1 ./inbox/new/*:2,' 57 | rm -f ./inbox/new/* 58 | 59 | mdeliver inbox/ <./tmp.2 60 | check 'message 2 is delivered verbatim via stdin' 'cmp ./tmp.2 ./inbox/new/*:2,' 61 | rm -f ./inbox/new/* 62 | 63 | mdeliver inbox/ <./tmp.3 64 | check 'message 3 gets a newline via stdin' 'cmp ./tmp.3 ./inbox/new/*:2,' 65 | rm -f ./inbox/new/* 66 | 67 | mdeliver inbox/ <./tmp.4 68 | check 'message 4 is delivered verbatim via stdin' 'cmp ./tmp.4 ./inbox/new/*:2,' 69 | rm -f ./inbox/new/* 70 | 71 | 72 | cat <tmp.mbox 73 | From nobody Thu Jan 1 00:59:59 1970 74 | Subject: message 1 75 | 76 | This is message 1. 77 | From nobody Thu Jan 1 00:59:59 1970 78 | Subject: message 2 79 | 80 | This is message 2. 81 | 82 | EOF 83 | 84 | mdeliver -M inbox/ <./tmp.mbox 85 | check 'mdeliver -M is tolerant with missing empty lines' 'ls inbox/new | wc -l | grep 2' 86 | -------------------------------------------------------------------------------- /t/2000-mpick.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 29 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | mkdir -p inbox/cur 12 | touch "inbox/cur/1:2,S" 13 | touch "inbox/cur/2:2,ST" 14 | touch "inbox/cur/3:2,SRT" 15 | touch "inbox/cur/4:2,SRFT" 16 | touch "inbox/cur/5:2,T" 17 | touch "inbox/cur/6:2,SRF" 18 | touch "inbox/cur/7:2,SR" 19 | touch "inbox/cur/8:2,SF" 20 | touch "inbox/cur/9:2," 21 | 22 | check_same 'flag trashed' 'mlist inbox | mpick -t trashed' 'mlist -T inbox' 23 | check_same 'flag not trashed' 'mlist inbox | mpick -t !trashed' 'mlist -t inbox' 24 | check_same 'flag seen' 'mlist inbox | mpick -t seen' 'mlist -S inbox' 25 | check_same 'flag not seen' 'mlist inbox | mpick -t !seen' 'mlist -s inbox' 26 | check_same 'flag seen and trashed' 'mlist inbox | mpick -t seen -t trashed' 'mlist -ST inbox' 27 | check_same 'flag seen and not trashed' 'mlist inbox | mpick -t seen -t !trashed' 'mlist -St inbox' 28 | check_same 'flag replied' 'mlist inbox | mpick -t replied' 'mlist -R inbox' 29 | check_same 'flag forwarded' 'mlist inbox | mpick -t passed' 'mlist -P inbox' 30 | 31 | 32 | cat <"inbox/cur/1:2,S" 33 | From: Peter Example 34 | Subject: Hey whats up? 35 | Date: Thu, 30 Mar 2017 15:41:17 +0200 36 | Message-Id: 37 | 38 | Greetings 39 | ! 40 | 41 | cat <"inbox/cur/9:2," 42 | From: Peter Example 43 | Subject: wow nice subject 44 | Date: Thu, 30 Mar 2017 15:42:00 +0200 45 | Message-Id: 46 | 47 | shit happens 48 | ! 49 | 50 | cat <"inbox/cur/5:2,T" 51 | From: Obvious spam 52 | Subject: look at this awesome pdf 53 | Date: Thu, 30 Mar 2017 15:42:05 +0200 54 | Message-Id: 55 | Foo: bar 56 | 57 | Check my resume! 58 | 59 | Greetings 60 | 61 | #application/pdf ../../mshow 62 | ! 63 | 64 | cat <shebang 65 | #!$(command -v mpick) -F 66 | from.addr == "peter@example.org" && from.disp == "Peter Example" 67 | ! 68 | chmod +x shebang 69 | 70 | check 'search subject' 'mlist inbox | mpick -t "subject =~~ \"wow\"" | grep -q inbox/cur/9:2,' 71 | check_test 'search addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l' 72 | check_test 'search name' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l' 73 | check_test 'search spam' -eq 1 'mlist inbox | mpick -t "trashed && subject =~ \"pdf\"" | wc -l' 74 | check_test 'any header' -eq 1 'mlist inbox | mpick -t "\"Foo\" =~~ \"bar\"" | wc -l' 75 | check_test 'addr decode addr' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\"" | wc -l' 76 | check_test 'addr decode disp' -eq 2 'mlist inbox | mpick -t "from.disp == \"Peter Example\"" | wc -l' 77 | check_test 'shebang' -eq 2 'mlist inbox | ./shebang | wc -l' 78 | check_test 'ternary' -eq 2 'mlist inbox | mpick -t "from.addr == \"peter@example.org\" ? print : skip" | wc -l' 79 | 80 | check_same 'pipe command' 'mlist inbox | mpick -t "print |\"cat -n\" && skip"' 'mlist inbox | cat -n' 81 | check_same 'create file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox' 82 | check_same 'overwrite file' 'mlist inbox | mpick -t "print >\"foo\" && skip" && cat foo' 'mlist inbox' 83 | check_same 'append file' 'mlist inbox | mpick -t "print >>\"foo\" && skip" && cat foo' 'mlist inbox && mlist inbox' 84 | 85 | check_same 'unknown ident' 'mlist inbox | mpick -t "let x = x in x" 2>&1' \ 86 | "echo \"mpick: parse error: argv:1:9: unknown expression at 'x in x'\"" 87 | 88 | cat <expr 89 | let foo = from.addr == "peter@example.org" 90 | let bar = from.disp == "Peter Example" 91 | # random comment 92 | in 93 | foo && bar # another comment 94 | ! 95 | check_test 'let expression' -eq 2 'mlist inbox | mpick -F ./expr | wc -l' 96 | 97 | cat <expr 98 | let foo = from.addr == "peter@example.org" 99 | let bar = from.disp == "Peter Example" 100 | # random comment 101 | in 102 | foo && foo 103 | ! 104 | check_test 'let expression double free' -eq 2 'mlist inbox | mpick -F ./expr | wc -l' 105 | 106 | cat <expr 107 | let foo = 108 | let bar = from.disp == "Peter Example" 109 | in 110 | bar && from.addr == "peter@example.org" 111 | in 112 | foo 113 | ! 114 | check_test 'let expression nested' -eq 2 'mlist inbox | mpick -F ./expr | wc -l' 115 | 116 | cat <expr 117 | let foo = from.addr == "peter@example.org" 118 | let bar = foo && subject =~ "wow" 119 | in 120 | bar 121 | ! 122 | check_test 'let scoping' -eq 1 'mlist inbox | mpick -F ./expr | wc -l' 123 | 124 | cat <expr 125 | let foo = from.addr == "peter@example.org" 126 | let bar = from.disp == "Peter Example" 127 | in 128 | foo |"sed ""s/^/1:&/""" && bar |"sed ""s/^/2:&/""" && skip 129 | ! 130 | check_test 'multi redir' -eq 4 'mlist inbox | mpick -F ./expr | wc -l' 131 | check_test 'multi redir prefixes' -eq 2 'mlist inbox | mpick -F ./expr | cut -d: -f1 | sort -u | wc -l' 132 | 133 | ) 134 | 135 | check 'environment variable' 'mlist inbox | MSGID="" mpick -t "\"message-id\" == \$MSGID"' 136 | -------------------------------------------------------------------------------- /t/3000-magrep.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 10 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | mkdir -p "inbox/cur" 12 | 13 | cat <"inbox/cur/1:2," 14 | From: Piet Pompies 15 | Subject: wow nice subject 16 | Date: Thu, 30 Mar 2017 15:42:05 +0200 17 | Message-Id: 18 | 19 | shit happens 20 | ! 21 | 22 | cat <"inbox/cur/2:2," 23 | From: Piet Pompies 24 | Subject: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 25 | Date: Thu, 30 Mar 2017 15:42:05 +0200 26 | Message-Id: 27 | 28 | Greetings 29 | ! 30 | 31 | cat <"inbox/cur/3:2," 32 | From: Piet Pompies 33 | Subject: 1 multi subject one 34 | Subject: 2 multi subject two 35 | Subject: 3 multi subject three 36 | Date: Thu, 30 Mar 2017 15:42:05 +0200 37 | Message-Id: 38 | 39 | ! 40 | 41 | cat <"inbox/cur/4:2," 42 | To: "John Doe" , 43 | "John Doe" , 44 | "John Doe" , 45 | "John Doe" , 46 | "John Doe" , 47 | "John Doe" , 48 | "John Doe" , 49 | "John Doe" , 50 | "John Doe" , 51 | "John Doe" , 52 | "John Doe" , 53 | "John Doe" , 54 | "John Doe" , 55 | "John Doe" , 56 | "John Doe" , 57 | "John Doe" , 58 | "John Doe" , 59 | "John Doe" , 60 | "John Doe" , 61 | "John Doe" , 62 | "John Doe" , 63 | "John Doe" , 64 | "John Doe" , 65 | "John Doe" , 66 | "John Doe" , 67 | "John Doe" , 68 | "John Doe" , 69 | "John Doe" , 70 | "John Doe" , 71 | "John Doe" , 72 | "John Doe" , 73 | "John Doe" , 74 | "John Doe" , 75 | "John Doe" , 76 | "John Doe" , 77 | "John Doe" , 78 | "John Doe" , 79 | "John Doe" , 80 | "John Doe" , 81 | "John Doe" , 82 | "John Doe" , 83 | "John Doe" , 84 | "John Doe" , 85 | "John Doe" , 86 | "John Doe" , 87 | "John Doe" , 88 | "John Doe" , 89 | "John Doe" , 90 | "John Doe" , 91 | "John Doe" , 92 | "John Doe" , 93 | "John Doe" , 94 | "John Doe" , 95 | "John Doe" , 96 | "John Doe" , 97 | "John Doe" , 98 | "John Doe" , 99 | "John Doe" , 100 | "John Doe" , 101 | "John Doe" , 102 | "John Doe" , 103 | "John Doe" , 104 | "John Doe" , 105 | "John Doe" , 106 | "John Doe" , 107 | "John Doe" , 108 | "John Doe" , 109 | "John Doe" , 110 | "John Doe" , 111 | "John Doe" , 112 | "John Doe" , 113 | "John Doe" , 114 | "John Doe" , 115 | "John Doe" , 116 | "John Doe" , 117 | "John Doe" , 118 | "John Doe" , 119 | "John Doe" , 120 | "John Doe" , 121 | "John Doe" , 122 | "John Doe" , 123 | "John Doe" , 124 | "John Doe" , 125 | "John Doe" , 126 | "John Doe" , 127 | "John Doe" , 128 | "John Doe" , 129 | "John Doe" , 130 | "John Doe" , 131 | "John Doe" , 132 | "John Doe" , 133 | "John Doe" , 134 | "John Doe" , 135 | "John Doe" , 136 | "John Doe" , 137 | "John Doe" , 138 | "John Doe" , 139 | "John Doe" , 140 | "John Doe" , 141 | "John Doe" , 142 | "John Doe" , 143 | "John Doe" , 144 | "John Doe" , 145 | "John Doe" , 146 | "John Doe" , 147 | "John Doe" , 148 | "John Doe" , 149 | "John Doe" , 150 | "John Doe" , 151 | "John Doe" , 152 | "John Doe" , 153 | "John Doe" , 154 | "John Doe" , 155 | "John Doe" , 156 | "John Doe" , 157 | "John Doe" , 158 | "John Doe" , 159 | "John Doe" , 160 | "John Doe" , 161 | "John Doe" , 162 | "John Doe" , 163 | "John Doe" 164 | ! 165 | 166 | cat <seq 167 | inbox/cur/1:2, 168 | inbox/cur/2:2, 169 | inbox/cur/3:2, 170 | inbox/cur/4:2, 171 | ! 172 | 173 | export MAILSEQ=seq 174 | 175 | check_test 'subject' -eq 1 'magrep subject:nice : | wc -l' 176 | check_test 'ignorecase' -eq 1 'magrep -i subject:NICE : | wc -l' 177 | check_test 'invert' -eq 2 'magrep -v subject:nice : | wc -l' 178 | check_test 'max matches' -eq 2 'magrep -m 2 from:Piet : | wc -l' 179 | check_test 'long subject' -eq 1 'magrep subject:aliqua : | wc -l' 180 | check_test 'decode large rfc2047 header' -eq 1 'magrep -d to:John : | wc -l' 181 | 182 | echo 'inbox/cur/1:2,: subject: wow nice subject' >expect 183 | check_same 'print' 'magrep -p subject:nice :' 'cat expect' 184 | 185 | echo 'inbox/cur/1:2,: subject: nice' >expect 186 | check_same 'print match' 'magrep -po subject:nice :' 'cat expect' 187 | 188 | echo 'nice' >expect 189 | check_same 'print match only' 'magrep -o subject:nice :' 'cat expect' 190 | 191 | echo 'inbox/cur/3:2,' >expect 192 | check_same 'multiple subjects' 'magrep subject:multi :' 'cat expect' 193 | 194 | ) 195 | -------------------------------------------------------------------------------- /t/4000-msed.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 6 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | mkdir -p "inbox/cur" 12 | 13 | cat <"inbox/cur/1:2," 14 | From: Rajwinder Kaur 15 | Subject: Hello 16 | Date: Thu, 30 Mar 2017 15:42:05 +0200 17 | Message-Id: 18 | 19 | body 20 | ! 21 | 22 | cat <seq 23 | inbox/cur/1:2, 24 | ! 25 | 26 | export MAILSEQ=seq 27 | 28 | check 'append new' 'msed "/foobar/a/value/" 1 | grep "Foobar: value"' 29 | check 'append existing' 'msed "/subject/a/world/" 1 | grep -v "world"' 30 | check_test 'append multiple' -eq 2 'msed "/foo/a/catch/;/bar/a/catch/" 1 | grep -c catch' 31 | check 'change' 'msed "/subject/c/world/" 1 | grep "Subject: world"' 32 | check 'delete' 'msed "/message-id/d" 1 | grep -v "Message-Id"' 33 | check 'substitute' 'msed "/subject/s/\(Hello\)/\1 World/" 1 | grep "^Subject: Hello World$"' 34 | 35 | ) 36 | -------------------------------------------------------------------------------- /t/4500-mflow.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 12 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | export PIPE_CONTENTTYPE='text/plain; format=flowed' 12 | export COLUMNS=80 13 | 14 | cat <a 15 | this 16 | is 17 | flowed. 18 | ! 19 | cat <b 20 | this is flowed. 21 | ! 22 | check 'simple reflow' 'mflow a 25 | this 26 | is 27 | two spaces. 28 | ! 29 | cat <b 30 | this is two spaces. 31 | ! 32 | check 'simple space stuffing' 'mflow a 35 | this 36 | is 37 | flowed. 38 | this is fixed. 39 | ! 40 | cat <b 41 | this is flowed. 42 | this is fixed. 43 | ! 44 | check 'simple fixed' 'mflow a 47 | > this 48 | > is 49 | > quoted. 50 | this 51 | is 52 | unquoted. 53 | ! 54 | cat <b 55 | > this is quoted. 56 | this is unquoted. 57 | ! 58 | check 'simple quoted' 'mflow a 64 | > this 65 | > is 66 | > delsp. 67 | 68 | > double 69 | > spaced 70 | ! 71 | cat <b 72 | > thisisdelsp. 73 | 74 | > double spaced 75 | ! 76 | check 'simple delsp' 'mflow a 80 | this 81 | is 82 | way more than eighty chars which is the terminal width to flow. 83 | this 84 | is 85 | way more than eighty chars which is the terminal width to flow. 86 | ! 87 | cat <b 88 | this is way more than eighty chars which is the terminal width to flow. this is 89 | way more than eighty chars which is the terminal width to flow. 90 | ! 91 | check 'simple wrap' 'mflow a 94 | this 95 | is 96 | way more than eighty chars which is the terminal width to flow. 97 | averylongwordcomeshere. 98 | this 99 | is 100 | way more than eighty chars which is the terminal width to flow. 101 | ! 102 | cat <b 103 | this is way more than eighty chars which is the terminal width to flow. 104 | averylongwordcomeshere. this is way more than eighty chars which is the 105 | terminal width to flow. 106 | ! 107 | check 'more complex wrap' 'mflow a 110 | foo 111 | bar. 112 | 113 | quux. 114 | ! 115 | cat <b 116 | foo bar. 117 | 118 | quux. 119 | ! 120 | check 'space before empty line' 'mflow a 123 | Aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee 124 | aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee 125 | aaaaa bbbbb ccccc 126 | ffffff gggggg hhhhhh iiiiii. 127 | ! 128 | cat <b 129 | Aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc 130 | ddddd eeeee aaaaa bbbbb ccccc ddddd eeeee aaaaa bbbbb ccccc ffffff gggggg 131 | hhhhhh iiiiii. 132 | ! 133 | check 'fixed lines are wrapped too' 'mflow a 136 | some 137 | wrapped. 138 | -- 139 | signature 140 | ! 141 | cat <b 142 | some wrapped. 143 | -- 144 | signature 145 | ! 146 | check 'passes usenet signature marker as is' 'mflow a 149 | some regular text being force wrapped because the line is way too long oh no who writes so long lines. 150 | ! 151 | cat <b 152 | some regular text being force wrapped because the line is way too long oh no 153 | who writes so long lines. 154 | ! 155 | check 'force wrapping' 'mflow -f a 158 | > some regular text being force wrapped because the line is way too long oh no who writes so long lines. 159 | ! 160 | cat <b 161 | > some regular text being force wrapped because the line is way too long oh no 162 | > who writes so long lines. 163 | ! 164 | check 'force wrapping of quoted text' 'mflow -f "inbox/cur/1:2," 14 | From: Rajwinder Kaur 15 | Subject: Hello 16 | Date: Thu, 30 Mar 2017 15:42:05 +0200 17 | Message-Id: 18 | 19 | body 20 | ! 21 | 22 | cat <seq 23 | inbox/cur/1:2, 24 | inbox/cur/1:2, 25 | ! 26 | 27 | export MAILSEQ=seq 28 | 29 | check_same 'ISO date' 'TZ=utc mscan -f "%16D" 1' 'echo "2017-03-30 13:42"' 30 | check_same 'from name' 'mscan -f "%f" 1' 'echo "Rajwinder Kaur"' 31 | check_test 'multiple mmsg' -eq 2 'mscan 1 1 | wc -l' 32 | 33 | ) 34 | -------------------------------------------------------------------------------- /t/6000-msort.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 5 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | mkdir -p "inbox/cur" 12 | 13 | cat <"inbox/cur/1:2," 14 | From: Rajwinder Kaur 15 | Subject: namaste 16 | Date: Thu, 30 Mar 2017 15:42:05 +0200 17 | Message-Id: 18 | 19 | ! 20 | 21 | cat <"inbox/cur/2:2," 22 | From: имярек <имярек@example.com> 23 | Subject: Здравствуйте 24 | Date: Thu, 30 Mar 2017 15:42:10 +0200 25 | Message-Id: 26 | 27 | ! 28 | 29 | cat <"inbox/cur/3:2," 30 | From: Pera Perić 31 | Subject: Здраво 32 | Date: Thu, 30 Mar 2017 15:40:32 +0200 33 | Message-Id: 34 | 35 | ! 36 | 37 | cat <"inbox/cur/4:2," 38 | From: Perico de los palotes 39 | Subject: Hola 40 | Date: Thu, 30 Mar 2017 16:20:11 +0200 41 | Message-Id: 42 | 43 | ! 44 | 45 | cat <seq 46 | inbox/cur/1:2, 47 | inbox/cur/2:2, 48 | inbox/cur/3:2, 49 | inbox/cur/4:2, 50 | ! 51 | 52 | export MAILSEQ=seq 53 | 54 | check_same 'filename' 'msort -F :' 'cat seq' 55 | 56 | cat <expect 57 | inbox/cur/3:2, 58 | inbox/cur/1:2, 59 | inbox/cur/2:2, 60 | inbox/cur/4:2, 61 | ! 62 | check_same 'date' 'msort -d :' 'cat expect' 63 | 64 | cat <expect 65 | inbox/cur/4:2, 66 | inbox/cur/2:2, 67 | inbox/cur/1:2, 68 | inbox/cur/3:2, 69 | ! 70 | check_same 'reverse date' 'msort -dr :' 'cat expect' 71 | 72 | cat <expect 73 | inbox/cur/2:2, 74 | inbox/cur/3:2, 75 | inbox/cur/4:2, 76 | inbox/cur/1:2, 77 | ! 78 | check_same 'from' 'msort -f :' 'cat expect' 79 | 80 | cat <expect 81 | inbox/cur/3:2, 82 | inbox/cur/2:2, 83 | inbox/cur/4:2, 84 | inbox/cur/1:2, 85 | ! 86 | check_same 'subject' 'msort -s :' 'cat expect' 87 | 88 | ) 89 | -------------------------------------------------------------------------------- /t/7000-mseq.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 11 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | cat <seq 12 | inbox/cur/1:2, 13 | inbox/cur/2:2, 14 | inbox/cur/3:2, 15 | inbox/cur/4:2, 16 | inbox/cur/5_1:2, 17 | inbox/cur/6_2:2, 18 | inbox/cur/7_3:2, 19 | inbox/cur/8_4:2, 20 | inbox/cur/9:2, 21 | inbox/cur/10:2, 22 | ! 23 | 24 | export MAILCUR=cur MAILSEQ=seq 25 | 26 | check 'set current' 'mseq -C 1 && mseq . | grep "inbox/cur/1:2,"' 27 | check 'set next' 'mseq -C + && mseq . | grep "inbox/cur/2:2,"' 28 | check 'set prev' 'mseq -C .- && mseq . | grep "inbox/cur/1:2,"' 29 | check 'last' 'mseq "$" | grep "inbox/cur/10:2,"' 30 | check_test 'whole thread' -eq 4 'mseq 6= | wc -l' 31 | check_test 'subthread' -eq 2 'mseq 7_ | wc -l' 32 | check 'parent' 'mseq 6^ | grep "inbox/cur/5_1:2,"' 33 | check_test 'range' -eq 3 'mseq 1:3 | wc -l' 34 | check_same 'multiple mmsg' 'mseq 1 2' 'printf "inbox/cur/1:2,\ninbox/cur/2:2,\n"' 35 | 36 | cat <seq 37 | inbox/cur/1:2, 38 | inbox/cur/2:2, 39 | inbox/cur/3:2, 40 | ! 41 | 42 | check_test 'whole thread at the end' -eq 3 'mseq 2= | wc -l' 43 | check_test 'subthread at the end' -eq 2 'mseq 2_ | wc -l' 44 | 45 | ) 46 | -------------------------------------------------------------------------------- /t/8000-mflag.t: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | cd ${0%/*} 3 | . ./lib.sh 4 | plan 16 5 | 6 | rm -rf test.dir 7 | mkdir test.dir 8 | ( 9 | cd test.dir 10 | 11 | cat <seq 12 | inbox/cur/1:2, 13 | inbox/cur/2:2, 14 | ! 15 | 16 | mkdir -p inbox/cur 17 | while read f; do touch "$f"; done /dev/null 1>&2; then 9 | printf 'ok - %s\n' "$msg" 10 | else 11 | printf 'not ok - %s\n' "$msg" 12 | false 13 | fi 14 | true 15 | } 16 | 17 | check_test() { 18 | msg=$1; op=$2; test=$3; shift 3 19 | if [ "$(eval "$@" 2>/dev/null)" "$op" "$test" ]; then 20 | printf 'ok - %s\n' "$msg" 21 | else 22 | printf 'not ok - %s\n' "$msg" 23 | false 24 | fi 25 | true 26 | } 27 | 28 | check_same() { 29 | msg=$1 30 | shift 31 | eval "$1 || true" 2>/dev/null 1>&2 >out1 || true 32 | eval "$2 || true" 2>/dev/null 1>&2 >out2 || true 33 | diff -u out1 out2 || true 34 | check "$msg" cmp out1 out2 35 | } 36 | -------------------------------------------------------------------------------- /u8decode.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Decode one UTF-8 codepoint into cp, return number of bytes to next one. 4 | // On invalid UTF-8, return -1, and do not change cp. 5 | // Invalid codepoints are not checked. 6 | // 7 | // This code is meant to be inlined, if cp is unused it can be optimized away. 8 | static int 9 | u8decode(const char *cs, uint32_t *cp) 10 | { 11 | const uint8_t *s = (uint8_t *)cs; 12 | 13 | if (*s == 0) { *cp = 0; return 0; } 14 | if (*s < 0x80) { *cp = *s; return 1; } 15 | if (*s < 0xc2) { return -1; } //cont+overlong 16 | if (*s < 0xe0) { *cp = *s & 0x1f; goto u2; } 17 | if (*s < 0xf0) { 18 | if (*s == 0xe0 && (s[1] & 0xe0) == 0x80) return -1; //overlong 19 | if (*s == 0xed && (s[1] & 0xe0) == 0xa0) return -1; //surrogate 20 | *cp = *s & 0x0f; goto u3; 21 | } 22 | if (*s < 0xf5) { 23 | if (*s == 0xf0 && (s[1] & 0xf0) == 0x80) return -1; //overlong 24 | if (*s == 0xf4 && (s[1] > 0x8f)) return -1; //too high 25 | *cp = *s & 0x07; goto u4; 26 | } 27 | return -1; 28 | 29 | u4: if ((*++s & 0xc0) != 0x80) return -1; *cp = (*cp << 6) | (*s & 0x3f); 30 | u3: if ((*++s & 0xc0) != 0x80) return -1; *cp = (*cp << 6) | (*s & 0x3f); 31 | u2: if ((*++s & 0xc0) != 0x80) return -1; *cp = (*cp << 6) | (*s & 0x3f); 32 | return s - (uint8_t *)cs + 1; 33 | } 34 | -------------------------------------------------------------------------------- /xpledge.h: -------------------------------------------------------------------------------- 1 | #ifndef PLEDGE_H 2 | #define PLEDGE_H 3 | 4 | #ifdef __OpenBSD__ 5 | 6 | #ifndef _BSD_SOURCE 7 | #define _BSD_SOURCE 8 | #endif 9 | 10 | #include 11 | #include 12 | 13 | static void 14 | xpledge(const char *promises, const char *execpromises) 15 | { 16 | if (pledge(promises, execpromises) == -1) 17 | err(1, "pledge"); 18 | } 19 | 20 | #else 21 | 22 | #define xpledge(promises, execpromises) do { } while(0) 23 | 24 | #endif /* __OpenBSD__ */ 25 | 26 | #endif /* PLEDGE_H */ 27 | --------------------------------------------------------------------------------