--- /dev/null
+/*
+
+foot_filter.c
+
+(C) 2010 Ben Schmidt
+
+This Source Code Form is subject to the terms of the Mozilla Public License
+Version 2.0. If a copy of the MPL was not distributed with this file, You can
+obtain one at http://mozilla.org/MPL/2.0/.
+
+*/
+
+// Check out the -V option; it outputs this and more
+#define FOOT_FILTER_VERSION "foot_filter version 1.2, (C) 2010 Ben Schmidt"
+
+static const char * USAGE="\n\
+usage: foot_filter [-p plain_footer_file] [-h html_footer_file]\n\
+ [{-P|-H} mime_footer_file] [-s]\n\
+ foot_filter -V\n\
+\n\
+plain_footer_file, if present, will be appended to mails with plain text\n\
+sections only. Similarly, html_footer_file. If mime_footer_file (either\n\
+plain, -P, or HTML, -H) is given, it will be used when a mail with\n\
+alternative formats is encountered, or if the footer for the relevant\n\
+type of mail is not present; a new MIME section will be added.\n\
+\n\
+-s turns on smart mode which endeavours to remove included/quoted copies of\n\
+the (or a similar) footer by surrounding the footer with patterns it later\n\
+recognises. It also endeavours to strip 'padding' surrounding the old\n\
+footers to make things as clean as possible. This includes whitespace\n\
+(including ' ' and '<br>'), '>' quoting characters, various pairs of\n\
+HTML tags (p, blockquote, div, span, font; it's naive, it doesn't check\n\
+tags in between are balanced at all, so in '<p>prefix</p><p>suffix</p>' the\n\
+first and last tags are paired), and even horizontal rules when inside\n\
+paired tags (e.g. use '<div><hr/>footer</div>'). If the smart strings are\n\
+found in the footer, they won't be added by the program, so you have the\n\
+necessary control to do this.\n\
+\n\
+New footers are added prior to trailing whitespace and a few closing html\n\
+tags (body, html) as well. You almost certainly want to begin your footer\n\
+with an empty line because of this.\n\
+\n\
+Since these alterations, by their very nature, break signed mail,\n\
+signatures are removed while processing. To keep some value from signatures,\n\
+have the MTA verify them and add a header (or even supply an alternative\n\
+footer to this program), and resign them to authenticate they came from the\n\
+mailing list directly after the signature verification was done and recorded.\n\
+Or don't use these kinds of transformations at all.\n\
+\n\
+-V shows the version and exits.\n\
+\n\
+Program is running now. Send EOF or interrupt to stop it. To avoid this usage\n\
+message if wanting to run without arguments, use '--' as an argument.\n\
+\n";
+
+/*
+
+This is a fairly simple program not expecting much extension. As such, some
+liberties have been taken and some fun has been had by the author. Correctness
+has been prioritised in design, but speed and efficiency have been taken into
+consideration and prioritised above readability and modularity and other such
+generally recommended programming practices. If making changes, great care
+should be taken to understand how and where (everywhere) globals are used
+before making them. Don't try to modify the program without understanding how
+the whole thing works together or you will get burnt. You have been warned.
+
+Relevant RFCs:
+http://www.ietf.org/rfc/rfc2015.txt
+http://www.ietf.org/rfc/rfc3851.txt
+http://www.ietf.org/rfc/rfc2045.txt
+http://www.ietf.org/rfc/rfc2046.txt
+http://www.ietf.org/rfc/rfc822.txt
+http://www.ietf.org/rfc/rfc2183.txt
+
+For program configuration, see the 'constants' section below.
+
+Also see code comments throughout.
+
+Future possibilities:
+
+- Saving copies of original mail in 'semi-temp' files for debugging.
+
+- Stripping attachments and save them (e.g. in a location that can become a
+ 'files uploaded' section on a website). Replace them with links to the
+ website, even.
+
+- Making the prefixes, suffixes, replacements, padding, guts, pairs,
+ configurable at runtime.
+
+- Attaching signed mail, or wrapping in a multipart rather than removing
+ signatures; wouldn't be hard if always using MIME footers.
+
+- Following a script to allow various other header transformations (addition,
+ removal, etc.), or other transformations.
+
+- Prologues as well as or instead of footers.
+
+*/
+
+/* tag: includes */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <syslog.h>
+#include <errno.h>
+#include <string.h>
+#include <sysexits.h>
+
+/* tag: typedefs */
+
+// splint has bools, but C doesn't!
+#ifndef S_SPLINT_S
+typedef int bool;
+#define false (0)
+#define true (1)
+#endif
+
+// This is mostly to be able to include splint annotations
+typedef /*@null@*//*@observer@*/ const char * const_null_string;
+typedef /*@null@*/ char * null_string;
+typedef /*@null@*//*@owned@*/ char * owned_null_string;
+typedef /*@null@*//*@dependent@*/ char * dependent_null_string;
+
+// 'Callbacks'; they communicate primarily using globals, see below
+typedef bool (*callback_t)();
+typedef void (*function_t)();
+
+// For fill()
+typedef enum {
+ echo,
+ encode,
+ shunt,
+ discard,
+ stop,
+ fail
+} when_full_t;
+
+// Various places
+typedef enum {
+ unencoded,
+ quoted_printable,
+ base64
+} encoding_t;
+
+// For returning multiple characters, and a request to delete backlog
+// when decoding
+typedef struct {
+ int r;
+ int c1;
+ int c2;
+ int c3;
+} decode_t;
+
+/* tag: constants */
+
+/* tag: header_constants */
+
+// How many MIME Content- headers we expect, maximum, in a mail. If we have
+// more than that, we won't be able to process MIME so well, but we won't fail
+// catastrophically.
+#define mime_headers_max 16
+
+/* tag: footer_constants */
+
+// Stuff for processing the footer's smart removal and (smart or not)
+// insertion
+
+static const char * plain_prefix = "------~----------";
+static const char * plain_suffix = "------~~---------";
+static const char * plain_replacement = "\r\n\r\n";
+static const_null_string plain_tails[] = {
+ " ","\t","\r","\n",
+ NULL
+};
+static const_null_string plain_padding[] = {
+ ">"," ","\t","\r","\n",
+ NULL
+};
+static const_null_string plain_guts[] = {
+ NULL
+};
+static const_null_string plain_pairs[] = {
+ NULL
+};
+
+static const char * html_prefix = "------~----------";
+static const char * html_suffix = "------~~---------";
+static const char * html_replacement = "\r\n<br/><br/>\r\n";
+static const_null_string html_tails[] = {
+ "</html>","</HTML>","</body>","</BODY>",
+ " "," ","&NBSP;","\t","\r","\n",
+ "<br>","<BR>","<br/>","<BR/>","<br />","<BR />",
+ NULL
+};
+static const_null_string html_padding[] = {
+ ">",">",
+ " "," ","&NBSP;","\t","\r","\n",
+ "<br>","<BR>","<br/>","<BR/>","<br />","<BR />",
+ NULL
+};
+static const_null_string html_guts[] = {
+ // These are removed in an attempt to make a pair
+ "<hr>","<HR>","<hr/>","<HR/>","<hr />","<HR />",
+ " "," ","&NBSP;","\t","\r","\n",
+ "<br>","<BR>","<br/>","<BR/>","<br />","<BR />",
+ NULL
+};
+static const_null_string html_pairs[] = {
+ // Closing part (or NULL to mark no more), end of opening part,
+ // start of opening part, NULL
+ // The search strategy is fairly naive; if it finds the closing part,
+ // it checks for the end of the opening part; if it finds that, it
+ // searches back for the first character of each of the opening part
+ // variants, and if that character is found and is the beginning of the
+ // whole variant, it removes the pair.
+ "</p>",">","<p>","<p ",NULL,
+ "</P>",">","<P>","<P ",NULL,
+ "</blockquote>",">","<blockquote>","<blockquote ",NULL,
+ "</BLOCKQUOTE>",">","<BLOCKQUOTE>","<BLOCKQUOTE ",NULL,
+ "</div>",">","<div>","<div ",NULL,
+ "</DIV>",">","<DIV>","<DIV ",NULL,
+ "</span>",">","<span>","<span ",NULL,
+ "</SPAN>",">","<SPAN>","<SPAN ",NULL,
+ "</font>",">","<font>","<font ",NULL,
+ "</FONT>",">","<FONT>","<FONT ",NULL,
+ NULL
+};
+
+/* tag: buffer_constants */
+
+// Also see comment at buffer_globals about how the buffer works.
+
+// The buffer size limits how far back footers can be deleted; the
+// section of mail from the closing boundary back this far will be
+// searched for footers to remove.
+#define mem_buffer_size 65536
+// mem_buffer_keep is how much we keep in memory when shunting
+// off to disk; as we must be able to shunt off at least something
+// to disk each time we need to, this must be at least 2 bytes
+// less than mem_buffer_size. This is how much we will be able to
+// backtrack in memory, e.g. to strip whitespace. Something a little
+// larger than the SMTP line length limit of 998 should keep it safe.
+#define mem_buffer_keep 1024
+// mem_buffer_margin is how much space we keep in memory in case
+// a callback (decoding!) needs to use it. Must be at least 4 for
+// decoding not to cause nasty corruption.
+#define mem_buffer_margin 8
+// The number of replacements we may wish to make; usually removing
+// the MIME headers is the most complicated thing to be done, plus
+// removing the MIME version header and the newline that ends the
+// headers.
+#define replacements_max (mime_headers_max+4)
+
+/* tag: error_constants */
+
+// Enable one or both of these
+#define USE_SYSLOG
+//#define USE_STDERR
+
+/* tag: helper_constants */
+
+// Disable this to use stdin/stdout with CRLF line endings as the spec
+// and transports do; footer files are always expected to have UNIX line
+// endings
+#define UNIX_EOL
+
+/* tag: globals */
+
+/* tag: header_globals */
+
+// Offsets into the buffer; only valid while it's still in there!
+static int mime_header_starts[mime_headers_max]={0};
+static int mime_header_ends[mime_headers_max]={0};
+// String copies (until freed)
+static owned_null_string saved_mime_headers[mime_headers_max]={NULL};
+// Count applies to all header globals above
+static int mime_headers_count = 0;
+
+// Individual header strings which are processed to have comments
+// removed and have normalised syntax for easy deduction of types,
+// etc.. _header_body pointers point into the _header strings.
+// Indexes are into the header arrays above.
+// We store the version start and end separately as we want to delete it,
+// but not reoutput it.
+static /*@owned@*/ null_string version_header=NULL;
+static /*@dependent@*/ null_string version_header_body=NULL;
+static int version_header_start=0;
+static int version_header_end=0;
+static /*@owned@*/ null_string type_header=NULL;
+static /*@dependent@*/ null_string type_header_body=NULL;
+static int type_header_index=0;
+static /*@owned@*/ null_string transfer_header=NULL;
+static /*@dependent@*/ null_string transfer_header_body=NULL;
+static int transfer_header_index=0;
+static /*@owned@*/ null_string disposition_header=NULL;
+static /*@dependent@*/ null_string disposition_header_body=NULL;
+static int disposition_header_index=0;
+
+// Flag that we had errors reading headers so not to do anything fancy
+static bool mime_bad = false;
+
+/* tag: footer_globals */
+
+// Footer text; we just store \n but it is translated to CRLF
+// The actual buffers
+static /*@owned@*/ null_string plain_footer_buffer = NULL;
+static /*@owned@*/ null_string html_footer_buffer = NULL;
+static /*@owned@*/ null_string mime_footer_buffer = NULL;
+// Pointer to the string we're actually using
+static /*@dependent@*/ null_string plain_footer = NULL;
+static /*@dependent@*/ null_string html_footer = NULL;
+static /*@dependent@*/ null_string mime_footer = NULL;
+static bool html_mime_footer = false;
+// Whether to attempt deletion by surrounding the footer with special strings
+static bool smart_footer = false;
+
+/* tag: buffer_globals */
+
+// We have a buffer that may be partly on disk (disk_buffer), then
+// in memory (mem_buffer). The memory buffer wraps around as necessary.
+// The disk buffer begins at disk_buffer_start in the file. The _filled
+// variables say how much is in each part of the buffer, and the total
+// is in buffer_filled. A portion of the buffer is considered to have
+// been 'read'; before this is the lookbehind, and after this is the
+// lookahead. The buffer may also be marked at a certain location,
+// which is and should be almost always in the lookbehind (if in the
+// lookahead this should only be very temporary).
+
+static char mem_buffer_start[mem_buffer_size];
+static char * mem_buffer_end = mem_buffer_start+mem_buffer_size;
+static char * mem_buffer_next_empty = mem_buffer_start; // logical start
+static char * mem_buffer_next_fill = mem_buffer_start; // logical end + 1
+static int mem_buffer_filled=0;
+
+static /*@null@*/ FILE * disk_buffer = NULL;
+// Cannot pass mkstemp an unwritable string, so careful to declare this
+// as an array, not a pointer which would observe the string constant.
+static char disk_buffer_template[] = "foot_filter.XXXXXX";
+static int disk_buffer_start=0; // offset into temp file for buffer
+static int disk_buffer_filled=0;
+// disk_buffer_sought: location we are at in the temp file
+static int disk_buffer_sought=0;
+
+// at all times, buffer_filled == mem_buffer_filled + disk_buffer_filled
+static int buffer_filled=0;
+
+static int buffer_read=0; // offset into buffer
+static int buffer_mark=0; // offset into buffer; should be in lookbehind
+static bool buffer_marked=false;
+
+// The first and 'after last' characters to replace are stored
+static int replacement_starts[replacements_max] = {0};
+static int replacement_ends[replacements_max] = {0};
+static const_null_string replacement_strings[replacements_max] = {NULL};
+static int replacements_count=0;
+
+/* tag: callback_globals */
+
+// Used to communicate a character from the buffer to callbacks by the
+// functions the callback is directly called by
+static int buffer_char;
+
+// Used to communicate information between callback functions and the
+// functions that set up the callback (not the function that actually
+// calls the callback), and possibly for internal callback state. The
+// callbacks document how these should be used. Take special care to
+// follow the instructions, or things will go bad and be hard to track
+// down!
+static /*@dependent@*/ null_string callback_save;
+static const_null_string callback_compare;
+static int callback_match;
+static int callback_int;
+static bool callback_bool;
+
+/* tag: encoding_globals */
+
+// Current modes
+static encoding_t encoding;
+static encoding_t decoding;
+
+// State for the routines
+static char encoding_buffer[4];
+static int encoding_filled=0;
+static int encoding_echoed=0;
+static char decoding_buffer[4];
+static int decoding_filled=0;
+static int decoding_white=0;
+
+/* tag: prototypes */
+
+// Comments are made at function definitions where warranted.
+
+/* tag: main_prototypes */
+
+int main(int argc,char * argv[]);
+
+static void load_footer(/*@out@*//*@shared@*/ char ** footer,
+ /*@reldef@*/ char ** footer_buffer,
+ char * file,
+ /*@unique@*/ const_null_string prefix,
+ /*@unique@*/ const_null_string suffix);
+
+static void process_section(bool add_footer,
+ bool can_reenvelope, /*@null@*/ bool * parent_needs_footer);
+
+/* tag: header_prototypes */
+
+static inline void read_and_save_mime_headers();
+static char * remove_comments(/*@returned@*/ char * header,bool ext);
+static inline bool delimiting(char c,bool ext);
+static inline void remove_mime_headers();
+static inline void output_saved_mime_headers();
+static void free_saved_mime_headers();
+static bool /*@falsewhennull@*/ is_multipart(/*@null@*//*@out@*/ char ** boundary);
+static inline bool is_signed();
+static inline bool is_alternative();
+static inline bool is_mixed();
+static inline bool is_html();
+static inline bool is_plain();
+static inline bool is_signature();
+static inline bool is_attachment();
+static inline void set_decoding_type();
+static inline void change_to_mixed();
+static inline void generate_boundary(/*@out@*/ char ** boundary);
+static inline void output_mime_mixed_headers(const char * boundary);
+static inline void output_prolog();
+static void output_mime_footer(const char * boundary);
+static inline void output_boundary(const char * boundary);
+static inline void output_final_boundary(const char * boundary);
+static bool at_final_boundary(char * boundary);
+
+/* tag: footer_prototypes */
+
+static inline void process_text_section(bool add_footer,
+ /*@null@*/ const char * footer,
+ const char * prefix, const char * suffix, const char * replacement,
+ const_null_string * tails, const_null_string * padding,
+ const_null_string * guts, const_null_string * pairs,
+ char * boundary);
+static inline void pad(const_null_string * padding,
+ const_null_string * guts, const_null_string * pairs,
+ int * prefix_pos, int * suffix_pos);
+static inline void mark_tail(const_null_string * padding);
+static inline void encode_footer(const char * footer);
+
+/* tag: buffer_prototypes */
+
+static inline void read_buffer();
+static inline void echo_buffer();
+static inline void skip_buffer();
+static inline void echo_lookbehind();
+static inline void encode_lookbehind();
+static inline void encode_replacements();
+static inline void make_replacements(callback_t one_char,
+ callback_t start_marked);
+static inline void encode_to_mark();
+static inline void echo_disk_buffer();
+static inline void skip_disk_buffer();
+static inline void read_boundary(/*@out@*/ char ** boundary);
+static inline void echo_to_boundary(const char * boundary);
+static inline void skip_to_boundary(const char * boundary);
+static inline void decode_and_read_to_boundary_encoding_when_full(
+ const char * boundary);
+static inline bool process_one_line_checking_boundary(callback_t n_chars,
+ /*@null@*/ function_t process, callback_t processing,
+ when_full_t when_full, const char * boundary);
+
+static int pos_of(const char * text,int from,int to);
+
+static bool look(callback_t callback,int from,bool read);
+static bool lookback(callback_t callback,int from,bool mark);
+static bool empty(callback_t callback);
+static bool fill(callback_t callback, when_full_t when_full);
+
+static inline void create_disk_buffer();
+static void remove_disk_buffer();
+static inline void shunt_to_disk(int n);
+
+/* tag: callback_prototypes */
+
+static bool one_char();
+static bool echoing_one_char();
+static bool encoding_one_char();
+static bool n_chars();
+static bool echoing_n_chars();
+static bool encoding_n_chars();
+static bool saving_n_chars();
+static bool n_chars_until_match();
+static bool until_eol();
+// static bool echoing_until_eol();
+static bool counting_until_eol();
+static bool saving_until_eol();
+static bool decoding_until_eol();
+// static bool until_no_lookbehind();
+static bool echoing_until_no_lookbehind();
+static bool encoding_until_no_lookbehind();
+static bool until_no_disk_buffer();
+static bool echoing_until_no_disk_buffer();
+static bool encoding_until_no_disk_buffer();
+static bool until_no_buffer();
+// static bool echoing_until_no_buffer();
+static bool until_start_marked();
+static bool echoing_until_start_marked();
+static bool encoding_until_start_marked();
+static bool until_match();
+static bool comparing_head();
+static bool case_insensitively_comparing_head();
+
+/* tag: encoding_prototypes */
+
+static inline void encode_string(const char * s);
+static void encodechar(int c);
+static inline void finish_encoding();
+static /*@reldef@*/ decode_t decodechar(int c);
+static void decode_lookahead();
+static inline void finish_decoding();
+
+static inline int decode_hex(int c);
+static inline int decode_64(int c);
+static inline void encode_hex_byte(unsigned int h);
+static inline void encode_64(unsigned int b);
+
+/* tag: error_prototypes */
+
+static inline void * alloc_or_exit(size_t s) /*@allocates result@*/;
+static inline void /*@noreturnwhentrue@*/
+ resort_to_exit(bool when,const char * message,int status);
+static inline void /*@noreturnwhentrue@*/
+ resort_to_errno(bool when,const char * message,int status);
+static inline void resort_to_warning(bool when,const char * message);
+static inline void warning(const char * message);
+
+/* tag: helper_prototypes */
+
+static inline int get();
+static inline int put(int c);
+static inline int putstr(const char * s);
+
+static inline bool case_insensitively_heads(const char * head,const char * buffer);
+
+/* tag: functions */
+
+/* tag: main_functions */
+
+int main(int argc, char * argv[]) {
+ int opt;
+ bool show_version=false;
+ null_string plain_footer_file=NULL;
+ null_string html_footer_file=NULL;
+ null_string mime_footer_file=NULL;
+ // Initialise
+ resort_to_errno(atexit(remove_disk_buffer)!=0,
+ "cannot register exit function",EX_OSERR);
+ srandom((unsigned int)(getpid()*time(NULL)));
+ // Parse args
+ while ((opt=getopt(argc,argv,"p:h:P:H:sV"))!=-1) {
+ switch ((char)opt) {
+ case 'p': plain_footer_file=optarg; break;
+ case 'h': html_footer_file=optarg; break;
+ case 'P': mime_footer_file=optarg; html_mime_footer=false; break;
+ case 'H': mime_footer_file=optarg; html_mime_footer=true; break;
+ case 's': smart_footer=true; break;
+ case 'V': show_version=true; break;
+ default: warning("unrecognised commandline option");
+ }
+ }
+ if (show_version||argc<2) {
+ printf("%s\n",FOOT_FILTER_VERSION);
+#ifdef UNIX_EOL
+ printf(" with UNIX line endings\n");
+#else
+ printf(" with DOS line endings\n");
+#endif
+ printf(" reporting errors to: ");
+#ifdef USE_SYSLOG
+ printf("syslog ");
+#endif
+#ifdef USE_STDERR
+ printf("stderr ");
+#endif
+ printf("\n");
+ if (argc<2) fprintf(stderr,"%s",USAGE);
+ if (show_version) exit(EX_OK);
+ }
+ argc-=optind;
+ argv+=optind;
+ resort_to_warning(argc>0,"unexpected commandline argument");
+ // Load footers
+ if (plain_footer_file!=NULL)
+ load_footer(&plain_footer,&plain_footer_buffer,
+ plain_footer_file,
+ smart_footer?plain_prefix:NULL,smart_footer?plain_suffix:NULL);
+ if (html_footer_file!=NULL)
+ load_footer(&html_footer,&html_footer_buffer,
+ html_footer_file,
+ smart_footer?html_prefix:NULL,smart_footer?html_suffix:NULL);
+ if (mime_footer_file!=NULL)
+ load_footer(&mime_footer,&mime_footer_buffer,
+ mime_footer_file,NULL,NULL);
+ // Do the job
+ process_section(true,true,NULL);
+ // Finish
+ if (plain_footer_buffer!=NULL) free(plain_footer_buffer);
+ if (html_footer_buffer!=NULL) free(html_footer_buffer);
+ if (mime_footer_buffer!=NULL) free(mime_footer_buffer);
+ exit(EX_OK);
+}
+
+static void load_footer(/*@out@*//*@shared@*/ char ** footer,
+ /*@reldef@*/ char ** footer_buffer,
+ char * file,
+ /*@unique@*/ const_null_string prefix,
+ /*@unique@*/ const_null_string suffix) {
+ FILE * f;
+ int prefixl=0, footerl=0, suffixl=0;
+ char * ff;
+ if (prefix!=NULL&&suffix!=NULL) {
+ prefixl=(int)strlen(prefix);
+ suffixl=(int)strlen(suffix);
+ }
+ f=fopen(file,"r");
+ resort_to_errno(f==NULL,"error opening footer file",EX_NOINPUT);
+ resort_to_errno(fseek(f,0,SEEK_END)!=0,
+ "error seeking end of footer file",EX_IOERR);
+ resort_to_errno((footerl=(int)ftell(f))==-1,
+ "error finding footer length",EX_IOERR);
+ resort_to_errno(fseek(f,0,SEEK_SET)!=0,
+ "error seeking in footer file",EX_IOERR);
+ // prefix, \n, footer, \n, suffix, \0
+ *footer_buffer=alloc_or_exit(sizeof(char)*(prefixl+footerl+suffixl+3));
+ *footer=*footer_buffer;
+ *footer+=prefixl+1;
+ resort_to_errno(fread(*footer,1,(size_t)footerl,f)<(size_t)footerl,
+ "error reading footer",EX_IOERR);
+ // We strip off a single trailing newline to keep them from accumulating
+ // but to allow the user the option of adding them if desired
+ if ((*footer)[footerl-1]=='\n') --footerl;
+ (*footer)[footerl]='\0';
+ if (prefix==NULL||suffix==NULL) return;
+ // Put in the prefix and suffix as necessary
+ ff=strstr(*footer,prefix);
+ if (ff!=NULL) {
+ ff=strstr(ff,suffix);
+ if (ff!=NULL) return;
+ (*footer)[footerl]='\n';
+ ++footerl;
+ strcpy(*footer+footerl,suffix);
+ (*footer)[footerl+suffixl]='\0';
+ } else {
+ ff=strstr(*footer,suffix);
+ if (ff==NULL) {
+ (*footer)[footerl]='\n';
+ ++footerl;
+ strcpy(*footer+footerl,suffix);
+ (*footer)[footerl+suffixl]='\0';
+ }
+ *footer-=prefixl+1;
+ strcpy(*footer,prefix);
+ (*footer)[prefixl]='\n';
+ }
+}
+
+// Should be called with the boundary for the section as lookahead
+// in the buffer, but nothing more, and no lookbehind.
+static void process_section(bool add_footer,
+ bool can_reenvelope, /*@null@*/ bool * parent_needs_footer) {
+ char * external=NULL;
+ char * internal=NULL;
+ char * generated=NULL;
+ bool reenveloping=false;
+ bool child_needed_footer=false;
+ bool needs_footer=false;
+ bool unsigning=false;
+ if (parent_needs_footer!=NULL) *parent_needs_footer=false;
+ // The headers must be read, saved and echoed before making any
+ // recursive calls, as I'm naughty and using globals.
+ read_boundary(&external);
+ read_and_save_mime_headers();
+ if (mime_bad) {
+ // If an error, just resort to echoing
+ echo_buffer(); // Boundary and headers
+ // End headers with the extra line break
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ free_saved_mime_headers();
+ // Body
+ echo_to_boundary(external);
+ free(external);
+ return;
+ }
+ // Headers determining we skip this section
+ if (is_signature()) {
+ skip_buffer(); // Boundary and headers
+ skip_to_boundary(external);
+ return;
+ }
+ // Header processing
+ if (is_signed()) unsigning=true;
+ if (unsigning) change_to_mixed();
+ if (add_footer&&mime_footer!=NULL&&(
+ is_alternative()||(is_multipart(NULL)&&!is_mixed())||
+ (is_plain()&&plain_footer==NULL)||
+ (is_html()&&html_footer==NULL)
+ )) {
+ add_footer=false;
+ if (can_reenvelope) {
+ reenveloping=true;
+ remove_mime_headers();
+ } else if (parent_needs_footer!=NULL) *parent_needs_footer=true;
+ }
+ // Headers
+ echo_buffer(); // Boundary and possibly modified headers
+ if (reenveloping) {
+ generate_boundary(&generated);
+ output_mime_mixed_headers(generated);
+ output_prolog();
+ output_boundary(generated);
+ output_saved_mime_headers();
+ }
+ // End the headers with the extra line break
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ // Body processing
+ if (is_multipart(&internal)) {
+ // This branch frees the MIME headers before recursing.
+ // Don't include the prolog if it used to be signed;
+ // it usually says something like 'this message is signed'
+ if (unsigning) {
+ skip_to_boundary(internal);
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ } else {
+ echo_to_boundary(internal);
+ }
+ // The recursive call needs these globals
+ free_saved_mime_headers();
+ while (!at_final_boundary(internal)) {
+ process_section(add_footer,false,&child_needed_footer);
+ if (child_needed_footer) needs_footer=true;
+ }
+ if (needs_footer) output_mime_footer(internal);
+ free(internal);
+ echo_to_boundary(external);
+ } else {
+ // This branch frees the MIME headers at the end
+ if (!is_attachment()&&(
+ (is_plain()&&plain_footer!=NULL)||
+ (is_html()&&html_footer!=NULL))) {
+ // alternatively
+ // if (!is_attachment()&&(
+ // (is_plain()&&((add_footer&&plain_footer!=NULL)||smart_footer))||
+ // (is_html()&&((add_footer&&html_footer!=NULL)||smart_footer)))) {
+ if (is_plain()) {
+ process_text_section(add_footer,plain_footer,
+ plain_prefix,plain_suffix,plain_replacement,
+ plain_tails,plain_padding,plain_guts,plain_pairs,external);
+ } else {
+ process_text_section(add_footer,html_footer,
+ html_prefix,html_suffix,html_replacement,
+ html_tails,html_padding,html_guts,html_pairs,external);
+ }
+ } else {
+ echo_to_boundary(external);
+ }
+ free_saved_mime_headers();
+ }
+ // MIME stuff is freed now; take care not to use it.
+ /*@-branchstate@*/
+ if (reenveloping) {
+ // We ensure generated is not null in another if(reenveloping)
+ // conditional above
+ /*@-nullpass@*/
+ output_mime_footer(generated);
+ output_final_boundary(generated);
+ free(generated);
+ /*@=nullpass@*/
+ }
+ /*@=branchstate@*/
+ free(external);
+}
+
+/* tag: header_functions */
+
+static inline void read_and_save_mime_headers() {
+ /*@-mustfreeonly@*/
+ mime_bad=false;
+ // Mark current end of buffer
+ buffer_mark=buffer_read;
+ buffer_marked=true;
+ for (;;) {
+ do {
+ // Extend current header until beginning of next
+ callback_bool=false;
+ (void)fill(until_eol,shunt);
+ if (buffer_filled==buffer_read) {
+ // We probably hit EOF; just get out, and the whole
+ // mail will end up echoed out
+ warning("unexpected end of input");
+ break;
+ }
+ (void)look(one_char,buffer_read,false);
+ if (callback_int==(int)' '||callback_int==(int)'\t') {
+ // Continuation of previous header; read it
+ read_buffer();
+ continue;
+ }
+ // Start of new header; don't read it; process the old one
+ // (from the mark to the end of the lookbehind)
+ break;
+ } while (true);
+ // Process the old header, if there is one
+ if (buffer_mark<buffer_read) {
+ do {
+ callback_compare="MIME-Version:";
+ (void)look(case_insensitively_comparing_head,buffer_mark,false);
+ if (callback_bool) {
+ // MIME version header
+ version_header_start=buffer_mark;
+ version_header_end=buffer_read;
+ version_header=alloc_or_exit(sizeof(char)*(buffer_read-buffer_mark+1));
+ callback_save=version_header;
+ callback_int=buffer_read-buffer_mark;
+ callback_save[callback_int]='\0';
+ (void)look(saving_n_chars,buffer_mark,false);
+ callback_save=NULL;
+ version_header_body=remove_comments(version_header,true);
+ if (!case_insensitively_heads("1.0",version_header_body)) {
+ mime_bad=true;
+ }
+ break;
+ }
+ callback_compare="Content-";
+ (void)look(case_insensitively_comparing_head,buffer_mark,false);
+ if (!callback_bool) break;
+ // Another MIME header
+ if (mime_headers_count==mime_headers_max) {
+ warning("too many MIME headers");
+ mime_bad=true;
+ break;
+ }
+ mime_header_starts[mime_headers_count]=buffer_mark;
+ mime_header_ends[mime_headers_count]=buffer_read;
+ saved_mime_headers[mime_headers_count]=
+ alloc_or_exit(sizeof(char)*(buffer_read-buffer_mark)+1);
+ saved_mime_headers[mime_headers_count][0]='\0';
+ callback_save=saved_mime_headers[mime_headers_count];
+ callback_int=buffer_read-buffer_mark;
+ callback_save[callback_int]='\0';
+ (void)look(saving_n_chars,buffer_mark,false);
+ callback_compare="Content-Type:";
+ (void)look(case_insensitively_comparing_head,buffer_mark,false);
+ if (callback_bool) {
+ type_header=alloc_or_exit(sizeof(char)*(buffer_read-buffer_mark+1));
+ strcpy(type_header,saved_mime_headers[mime_headers_count]);
+ type_header_body=remove_comments(type_header,true);
+ type_header_index=mime_headers_count;
+ ++mime_headers_count;
+ break;
+ }
+ callback_compare="Content-Transfer-Encoding:";
+ (void)look(case_insensitively_comparing_head,buffer_mark,false);
+ if (callback_bool) {
+ transfer_header=alloc_or_exit(sizeof(char)*(buffer_read-buffer_mark+1));
+ strcpy(transfer_header,saved_mime_headers[mime_headers_count]);
+ transfer_header_body=remove_comments(transfer_header,true);
+ transfer_header_index=mime_headers_count;
+ ++mime_headers_count;
+ break;
+ }
+ callback_compare="Content-Disposition:";
+ (void)look(case_insensitively_comparing_head,buffer_mark,false);
+ if (callback_bool) {
+ disposition_header=alloc_or_exit(sizeof(char)*(buffer_read-buffer_mark+1));
+ strcpy(disposition_header,saved_mime_headers[mime_headers_count]);
+ disposition_header_body=remove_comments(disposition_header,true);
+ disposition_header_index=mime_headers_count;
+ ++mime_headers_count;
+ break;
+ }
+ ++mime_headers_count;
+ } while (false);
+ }
+ // Mark the new header
+ buffer_mark=buffer_read;
+ if (buffer_read==buffer_filled) {
+ // EOF; return
+ return;
+ }
+ // Read the first part of the new header; loop to read rest
+ read_buffer();
+ callback_compare="\r\n";
+ (void)look(comparing_head,buffer_mark,false);
+ if (callback_bool) {
+ // End of headers; strip the extra line; return
+ resort_to_exit(replacements_count==replacements_max,
+ "internal error: too many replacements",EX_SOFTWARE);
+ replacement_starts[replacements_count]=buffer_read-2;
+ replacement_ends[replacements_count]=buffer_read;
+ replacement_strings[replacements_count]=NULL;
+ ++replacements_count;
+ return;
+ }
+ }
+ /*@=mustfreeonly@*/
+}
+// Returns a pointer to the body part of the header field
+static char * remove_comments(/*@returned@*/ char * header,bool ext) {
+ // This removes comments and any superfluous whitespace in the
+ // header (a structured header, that is, RFC822); it fiddles with
+ // the quoted strings in such a way that backslash escaping means
+ // simply take the next character literally, rather than needing
+ // to do funny things with folded strings. The result is not suitable
+ // for output.
+ char * h=header;
+ char * hh;
+ char * hhh;
+ char * body=NULL;
+ char close;
+ int levels=0;
+ while (*h!=':') ++h;
+ ++h;
+ if (*h==' '||*h=='\t') ++h;
+ body=h;
+ hh=h;
+ while (*h!='\0') {
+ if (*h=='\r'&&*(h+1)=='\n') {
+ h+=2;
+ continue;
+ } else if ((*h==' '||*h=='\t')&&delimiting(*(hh-1),ext)) {
+ ++h;
+ continue;
+ } else if (delimiting(*h,ext)&&(*(hh-1)==' '||*(hh-1)=='\t')) {
+ if (hh!=body) --hh;
+ }
+ if (*h=='(') {
+ ++h;
+ levels=1;
+ while (levels>0) {
+ if (*h=='\0') break;
+ if (*h=='\\') {
+ ++h;
+ if (*h=='\0') break;
+ }
+ else if (*h=='(') ++levels;
+ else if (*h==')') --levels;
+ ++h;
+ }
+ if (!delimiting(*h,ext)&&!delimiting(*(hh-1),ext)) {
+ // Put in some whitespace if something delimiting isn't
+ // coming and hasn't just been
+ *hh=' ';
+ ++hh;
+ }
+ continue;
+ } else if (*h=='"'||*h=='[') {
+ if (*h=='[') close=']';
+ else close='"';
+ *hh=*h;
+ ++h; ++hh;
+ hhh=hh;
+ while (*h!='\0'&&*h!=close) {
+ if (*h=='\\') {
+ *hh=*h;
+ ++hh; ++h;
+ if (*h=='\0') break;
+ if (*h=='\r'&&*(h+1)=='\n') {
+ *hh=*h; ++hh; ++h;
+ *hh=*h; ++hh; ++h;
+ if (*h=='\0') break;
+ ++hh; ++h;
+ continue;
+ }
+ } else if (*h==(char)8) {
+ --hh; ++h;
+ if (hh<hhh) hh=hhh;
+ continue;
+ } else if (*h=='\r'&&*(h+1)=='\n') {
+ h+=2;
+ continue;
+ // alternatively
+ // *hh=*h; ++hh; ++h;
+ // *hh=*h; ++hh; ++h;
+ // if (*h=='\0') break;
+ // ++h;
+ // continue;
+ // or perhaps even (the spec is a bit ambiguous)
+ // h+=2;
+ // if (*h=='\0') break;
+ // ++h;
+ // continue;
+ }
+ *hh=*h;
+ ++h; ++hh;
+ }
+ if (*h=='\0') break;
+ }
+ *hh=*h;
+ ++h; ++hh;
+ }
+ if (*(hh-1)==' ') *(hh-1)='\0';
+ else *hh='\0';
+ return body;
+}
+static inline bool delimiting(char c,bool ext) {
+ // 'ext' (extended) delimiters include '/', '?' and '=' and assist
+ // by removing whitespace surrounding those, as these are
+ // delimiters in the MIME header fields, even though not RFC822;
+ // note that MIME doesn't use '.' but we still do.
+ return (c==' '||c=='\t'||c=='<'||c=='>'||c=='@'||
+ c==','||c==';'||c==':'||c=='\\'||c=='"'||
+ c=='.'||c=='['||c==']'||
+ (ext&&(c=='/'||c=='='||c=='?')));
+}
+static inline void remove_mime_headers() {
+ int h;
+ for (h=0;h<mime_headers_count;++h) {
+ resort_to_exit(replacements_count==replacements_max,
+ "internal error: too many replacements",EX_SOFTWARE);
+ replacement_starts[replacements_count]=mime_header_starts[h];
+ replacement_ends[replacements_count]=mime_header_ends[h];
+ replacement_strings[replacements_count]=NULL;
+ ++replacements_count;
+ }
+ if (version_header!=NULL) {
+ resort_to_exit(replacements_count==replacements_max,
+ "internal error: too many replacements",EX_SOFTWARE);
+ replacement_starts[replacements_count]=version_header_start;
+ replacement_ends[replacements_count]=version_header_end;
+ replacement_strings[replacements_count]=NULL;
+ ++replacements_count;
+ }
+}
+static inline void output_saved_mime_headers() {
+ int h;
+ for (h=0;h<mime_headers_count;++h) {
+ // The header includes its terminating CRLF
+ /*@-nullpass@*/
+ resort_to_errno(putstr(saved_mime_headers[h])==EOF,
+ "error echoing string",EX_IOERR);
+ /*@=nullpass@*/
+ }
+}
+static void free_saved_mime_headers() {
+ int h;
+ for (h=0;h<mime_headers_count;++h) {
+ /*@-nullpass@*/
+ free(saved_mime_headers[h]);
+ /*@=nullpass@*/
+ }
+ mime_headers_count=0;
+ if (version_header!=NULL) free(version_header);
+ version_header=NULL;
+ version_header_body=NULL;
+ if (type_header!=NULL) free(type_header);
+ type_header=NULL;
+ type_header_body=NULL;
+ if (transfer_header!=NULL) free(transfer_header);
+ transfer_header=NULL;
+ transfer_header_body=NULL;
+ if (disposition_header!=NULL) free(disposition_header);
+ disposition_header=NULL;
+ disposition_header_body=NULL;
+}
+static bool /*@falsewhennull@*/ is_multipart(/*@null@*//*@out@*/ char ** boundary) {
+ char * b;
+ /*@dependent@*/ char * bb;
+ int l=0;
+ if (type_header_body==NULL||
+ !case_insensitively_heads("multipart/",type_header_body)) {
+ if (boundary!=NULL) *boundary=NULL;
+ return false;
+ }
+ b=type_header_body+10;
+ for (;;) {
+ while (*b!='\0'&&*b!=';') ++b;
+ if (*b=='\0') {
+ warning("no boundary given");
+ if (boundary!=NULL) *boundary=NULL;
+ return false;
+ }
+ ++b;
+ if (case_insensitively_heads("boundary=",b)) break;
+ }
+ b+=9;
+ if (*b=='"') {
+ for (bb=b+1;*bb!='\0'&&*bb!='"';++bb) {
+ if (*bb=='\\') {
+ ++bb;
+ if (*bb=='\0') break;
+ }
+ ++l;
+ }
+ if (*bb=='\0') {
+ warning("error in boundary syntax");
+ if (boundary!=NULL) *boundary=NULL;
+ return false;
+ }
+ } else {
+ // MIME tokens can include '.'
+ for (bb=b;*bb!='\0'&&(!delimiting(*bb,true)||*bb=='.');++bb) ++l;
+ }
+ /*@-mustdefine@*/
+ if (boundary==NULL) return true;
+ /*@=mustdefine@*/
+ // Room for leading and trailing '--', and terminator
+ *boundary=alloc_or_exit(sizeof(char)*(l+5));
+ bb=*boundary;
+ *bb++='-';
+ *bb++='-';
+ if (*b=='"') {
+ ++b;
+ while (*b!='\0'&&*b!='"') {
+ if (*b=='\\') ++b;
+ *bb++=*b++;
+ }
+ } else {
+ // MIME tokens can include '.'
+ while (*b!='\0'&&(!delimiting(*b,true)||*b=='.')) *bb++=*b++;
+ }
+ *bb='\0';
+ return true;
+}
+static inline bool is_signed() {
+ return type_header_body!=NULL&&
+ case_insensitively_heads("multipart/signed",type_header_body);
+}
+static inline bool is_alternative() {
+ return type_header_body!=NULL&&
+ case_insensitively_heads("multipart/alternative",type_header_body);
+}
+static inline bool is_mixed() {
+ return type_header_body!=NULL&&
+ case_insensitively_heads("multipart/mixed",type_header_body);
+}
+static inline bool is_html() {
+ return type_header_body!=NULL&&
+ case_insensitively_heads("text/html",type_header_body);
+}
+static inline bool is_plain() {
+ return type_header_body==NULL||
+ case_insensitively_heads("text/plain",type_header_body);
+}
+static inline bool is_signature() {
+ return type_header_body!=NULL&&(
+ case_insensitively_heads("application/x-pkcs7-signature",
+ type_header_body)||
+ case_insensitively_heads("application/pgp-signature",
+ type_header_body));
+}
+static inline bool is_attachment() {
+ return disposition_header_body!=NULL&&
+ case_insensitively_heads("attachment",disposition_header_body);
+}
+static inline void set_decoding_type() {
+ if (transfer_header_body==NULL) {
+ decoding=unencoded;
+ return;
+ }
+ if (case_insensitively_heads("quoted-printable",transfer_header_body)) {
+ decoding=quoted_printable;
+ return;
+ }
+ if (case_insensitively_heads("base64",transfer_header_body)) {
+ decoding=base64;
+ return;
+ }
+ decoding=unencoded;
+ if (case_insensitively_heads("7bit",transfer_header_body)) return;
+ if (case_insensitively_heads("8bit",transfer_header_body)) return;
+ if (case_insensitively_heads("binary",transfer_header_body)) return;
+ warning("unrecognised transfer encoding");
+}
+static inline void change_to_mixed() {
+ char * boundary=NULL;
+ char * header;
+ char * b, * h;
+ int l=0;
+ if (!is_multipart(&boundary)) {
+ warning("internal error: changing non-multipart to mixed");
+ return;
+ }
+ boundary+=2;
+ // The special cases should never happen, as '\', '"', '\r' and '\n'
+ // aren't allowed in boundaries...but...just in case...
+ for (b=boundary;*b!='\0';++b) {
+ if (*b=='\r'||*b=='\n') {
+ warning("boundary with newline");
+ return;
+ }
+ if (*b=='"'||*b=='\\') ++l;
+ ++l;
+ }
+ header=alloc_or_exit(sizeof(char)*(50+l)); // Play it safe
+ strcpy(header,"Content-Type: multipart/mixed;\r\n boundary=\"");
+ for (h=header+43,b=boundary;*b!='\0';++h,++b) {
+ if (*b=='"'||*b=='\\') { *h='\\'; ++h; }
+ *h=*b;
+ }
+ *h++='"'; *h++='\r'; *h++='\n'; *h++='\0';
+ free(boundary-2);
+ /*@-nullpass@*/
+ free(saved_mime_headers[type_header_index]);
+ /*@=nullpass@*/
+ saved_mime_headers[type_header_index]=header;
+ resort_to_exit(replacements_count==replacements_max,
+ "internal error: too many replacements",EX_SOFTWARE);
+ replacement_starts[replacements_count]=
+ mime_header_starts[type_header_index];
+ replacement_ends[replacements_count]=
+ mime_header_ends[type_header_index];
+ replacement_strings[replacements_count]=
+ saved_mime_headers[type_header_index];
+ ++replacements_count;
+}
+static inline void generate_boundary(/*@out@*/ char ** boundary) {
+ int r;
+ *boundary=alloc_or_exit(sizeof(char)*42); // Life, the universe and everything
+ strcpy(*boundary,"--=_foot_filter_boundary_0123456789012_=");
+ for (r=25;r<38;++r) {
+ (*boundary)[r]=(char)((int)'A'+(random()%16));
+ }
+}
+static inline void output_mime_mixed_headers(const char * boundary) {
+ resort_to_errno(
+ putstr("MIME-Version: 1.0\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ resort_to_errno(
+ putstr("Content-Type: multipart/mixed;\r\n boundary=\"")==EOF,
+ "error echoing string",EX_IOERR);
+ resort_to_errno(putstr(boundary+2)==EOF,
+ "error echoing string",EX_IOERR);
+ // I put an extra CRLF just in case some mail reader expects the
+ // initial boundary to include one that is separate from the one that
+ // ends the headers.
+ resort_to_errno(
+ putstr("\"\r\n\r\n\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+}
+static inline void output_prolog() {
+ // Deliberately empty; we don't need any prolog
+}
+static void output_mime_footer(const char * boundary) {
+ output_boundary(boundary);
+ if (html_mime_footer) {
+ resort_to_errno(
+ putstr("Content-Type: text/html; charset=UTF-8\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ } else {
+ resort_to_errno(
+ putstr("Content-Type: text/plain; charset=UTF-8\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ }
+ resort_to_errno(
+ putstr("Content-Transfer-Encoding: quoted-printable\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ encoding=quoted_printable;
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+ /*@-nullpass@*/
+ encode_footer(mime_footer);
+ /*@=nullpass@*/
+ encodechar((int)'\r');
+ encodechar((int)'\n');
+ finish_encoding();
+ // This is logically part of the boundary that is about to come
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error echoing string",EX_IOERR);
+}
+static inline void output_boundary(const char * boundary) {
+ resort_to_errno(putstr(boundary)==EOF,"error echoing string",EX_IOERR);
+ resort_to_errno(putstr("\r\n")==EOF,"error echoing string",EX_IOERR);
+}
+static inline void output_final_boundary(const char * boundary) {
+ resort_to_errno(putstr(boundary)==EOF,"error echoing string",EX_IOERR);
+ resort_to_errno(putstr("--\r\n")==EOF,"error echoing string",EX_IOERR);
+}
+static bool at_final_boundary(char * boundary) {
+ int l;
+ // If no lookahead, we probably hit EOF, so we primarily just need to get
+ // out of loops and exit
+ if (buffer_filled-buffer_read==0) {
+ warning("probably unexpected end of input");
+ return true;
+ }
+ l=(int)strlen(boundary);
+ boundary[l]='-';
+ boundary[l+1]='-';
+ boundary[l+2]='\0';
+ /*@-temptrans@*/
+ callback_compare=boundary;
+ /*@=temptrans@*/
+ (void)look(comparing_head,buffer_read,false);
+ callback_compare=NULL;
+ boundary[l]='\0';
+ return callback_bool;
+}
+
+/* tag: footer_functions */
+
+static inline void process_text_section(bool add_footer,
+ /*@null@*/ const char * footer,
+ const char * prefix, const char * suffix, const char * replacement,
+ const_null_string * tails, const_null_string * padding,
+ const_null_string * guts, const_null_string * pairs,
+ char * boundary) {
+ int prefix_pos;
+ int later_prefix_pos;
+ int suffix_pos;
+ bool removed_footers=false;
+ bool removed_newline=false;
+ bool boundary_newline=false;
+ int prefixl=(int)strlen(prefix);
+ int suffixl=(int)strlen(suffix);
+ resort_to_exit(buffer_filled>0,
+ "internal error: unexpected data in buffer",EX_SOFTWARE);
+ set_decoding_type();
+ encoding=decoding;
+ decode_and_read_to_boundary_encoding_when_full(boundary);
+ if (smart_footer&&footer!=NULL) {
+ // alternatively
+ // if (smart_footer) {
+ for (;;) {
+ prefix_pos=pos_of(prefix,0,buffer_read);
+ if (prefix_pos==EOF) break;
+ suffix_pos=pos_of(suffix,prefix_pos,buffer_read);
+ if (suffix_pos==EOF) break;
+ for (;;) {
+ later_prefix_pos=
+ pos_of(prefix,prefix_pos+prefixl,suffix_pos-prefixl);
+ if (later_prefix_pos!=EOF) prefix_pos=later_prefix_pos;
+ else break;
+ }
+ suffix_pos+=suffixl;
+ pad(padding,guts,pairs,&prefix_pos,&suffix_pos);
+ replacement_starts[replacements_count]=prefix_pos;
+ replacement_ends[replacements_count]=suffix_pos;
+ // We may not want the last replacement so replace
+ // with nothing first
+ replacement_strings[replacements_count]=NULL;
+ ++replacements_count;
+ // We want the last replacement; encode it now before
+ // doing any more encoding
+ if (removed_footers) encode_string(replacement);
+ encode_replacements();
+ removed_footers=true;
+ }
+ }
+ if (*boundary!='\0'&&(decoding==quoted_printable||decoding==unencoded)) {
+ // If we're not using base64 encoding, and we're in multipart, there
+ // will be a final CRLF that is part of the input but logically part of
+ // the boundary, not the text. Removing the footer may have already
+ // removed it, so we need to check if it's here or not.
+ if (buffer_read>1) {
+ callback_compare="\r\n";
+ (void)look(comparing_head,buffer_read-2,false);
+ callback_compare=NULL;
+ if (callback_bool) boundary_newline=true;
+ }
+ }
+ if (add_footer&&footer!=NULL) {
+ // This will skip past the boundary newline
+ mark_tail(tails);
+ if (removed_footers&&buffer_mark==0) {
+ // The last replacement coincides with where the footer
+ // is going to go; don't use the replacement text.
+ removed_footers=false;
+ }
+ }
+ if (removed_footers) encode_string(replacement);
+ if (add_footer&&footer!=NULL) {
+ if (buffer_mark<buffer_read-2||
+ (buffer_mark==buffer_read-2&&!boundary_newline)) {
+ // If we tailed back past a newline (that wasn't the boundary
+ // newline which isn't really there) we don't bother appending
+ // a new one to the footer
+ callback_compare="\r\n";
+ (void)look(comparing_head,buffer_mark,false);
+ if (callback_bool) removed_newline=true;
+ }
+ encode_to_mark();
+ encodechar((int)'\r');
+ encodechar((int)'\n');
+ encode_footer(footer);
+ if (!removed_newline) {
+ encodechar((int)'\r');
+ encodechar((int)'\n');
+ }
+ }
+ if (boundary_newline) {
+ // Actually remove the boundary newline now
+ if (replacements_count==replacements_max) {
+ warning("internal error: too many replacements");
+ } else {
+ replacement_starts[replacements_count]=buffer_read-2;
+ replacement_ends[replacements_count]=buffer_read;
+ replacement_strings[replacements_count]=NULL;
+ ++replacements_count;
+ }
+ }
+ encode_lookbehind();
+ finish_encoding();
+ if (*boundary!='\0') {
+ // This is logically part of the boundary
+ resort_to_errno(putstr("\r\n")==EOF,"error echoing string",EX_IOERR);
+ }
+}
+static inline void pad(const_null_string * padding,
+ const_null_string * guts, const_null_string * pairs,
+ int * prefix_pos, int * suffix_pos) {
+ const char ** run;
+ const char ** test;
+ const char ** opening;
+ const char ** closing;
+ int saved_prefix_pos;
+ int definite_prefix_pos;
+ int definite_suffix_pos;
+ bool pair_succeeded;
+ bool pad_succeeded;
+ // Could generate lengths at init time for speed
+ int l;
+ int ll;
+ // If we succeed for one thing, we try it again straight away,
+ // as a number of types of padding are likely to occur in multiples.
+ // Then we keep trying the whole lot until nothing is left to do.
+ do {
+ // Try each piece of padding (or guts)
+ definite_prefix_pos=EOF;
+ definite_suffix_pos=EOF;
+ run=padding;
+ do {
+ do {
+ pad_succeeded=false;
+ test=run;
+ while (*test!=NULL) {
+ l=(int)strlen(*test);
+ for (;;) {
+ // Check for padding at tail
+ if (buffer_filled-*suffix_pos<l) break;
+ callback_compare=*test;
+ (void)look(comparing_head,*suffix_pos,false);
+ if (!callback_bool) break;
+ *suffix_pos+=l;
+ pad_succeeded=true;
+ }
+ for (;;) {
+ // Check for padding at head
+ if (*prefix_pos-l<0) break;
+ callback_compare=*test;
+ (void)look(comparing_head,*prefix_pos-l,false);
+ if (!callback_bool) break;
+ *prefix_pos-=l;
+ pad_succeeded=true;
+ }
+ ++test;
+ }
+ } while (pad_succeeded);
+ if (definite_prefix_pos==EOF) {
+ // Do a second run to deal with guts in a more tentative way
+ definite_prefix_pos=*prefix_pos;
+ definite_suffix_pos=*suffix_pos;
+ run=guts;
+ } else break;
+ } while (true);
+ // Try each pair
+ pair_succeeded=false;
+ closing=pairs;
+ while (*closing!=NULL) {
+ l=(int)strlen(*closing);
+ opening=closing+1;
+ // This loop is just to be broken out of; we don't try
+ // pairs twice when they succeed as they aren't too likely
+ // to nest.
+ do {
+ // Check for closing part
+ if (buffer_filled-*suffix_pos<l) break;
+ callback_compare=*closing;
+ (void)look(comparing_head,*suffix_pos,false);
+ if (!callback_bool) break;
+ // Check for end of opening part
+ ll=(int)strlen(*opening);
+ if (*prefix_pos-ll<0) break;
+ callback_compare=*opening;
+ (void)look(comparing_head,*prefix_pos-ll,false);
+ if (!callback_bool) break;
+ saved_prefix_pos=*prefix_pos;
+ // Try each variant
+ for (++opening;*opening!=NULL;
+ *prefix_pos=saved_prefix_pos,++opening) {
+ // Search back for first character
+ ll=(int)strlen(*opening);
+ if (*prefix_pos-ll<0) continue;
+ *prefix_pos-=ll;
+ callback_match=(int)**opening;
+ if (lookback(until_match,*prefix_pos,true)) {
+ // Check if this is the variant
+ callback_compare=*opening;
+ (void)look(comparing_head,*prefix_pos,false);
+ if (!callback_bool) continue;
+ *suffix_pos+=l;
+ pair_succeeded=true;
+ break;
+ }
+ }
+ } while (false);
+ // Skip all the variants if not skipped already
+ while (*opening!=NULL) ++opening;
+ closing=opening+1;
+ }
+ if (!pair_succeeded) {
+ // If the pair didn't succeed, we don't remove the guts
+ *prefix_pos=definite_prefix_pos;
+ *suffix_pos=definite_suffix_pos;
+ }
+ } while (pair_succeeded);
+}
+static inline void mark_tail(const_null_string * padding) {
+ // This is basically pad() with deletions, but adding the two lines
+ // at the start to initially place the mark. Comments at pad() apply.
+ bool something_changed;
+ const char ** test;
+ int l;
+ buffer_mark=buffer_read;
+ buffer_marked=true;
+ do {
+ something_changed=false;
+ // Try each piece of padding
+ test=padding;
+ while (*test!=NULL) {
+ l=(int)strlen(*test);
+ for (;;) {
+ // Check for padding at head
+ if (buffer_mark-l<0) break;
+ callback_compare=*test;
+ (void)look(comparing_head,buffer_mark-l,false);
+ if (!callback_bool) break;
+ buffer_mark-=l;
+ something_changed=true;
+ }
+ ++test;
+ }
+ } while (something_changed);
+}
+static inline void encode_footer(const char * footer) {
+ while (*footer!='\0') {
+ if (*footer=='\n') encodechar((int)'\r');
+ encodechar((int)(unsigned int)*footer);
+ footer++;
+ }
+}
+
+/* tag: buffer_functions */
+
+// The first few of these handle replacements, the rest not.
+static inline void read_buffer() {
+ buffer_read=buffer_filled;
+}
+static inline void echo_buffer() {
+ read_buffer();
+ echo_lookbehind();
+}
+static inline void skip_buffer() {
+ if (buffer_filled>0) (void)empty(until_no_buffer);
+}
+static inline void echo_lookbehind() {
+ make_replacements(echoing_one_char,echoing_until_start_marked);
+ if (buffer_read>0) (void)empty(echoing_until_no_lookbehind);
+}
+static inline void encode_lookbehind() {
+ make_replacements(encoding_one_char,encoding_until_start_marked);
+ if (buffer_read>0) (void)empty(encoding_until_no_lookbehind);
+}
+static inline void encode_replacements() {
+ make_replacements(encoding_one_char,encoding_until_start_marked);
+}
+static inline void make_replacements(callback_t one_char,
+ callback_t start_marked) {
+ int r, minr=0;
+ const char * c;
+ if (buffer_read==0) return;
+ buffer_marked=false;
+ while (replacements_count>0) {
+ for (r=0;r<replacements_count;++r) {
+ if (!buffer_marked||replacement_starts[r]<buffer_mark) {
+ buffer_marked=true;
+ minr=r;
+ buffer_mark=replacement_starts[r];
+ }
+ }
+ for (r=0;r<replacements_count;++r) {
+ replacement_starts[r]-=buffer_mark;
+ replacement_ends[r]-=buffer_mark;
+ }
+ if (buffer_mark>0) (void)empty(start_marked);
+ c = replacement_strings[minr];
+ if (c!=NULL) {
+ while (*c!='\0') {
+ buffer_char=(int)(unsigned int)*c;
+ (void)(*one_char)();
+ ++c;
+ }
+ }
+ buffer_marked=true;
+ buffer_mark=replacement_ends[minr];
+ for (r=0;r<replacements_count;++r) {
+ replacement_starts[r]-=buffer_mark;
+ replacement_ends[r]-=buffer_mark;
+ }
+ if (buffer_mark>0) (void)empty(until_start_marked);
+ for (r=minr;r<replacements_count-1;++r) {
+ replacement_starts[r]=replacement_starts[r+1];
+ replacement_ends[r]=replacement_ends[r+1];
+ replacement_strings[r]=replacement_strings[r+1];
+ }
+ --replacements_count;
+ buffer_marked=false;
+ }
+}
+static inline void encode_to_mark() {
+ if (buffer_mark>0) (void)empty(encoding_until_start_marked);
+}
+static inline void echo_disk_buffer() {
+ if (disk_buffer_filled>0) (void)empty(echoing_until_no_disk_buffer);
+}
+static inline void encode_disk_buffer() {
+ if (disk_buffer_filled>0) (void)empty(encoding_until_no_disk_buffer);
+}
+static inline void skip_disk_buffer() {
+ if (disk_buffer_filled>0) (void)empty(until_no_disk_buffer);
+}
+static inline void read_boundary(/*@out@*/ char ** boundary) {
+ int l=0;
+ if (buffer_filled>buffer_read) {
+ callback_bool=false;
+ callback_int=0;
+ resort_to_exit(!look(counting_until_eol,buffer_read,false),
+ "internal error: missing eol at section boundary",EX_SOFTWARE);
+ l=callback_int-2; // remove the CRLF, but keep the leading '--'
+ }
+ // Leave room to append a trailing '--' for testing final boundary;
+ // the CRLF will be written in this space by saving_until_eol too.
+ *boundary = alloc_or_exit(sizeof(char)*(l+3));
+ if (buffer_filled>buffer_read) {
+ callback_bool=false;
+ callback_save=*boundary;
+ (void)look(saving_until_eol,buffer_read,false);
+ callback_save=NULL;
+ }
+ (*boundary)[l]='\0';
+ if (buffer_filled>buffer_read) {
+ callback_bool=false;
+ (void)look(until_eol,buffer_read,true);
+ }
+}
+static inline void echo_to_boundary(const char * boundary) {
+ do {
+ echo_buffer();
+ } while (!process_one_line_checking_boundary(
+ echoing_n_chars,NULL,until_eol,echo,boundary));
+}
+static inline void skip_to_boundary(const char * boundary) {
+ do {
+ skip_buffer();
+ } while (!process_one_line_checking_boundary(
+ n_chars,NULL,until_eol,discard,boundary));
+}
+static inline void decode_and_read_to_boundary_encoding_when_full(
+ const char * boundary) {
+ do {
+ read_buffer();
+ } while (!process_one_line_checking_boundary(
+ encoding_n_chars,decode_lookahead,
+ decoding_until_eol,encode,boundary));
+ finish_decoding(); // This just sets state, doesn't change data
+}
+static inline bool process_one_line_checking_boundary(callback_t n_chars,
+ /*@null@*/ function_t process, callback_t processing,
+ when_full_t when_full, const char * boundary) {
+ bool stopped_by_design;
+ if (feof(stdin)!=0) {
+ // We're done! Call it a boundary (even if it isn't--we need to
+ // get out of loops cleanly and tidy up as best we can).
+ return true;
+ }
+ // Empty until enough space for boundary
+ if (mem_buffer_size-mem_buffer_filled<80) {
+ callback_int=80-(mem_buffer_size-mem_buffer_filled);
+ (void)empty(n_chars);
+ }
+ callback_bool=false;
+ stopped_by_design=fill(until_eol,stop);
+ if (stopped_by_design||feof(stdin)!=0) {
+ if (buffer_filled-buffer_read==0) {
+ return *boundary=='\0';
+ }
+ callback_bool=false;
+ if (*boundary!='\0') {
+ // Can only be at a boundary without being at EOF if there
+ // really is a boundary
+ /*@-temptrans@*/
+ callback_compare=boundary;
+ /*@=temptrans@*/
+ (void)look(comparing_head,buffer_read,false);
+ callback_compare=NULL;
+ }
+ if (!callback_bool&&process!=NULL) (*process)();
+ return callback_bool;
+ } else {
+ // Line is too long to be a boundary, so must be decoded
+ if (process!=NULL) (*process)();
+ callback_bool=false;
+ (void)fill(processing,when_full);
+ return false;
+ }
+}
+
+// Return the position of text whose start may occur in the buffer
+// anywhere between from and (just before) to. Use EOF for from to
+// go from current location; use EOF for to to read indefinitely;
+// EOF is returned if text is not found.
+static int pos_of(const char * text,int from,int to) {
+ int saved_buffer_read;
+ int pos=EOF;
+ if (*text=='\0') return from;
+ saved_buffer_read=buffer_read;
+ if (from!=EOF) buffer_read=from;
+ callback_match=(int)(unsigned int)*text;
+ for (;;) {
+ if (to!=EOF) {
+ callback_int=to-buffer_read;
+ if (!look(n_chars_until_match,buffer_read,true)) break;
+ } else {
+ if (!look(until_match,buffer_read,true)) break;
+ }
+ if (!callback_bool) break;
+ /*@-temptrans@*/
+ callback_compare=text+1;
+ /*@=temptrans@*/
+ (void)look(comparing_head,buffer_read,false);
+ callback_compare=NULL;
+ if (callback_bool) {
+ // Include the first character
+ pos=buffer_read-1;
+ break;
+ }
+ }
+ buffer_read=saved_buffer_read;
+ return pos;
+}
+
+// Look at characters in the buffer, starting at offset from,
+// 'reading' if so indicated (and looking at that location).
+// The callback is called after updating the reading pointer
+// and placing the character in the buffer. The character is
+// also passed by means of the buffer_char global.
+// EOF is sent to the callback when we run out of data.
+// There is no automatic attempt to fill the buffer.
+// The callback should return a boolean indicating whether
+// to continue. This function will return true if the callback
+// indicated to stop (including if it so indicated on EOF), or
+// false if it stopped for EOF.
+// We always call the callback at least once, so don't call
+// this function at all unless you definitely want to look
+// at something.
+static bool look(callback_t callback,int from,bool read) {
+ int pos=from;
+ int disk_buffer_pos;
+ char * mem_buffer_pos;
+ if (pos<disk_buffer_filled) {
+ disk_buffer_pos=disk_buffer_start+pos;
+ if (disk_buffer_sought!=disk_buffer_pos) {
+ /*@-nullpass@*/
+ resort_to_errno(fseek(disk_buffer,disk_buffer_pos,SEEK_SET)!=0,
+ "error seeking in temporary file",EX_IOERR);
+ /*@=nullpass@*/
+ disk_buffer_sought=disk_buffer_pos;
+ }
+ while (pos<disk_buffer_filled) {
+ /*@-nullpass@*/
+ buffer_char=getc(disk_buffer);
+ /*@=nullpass@*/
+ resort_to_errno(buffer_char==EOF,
+ "error reading temporary file",EX_IOERR);
+ // ++disk_buffer_pos; logically this happens, but it is unnecessary
+ ++disk_buffer_sought;
+ if (read&&pos==buffer_read) ++buffer_read;
+ ++pos;
+ if (!(*callback)()) return true;
+ }
+ }
+ if (pos<buffer_filled) {
+ mem_buffer_pos=mem_buffer_next_empty+(pos-disk_buffer_filled);
+ if (mem_buffer_pos>=mem_buffer_end) mem_buffer_pos-=mem_buffer_size;
+ while (pos<buffer_filled) {
+ buffer_char=(int)(unsigned int)*mem_buffer_pos;
+ ++mem_buffer_pos;
+ if (mem_buffer_pos==mem_buffer_end) mem_buffer_pos=mem_buffer_start;
+ if (read&&pos==buffer_read) ++buffer_read;
+ ++pos;
+ if (!(*callback)()) return true;
+ }
+ }
+ buffer_char=EOF;
+ if (!(*callback)()) return true;
+ return false;
+}
+// Does the same backwards, moving the mark if so indicated (and
+// looking at that location). The callback is called before the
+// mark is moved, though, and if it returns false, the mark is
+// not moved, but the function returns true immediately.
+// There is no call to the callback with EOF when we get to the
+// start of the buffer, so the function always returns false in
+// that case, and unmarks the buffer. Again, the callback is
+// always called at least once.
+static bool lookback(callback_t callback,int from,bool mark) {
+ int pos=from;
+ int disk_buffer_pos;
+ char * mem_buffer_pos;
+ if (pos>=disk_buffer_filled) {
+ mem_buffer_pos=mem_buffer_next_empty+(pos-disk_buffer_filled);
+ if (mem_buffer_pos>=mem_buffer_end) mem_buffer_pos-=mem_buffer_size;
+ while (pos>=disk_buffer_filled) {
+ buffer_char=(int)(unsigned int)*mem_buffer_pos;
+ if (!(*callback)()) return true;
+ --mem_buffer_pos;
+ if (mem_buffer_pos==mem_buffer_start-1)
+ mem_buffer_pos=mem_buffer_end-1;
+ if (mark&&pos==buffer_mark) --buffer_mark;
+ --pos;
+ }
+ }
+ if (pos>=0&&disk_buffer_filled>0) {
+ disk_buffer_pos=disk_buffer_start+pos;
+ // Reading backwards in the disk buffer is potentially very nasty;
+ // hopefully it never actually happens
+ while (pos>=0) {
+ /*@-nullpass@*/
+ resort_to_errno(fseek(disk_buffer,disk_buffer_pos,SEEK_SET)!=0,
+ "error seeking in temporary file",EX_IOERR);
+ disk_buffer_sought=disk_buffer_pos;
+ buffer_char=getc(disk_buffer);
+ /*@=nullpass@*/
+ resort_to_errno(buffer_char==EOF,
+ "error reading temporary file",EX_IOERR);
+ ++disk_buffer_sought;
+ if (!(*callback)()) return true;
+ --disk_buffer_pos;
+ if (mark&&pos==buffer_mark) --buffer_mark;
+ --pos;
+ }
+ }
+ if (mark&&buffer_mark==-1) {
+ buffer_mark=0;
+ buffer_marked=false;
+ }
+ // We don't call the callback on EOF when going backwards
+ // buffer_char=EOF;
+ // (void)(*callback)();
+ return false;
+}
+// Remove characters from the (beginning of the) buffer. The same
+// general principles as for look() apply. The callback is called
+// after the character is removed and all accounting has been done, so
+// perhaps the only place you can reliably find the character is in
+// the buffer_char global. Again the callback gets an EOF call if
+// there's nothing more to empty, and no automatic filling is done.
+// The callback and function return values are as for look() and
+// again, the callback is always called at least once; this means at
+// least one character is always removed from the buffer, so only call
+// the function if something definitely should be removed.
+static bool empty(callback_t callback) {
+ if (disk_buffer_filled>0) {
+ if (disk_buffer_sought!=disk_buffer_start) {
+ /*@-nullpass@*/
+ resort_to_errno(fseek(disk_buffer,disk_buffer_start,SEEK_SET)!=0,
+ "error seeking in temporary file",EX_IOERR);
+ /*@=nullpass@*/
+ disk_buffer_sought=disk_buffer_start;
+ }
+ while (disk_buffer_filled>0) {
+ /*@-nullpass@*/
+ buffer_char=getc(disk_buffer);
+ /*@=nullpass@*/
+ resort_to_errno(buffer_char==EOF,
+ "error reading temporary file",EX_IOERR);
+ ++disk_buffer_sought;
+ ++disk_buffer_start;
+ --disk_buffer_filled;
+ --buffer_filled;
+ if (buffer_read>0) --buffer_read;
+ if (buffer_marked) {
+ if (buffer_mark>0) --buffer_mark;
+ else buffer_marked=false;
+ }
+ if (!(*callback)()) return true;
+ }
+ }
+ while (mem_buffer_filled>0) {
+ buffer_char=(int)(unsigned int)*mem_buffer_next_empty;
+ ++mem_buffer_next_empty;
+ if (mem_buffer_next_empty==mem_buffer_end) mem_buffer_next_empty=mem_buffer_start;
+ --mem_buffer_filled;
+ --buffer_filled;
+ if (buffer_read>0) --buffer_read;
+ if (buffer_marked) {
+ if (buffer_mark>0) --buffer_mark;
+ else buffer_marked=false;
+ }
+ if (!(*callback)()) return true;
+ }
+ buffer_char=EOF;
+ if (!(*callback)()) return true;
+ return false;
+}
+// Get more characters into the (end of the) buffer. The same
+// general principles as for look() apply. The callback is called
+// after the character is added and all accounting has been done,
+// gets the character via buffer_char, including an EOF when no more
+// input is available (EOF on stdin). It should return whether to get
+// more characters, and this function will return whether its exit was
+// requested by the callback or not (the callback may signal EOF is
+// an appropriate place to stop and we still return true).
+// When the buffer is full there are a number of automatic options
+// echo old the data to stdout or call encodechar for it one character
+// at a time; shunt a block off to disk, keeping mem_buffer_keep in
+// memory, discard it a character at a time, stop (and return false;
+// no EOF call is made), or fail (exit). Here 'full' is defined as
+// less than mem_buffer_margin of space after adding the most recent
+// character, so there is always a bit of space for callbacks to do
+// input transformations. Again, at least one character is always
+// added (if possible), and thus consumed from stdin, so only call this
+// if you really want to do that.
+static bool fill(callback_t callback, when_full_t when_full) {
+ if (feof(stdin)!=0) {
+ buffer_char=EOF;
+ if (!(*callback)()) return true;
+ return false;
+ }
+ for (;;) {
+ /*@-infloops@*/
+ while (mem_buffer_filled>=mem_buffer_size-mem_buffer_margin) {
+ switch (when_full) {
+ case echo:
+ if (disk_buffer_filled>0) echo_disk_buffer();
+ (void)empty(echoing_one_char);
+ break;
+ case encode:
+ if (disk_buffer_filled>0) encode_disk_buffer();
+ (void)empty(encoding_one_char);
+ break;
+ case discard:
+ if (disk_buffer_filled>0) skip_disk_buffer();
+ (void)empty(one_char);
+ break;
+ case shunt:
+ shunt_to_disk(mem_buffer_filled-mem_buffer_keep);
+ break;
+ case stop:
+ return false;
+ case fail: default:
+ resort_to_exit(true,"buffer full",EX_SOFTWARE);
+ }
+ }
+ /*@=infloops@*/
+ buffer_char=get();
+ if (buffer_char==EOF) {
+ resort_to_errno(ferror(stdin)!=0,"error reading input",EX_IOERR);
+ if (!(*callback)()) return true;
+ return false;
+ }
+ *mem_buffer_next_fill=(char)buffer_char;
+ ++mem_buffer_next_fill;
+ if (mem_buffer_next_fill==mem_buffer_end) mem_buffer_next_fill=mem_buffer_start;
+ ++mem_buffer_filled;
+ ++buffer_filled;
+ if (!(*callback)()) return true;
+ }
+}
+
+static inline void create_disk_buffer() {
+ int fildes;
+ fildes=mkstemp(disk_buffer_template);
+ resort_to_errno(fildes==-1,
+ "cannot create temporary file",EX_CANTCREAT);
+ disk_buffer=fdopen(fildes,"rw");
+ resort_to_errno(disk_buffer==NULL,
+ "cannot create temporary stream",EX_CANTCREAT);
+}
+static void remove_disk_buffer() {
+ if (disk_buffer!=NULL) {
+ resort_to_warning(fclose(disk_buffer)!=0,
+ "error closing temporary file");
+ disk_buffer=NULL;
+ resort_to_warning(unlink(disk_buffer_template)!=0,
+ "error removing temporary file");
+ }
+}
+static inline void shunt_to_disk(int n) {
+ if (disk_buffer==NULL) create_disk_buffer();
+ if (disk_buffer_sought!=disk_buffer_start+disk_buffer_filled) {
+ disk_buffer_sought=disk_buffer_start+disk_buffer_filled;
+ /*@-nullpass@*/
+ resort_to_errno(fseek(disk_buffer,
+ disk_buffer_start+disk_buffer_filled,SEEK_SET)!=0,
+ "cannot seek to end of temporary file",EX_IOERR);
+ /*@=nullpass@*/
+ }
+ while (n>0) {
+ resort_to_exit(mem_buffer_filled==0,
+ "internal error: shunting too much to disk",EX_SOFTWARE);
+ /*@-nullpass@*/
+ resort_to_errno(putc(*mem_buffer_next_empty,disk_buffer)==EOF,
+ "error writing to temporary file",EX_IOERR);
+ /*@=nullpass@*/
+ ++disk_buffer_sought;
+ ++disk_buffer_filled;
+ ++mem_buffer_next_empty;
+ if (mem_buffer_next_empty==mem_buffer_end) mem_buffer_next_empty=mem_buffer_start;
+ --mem_buffer_filled;
+ --n;
+ }
+}
+
+/* tag: callback_functions */
+
+static bool one_char() {
+ callback_int=buffer_char;
+ return false;
+}
+static bool echoing_one_char() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ callback_int=buffer_char;
+ return false;
+}
+static bool encoding_one_char() {
+ if (buffer_char!=EOF) encodechar(buffer_char);
+ callback_int=buffer_char;
+ return false;
+}
+// Set up callback_int before using this.
+static bool n_chars() {
+ return --callback_int>0;
+}
+// Set up callback_int before using this.
+static bool echoing_n_chars() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ return --callback_int>0;
+}
+// Set up callback_int before using this.
+static bool encoding_n_chars() {
+ if (buffer_char!=EOF) encodechar(buffer_char);
+ return --callback_int>0;
+}
+// Set up callback_int and callback_save before using this.
+static bool saving_n_chars() {
+ if (buffer_char!=EOF) *callback_save++=(char)buffer_char;
+ // We don't actually need this, though it's a good idea, really!
+ // *callback_save='\0';
+ return --callback_int>0;
+}
+// Set up callback_int and callback_match before using this.
+static bool n_chars_until_match() {
+ callback_bool=buffer_char==callback_match;
+ return --callback_int>0&&buffer_char!=callback_match;
+}
+// Do callback_bool=false before using this.
+static bool until_eol() {
+ if (buffer_char==(int)'\n') return !callback_bool;
+ callback_bool=buffer_char==(int)'\r';
+ return true;
+}
+// Do callback_bool=false before using this.
+/*static bool echoing_until_eol() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ if (buffer_char==(int)'\n') return !callback_bool;
+ callback_bool=buffer_char==(int)'\r';
+ return true;
+}*/
+// Do callback_bool=false, callback_int=0 before using this.
+static bool counting_until_eol() {
+ if (buffer_char!=EOF) ++callback_int;
+ if (buffer_char==(int)'\n') return !callback_bool;
+ callback_bool=buffer_char==(int)'\r';
+ return true;
+}
+// Do callback_bool=false and set up callback_save before using this.
+static bool saving_until_eol() {
+ if (buffer_char!=EOF) *callback_save++=(char)buffer_char;
+ // We don't actually need this, though it's a good idea, really!
+ // *callback_save='\0';
+ if (buffer_char==(int)'\n') return !callback_bool;
+ callback_bool=buffer_char==(int)'\r';
+ return true;
+}
+// Do callback_bool=false before using this.
+static bool decoding_until_eol() {
+ // We decode as we fill and work directly in the buffer to make
+ // the transformation. We are guaranteed enough space to do this by
+ // mem_buffer_margin.
+ decode_t decoded;
+ decoded=decodechar(buffer_char);
+ // We always remove the latest undecoded character from the
+ // buffer.
+ ++decoded.r;
+ if (decoded.r>mem_buffer_filled) {
+ // This will only happen for quoted-printable decoding
+ // whitespace stripping, and we can just live with it
+ // if we can't get rid of it all; with sensible constants
+ // something really is disobeying MIME and probably SMTP
+ // about line length anyway if this happens.
+ warning("unable to strip all whitespace; not enough in memory");
+ decoded.r=mem_buffer_filled;
+ }
+ if (buffer_filled-decoded.r<buffer_read) {
+ // We should always be working in lookahead when this happens,
+ // but better safe than sorry!
+ warning("unable to strip all whitespace; not enough unread");
+ decoded.r=buffer_filled-buffer_read;
+ }
+ if (buffer_marked&&buffer_filled-decoded.r<buffer_mark) {
+ // Marks should be in lookbehind too, but again,
+ // better safe than sorry! We unmark. Filling often
+ // does that anyway.
+ buffer_marked=false;
+ buffer_mark=0;
+ }
+ mem_buffer_next_fill-=decoded.r;
+ if (mem_buffer_next_fill<mem_buffer_start)
+ mem_buffer_next_fill+=mem_buffer_size;
+ mem_buffer_filled-=decoded.r;
+ buffer_filled-=decoded.r;
+ if (decoded.c1!=EOF) {
+ *mem_buffer_next_fill=(char)decoded.c1;
+ ++mem_buffer_next_fill;
+ if (mem_buffer_next_fill==mem_buffer_end) mem_buffer_next_fill=mem_buffer_start;
+ ++mem_buffer_filled;
+ ++buffer_filled;
+ if (decoded.c2!=EOF) {
+ *mem_buffer_next_fill=(char)decoded.c2;
+ ++mem_buffer_next_fill;
+ if (mem_buffer_next_fill==mem_buffer_end) mem_buffer_next_fill=mem_buffer_start;
+ ++mem_buffer_filled;
+ ++buffer_filled;
+ if (decoded.c3!=EOF) {
+ *mem_buffer_next_fill=(char)decoded.c3;
+ ++mem_buffer_next_fill;
+ if (mem_buffer_next_fill==mem_buffer_end) mem_buffer_next_fill=mem_buffer_start;
+ ++mem_buffer_filled;
+ ++buffer_filled;
+ }
+ }
+ }
+ // We check for eol using the input stream, not the decoded
+ // stream, as it's all about the upcoming boundary
+ if (buffer_char==(int)'\n') return !callback_bool;
+ callback_bool=buffer_char==(int)'\r';
+ return true;
+}
+/*static bool until_no_lookbehind() {
+ return buffer_read!=0;
+}*/
+static bool echoing_until_no_lookbehind() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ return buffer_read!=0;
+}
+static bool encoding_until_no_lookbehind() {
+ if (buffer_char!=EOF) encodechar(buffer_char);
+ return buffer_read!=0;
+}
+static bool until_no_disk_buffer() {
+ return disk_buffer_filled!=0;
+}
+static bool echoing_until_no_disk_buffer() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ return disk_buffer_filled!=0;
+}
+static bool encoding_until_no_disk_buffer() {
+ if (buffer_char!=EOF) encodechar(buffer_char);
+ return disk_buffer_filled!=0;
+}
+static bool until_no_buffer() {
+ return buffer_filled!=0;
+}
+/*static bool echoing_until_no_buffer() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ return buffer_filled!=0;
+}*/
+static bool until_start_marked() {
+ return !(buffer_marked?buffer_mark==0:buffer_read==0);
+}
+static bool echoing_until_start_marked() {
+ if (buffer_char!=EOF) {
+ resort_to_errno(put(buffer_char)==EOF,"error echoing",EX_IOERR);
+ }
+ return !(buffer_marked?buffer_mark==0:buffer_read==0);
+}
+static bool encoding_until_start_marked() {
+ if (buffer_char!=EOF) encodechar(buffer_char);
+ return !(buffer_marked?buffer_mark==0:buffer_read==0);
+}
+// Set up callback_match before using this.
+static bool until_match() {
+ return buffer_char!=callback_match;
+}
+// Set up callback_compare before using this.
+static bool comparing_head() {
+ /*@-nullderef@*/
+ if (buffer_char!=(int)(unsigned int)*callback_compare) {
+ callback_bool=false;
+ return false;
+ }
+ /*@-modobserver@*/
+ ++callback_compare;
+ /*@=modobserver@*/
+ if (*callback_compare=='\0') {
+ callback_bool=true;
+ return false;
+ }
+ return true;
+ /*@=nullderef@*/
+}
+// Set up callback_compare before using this.
+static bool case_insensitively_comparing_head() {
+ /*@-nullderef@*/
+ int c1=(int)(unsigned int)*callback_compare;
+ int c2=buffer_char;
+ if (c1!=c2&&
+ (c1<(int)'A'||c1>(int)'Z'||c2!=c1-(int)'A'+(int)'a')&&
+ (c2<(int)'A'||c2>(int)'Z'||c1!=c2-(int)'A'+(int)'a')) {
+ callback_bool=false;
+ return false;
+ }
+ /*@-modobserver@*/
+ ++callback_compare;
+ /*@=modobserver@*/
+ if (*callback_compare=='\0') {
+ callback_bool=true;
+ return false;
+ }
+ return true;
+ /*@=nullderef@*/
+}
+
+/* tag: encoding_functions */
+
+static inline void encode_string(const char * s) {
+ while (*s!='\0') {
+ encodechar((int)(unsigned int)*s);
+ s++;
+ }
+}
+static void encodechar(int c) {
+ if (encoding==unencoded) {
+ if (c!=EOF) resort_to_errno(put(c)==EOF,"error encoding",EX_IOERR);
+ return;
+ } else if (encoding==quoted_printable) {
+ if (encoding_echoed>=68) {
+ // We need a soft line break, or are close enough to needing
+ // one (76 chars max; unclear whether that counts the CRLF; and
+ // we may output two 3 character sequences which we don't want
+ // to follow with an unescaped CRLF). This scheme will probably
+ // make mail look a bit awful, but that's fairly standard anyway,
+ // and it shouldn't degrade.
+ resort_to_errno(putstr("=\r\n")==EOF,
+ "error encoding string",EX_IOERR);
+ encoding_echoed=0;
+ }
+ if (encoding_filled==1) {
+ // Whatever happens, we'll deal with this now
+ encoding_filled=0;
+ if (encoding_buffer[0]=='\r') {
+ if (c==(int)'\n') {
+ // Output them as is and we're done for now
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error encoding string",EX_IOERR);
+ encoding_echoed=0;
+ return;
+ } else {
+ // Must encode the bare CR and continue as normal
+ resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR);
+ encode_hex_byte((unsigned int)'\r');
+ encoding_echoed+=3;
+ }
+ } else {
+ // encoding_buffer[0] must be whitespace
+ if (c==EOF||c==(int)'\r') {
+ // Must encode it
+ resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR);
+ encode_hex_byte((unsigned int)encoding_buffer[0]);
+ encoding_echoed+=3;
+ } else {
+ // It is fine to output it now as something else is coming
+ resort_to_errno(put(
+ (int)(unsigned int)encoding_buffer[0])==EOF,
+ "error encoding",EX_IOERR);
+ encoding_echoed+=1;
+ }
+ }
+ }
+ if ((c>=33&&c<=60)||(c>=62&&c<=126)) {
+ resort_to_errno(put(c)==EOF,"error encoding",EX_IOERR);
+ ++encoding_echoed;
+ } else if (c==(int)' '||c==(int)'\t') {
+ if (encoding_echoed>=55) {
+ // My concession to readability; since it's likely to be
+ // a big mess with a 68 character width, we might as well
+ // break a bit earlier on a nice word boundary. And it'll
+ // in fact look better if we break with roughly equal size
+ // lines, assuming they come in at close to 76 characters
+ // wide, so we might as well make a nice skinny column.
+ // rather than a ragged one that uses the same amount of
+ // space. Compromising between the two, then, as some
+ // formats, like HTML, don't have many hard line breaks
+ // anyway, is what we get.
+ resort_to_errno(put(c)==EOF,"error encoding",EX_IOERR);
+ resort_to_errno(putstr("=\r\n")==EOF,
+ "error encoding string",EX_IOERR);
+ encoding_echoed=0;
+ } else {
+ // Store it; we may need to encode it if it's at end of line
+ encoding_filled=1;
+ encoding_buffer[0]=(char)c;
+ }
+ } else if (c==(int)'\r') {
+ // Store it; '\n' may be coming up
+ encoding_filled=1;
+ encoding_buffer[0]='\r';
+ } else if (c==EOF) {
+ // No buffer, and we're done! Reset for another run.
+ encoding_echoed=0;
+ } else {
+ // Anything else must be encoded as a sequence.
+ resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR);
+ encode_hex_byte((unsigned int)c);
+ encoding_echoed+=3;
+ }
+ } else if (encoding==base64) {
+ if (c==EOF) {
+ // Reset for next run; we won't need it here
+ encoding_echoed=0;
+ if (encoding_filled==0) return;
+ encoding_buffer[encoding_filled]='\0';
+ } else {
+ encoding_buffer[encoding_filled++]=(char)c;
+ }
+ if (encoding_filled==3||c==EOF) {
+ encode_64((((unsigned int)encoding_buffer[0]>>2)&0x3f));
+ encode_64((((unsigned int)encoding_buffer[0]&0x03)<<4)|
+ (((unsigned int)encoding_buffer[1]>>4)&0x0f));
+ if (encoding_filled==1) {
+ resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR);
+ resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR);
+ // Reset for next run
+ encoding_filled=0;
+ return;
+ }
+ encode_64((((unsigned int)encoding_buffer[1]&0x0f)<<2)|
+ (((unsigned int)encoding_buffer[2]>>6)&0x03));
+ if (encoding_filled==2) {
+ resort_to_errno(put((int)'=')==EOF,"error encoding",EX_IOERR);
+ // Reset for next run
+ encoding_filled=0;
+ return;
+ }
+ encode_64((((unsigned int)encoding_buffer[2]&0x3f)));
+ encoding_echoed+=4;
+ if (encoding_echoed>=72) {
+ resort_to_errno(putstr("\r\n")==EOF,
+ "error encoding string",EX_IOERR);
+ encoding_echoed=0;
+ }
+ encoding_filled=0;
+ }
+ } else {
+ resort_to_exit(true,"internal error: unknown encoding",EX_SOFTWARE);
+ }
+}
+static inline void finish_encoding() {
+ encodechar(EOF);
+}
+// The function takes an input character c and returns up to four output
+// characters (a character will be EOF to indicate no further characters
+// to store; note that this doesn't mean there will be no more ever; only
+// if EOF is returned when EOF was input does it meant this), and a number
+// of characters to remove before adding the aforementioned characters.
+static decode_t decodechar(int c) {
+ int h;
+ unsigned int b1, b2, b3, b4;
+ decode_t o;
+ o.r=0; o.c1=EOF; o.c2=EOF; o.c3=EOF;
+ if (decoding==unencoded) {
+ o.c1=c;
+ return o;
+ } else if (decoding==quoted_printable) {
+ // decoding_buffer may hold '=' and maybe a hex digit or a CR.
+ if (decoding_filled==2) {
+ // Whatever happens, it's all settled now.
+ decoding_filled=0;
+ if (decoding_buffer[1]=='\r') {
+ if (c==(int)'\n') { return o; }
+ // Invalid; leave as is--will be encoded later.
+ o.c1=(int)'='; o.c2=(int)'\r'; o.c3=c;
+ return o;
+ }
+ h=decode_hex(c);
+ if (h==EOF) {
+ // Invalid; leave as is--will be encoded later.
+ o.c1=(int)'='; o.c2=(int)(unsigned int)decoding_buffer[1]; o.c3=c;
+ return o;
+ }
+ // We have a full sequence representing a single character.
+ o.c1=decode_hex((int)(unsigned int)decoding_buffer[1])*16+h;
+ return o;
+ } else if (decoding_filled==1) {
+ if (c==(int)'\r'||decode_hex(c)!=EOF) {
+ // Valid character after =
+ decoding_filled=2;
+ decoding_buffer[1]=(char)c;
+ return o;
+ }
+ // Invalid; leave as is--will be encoded later.
+ decoding_filled=0;
+ o.c1=(int)'='; o.c2=c;
+ return o;
+ } else if (decoding_filled==0) {
+ if (c==(int)'=') {
+ // The first character can only ever be '=' so we
+ // don't actually bother to store it; just say it's there.
+ decoding_white=0;
+ decoding_filled=1;
+ return o;
+ }
+ // Keep track of whitespace.
+ if (c==(int)' '||c==(int)'\t') ++decoding_white;
+ else decoding_white=0;
+ // Remove trailing whitespace.
+ if (c==EOF||c==(int)'\r') { o.r=decoding_white; decoding_white=0; }
+ // Otherwise we just keep it. If it's EOF, we're done.
+ o.c1=c;
+ return o;
+ } else {
+ warning("internal error: decoding buffer too full");
+ return o;
+ }
+ } else if (decoding==base64) {
+ if (c==EOF) {
+ // Just in case it was corrupted, make sure we're reset
+ decoding_filled=0;
+ return o;
+ }
+ if (c==(int)'='||decode_64(c)!=EOF)
+ decoding_buffer[decoding_filled++]=(char)c;
+ if (decoding_filled==4) {
+ // We empty it whatever happens here
+ decoding_filled=0;
+ b1=(unsigned int)decode_64((int)decoding_buffer[0]);
+ b2=(unsigned int)decode_64((int)decoding_buffer[1]);
+ o.c1=(int)(((b1&0x3f)<<2)|((b2>>4)&0x03));
+ if (decoding_buffer[2]=='=') return o;
+ b3=(unsigned int)decode_64((int)decoding_buffer[2]);
+ o.c2=(int)(((b2&0x0f)<<4)|((b3>>2)&0x0f));
+ if (decoding_buffer[3]=='=') return o;
+ b4=(unsigned int)decode_64((int)decoding_buffer[3]);
+ o.c3=(int)(((b3&0x03)<<6)|(b4&0x3f));
+ }
+ return o;
+ } else {
+ resort_to_exit(true,"internal error: unknown encoding",EX_SOFTWARE);
+ // Never reached
+ return o;
+ }
+}
+static void decode_lookahead() {
+ // Decoding will always shrink, so this is quite easy
+ char * c;
+ char * cc;
+ decode_t decoded;
+ int pos=buffer_read;
+ int decpos=buffer_read;
+ resort_to_exit(buffer_read<disk_buffer_filled,
+ "internal error: decoding from disk",EX_SOFTWARE);
+ c=mem_buffer_next_empty+pos-disk_buffer_filled;
+ if (c>=mem_buffer_end) c-=mem_buffer_size;
+ cc=c;
+ while (pos<buffer_filled) {
+ decoded=decodechar((int)(unsigned int)*c);
+ if (decoded.r>0) {
+ resort_to_exit(decpos-decoded.r<buffer_read,
+ "internal error: removing more than was decoded",EX_SOFTWARE);
+ decpos-=decoded.r;
+ cc-=decoded.r;
+ if (cc<mem_buffer_start) cc+=mem_buffer_size;
+ }
+ if (decoded.c1!=EOF) {
+ *cc=(char)decoded.c1;
+ ++decpos; ++cc;
+ if (cc==mem_buffer_end) cc=mem_buffer_start;
+ if (decoded.c2!=EOF) {
+ *cc=(char)decoded.c2;
+ ++decpos; ++cc;
+ if (cc==mem_buffer_end) cc=mem_buffer_start;
+ if (decoded.c3!=EOF) {
+ *cc=(char)decoded.c3;
+ ++decpos; ++cc;
+ if (cc==mem_buffer_end) cc=mem_buffer_start;
+ }
+ }
+ }
+ ++pos; ++c;
+ if (c==mem_buffer_end) c=mem_buffer_start;
+ }
+ buffer_filled+=decpos-pos;
+ mem_buffer_filled+=decpos-pos;
+ mem_buffer_next_fill+=decpos-pos;
+ if (mem_buffer_next_fill<mem_buffer_start)
+ mem_buffer_next_fill+=mem_buffer_size;
+}
+static inline void finish_decoding() {
+ // As it will have just experienced a CRLF or an EOF, the only thing
+ // this can do is reset the state if base64 was truncated.
+ // We won't gain any more characters or need to remove anything.
+ // It is important that this is always the case as other routines
+ // rely on it.
+ (void)decodechar(EOF);
+}
+
+// These are slow but easy to write! Lookup tables would be quicker.
+// Still, I think it'll probably be fast enough.
+static inline int decode_hex(int c) {
+ if (c>=(int)'0'&&c<=(int)'9') return c-(int)'0';
+ if (c>=(int)'A'&&c<=(int)'F') return c-(int)'A'+10;
+ return EOF;
+}
+static inline int decode_64(int c) {
+ if (c>=(int)'A'&&c<=(int)'Z') return c-(int)'A';
+ if (c>=(int)'a'&&c<=(int)'z') return c-(int)'a'+26;
+ if (c>=(int)'0'&&c<=(int)'9') return c-(int)'0'+52;
+ if (c==(int)'+') return 62;
+ if (c==(int)'/') return 63;
+ // if (c==(int)'=') return EOF;
+ return EOF;
+}
+static inline void encode_hex_byte(unsigned int h) {
+ int h1=(int)((h>>4)&0x0f);
+ int h2=(int)(h&0x0f);
+ if (h1<10) resort_to_errno(put((int)'0'+h1)==EOF,"error encoding",EX_IOERR);
+ else if (h1<16)
+ resort_to_errno(put((int)'A'+h1-10)==EOF,"error encoding",EX_IOERR);
+ else resort_to_exit(true,"internal error: byte too large",EX_SOFTWARE);
+ if (h2<10) resort_to_errno(put((int)'0'+h2)==EOF,"error encoding",EX_IOERR);
+ else if (h2<16)
+ resort_to_errno(put((int)'A'+h2-10)==EOF,"error encoding",EX_IOERR);
+ else resort_to_exit(true,"internal error: byte too large",EX_SOFTWARE);
+}
+static inline void encode_64(unsigned int b) {
+ if (b<26)
+ resort_to_errno(put((int)'A'+b)==EOF,"error encoding",EX_IOERR);
+ else if (b<52)
+ resort_to_errno(put((int)'a'+b-26)==EOF,"error encoding",EX_IOERR);
+ else if (b<62)
+ resort_to_errno(put((int)'0'+b-52)==EOF,"error encoding",EX_IOERR);
+ else if (b==62)
+ resort_to_errno(put((int)'+')==EOF,"error encoding",EX_IOERR);
+ else if (b==63)
+ resort_to_errno(put((int)'/')==EOF,"error encoding",EX_IOERR);
+ else resort_to_exit(true,
+ "internal error: base64 value too large",EX_SOFTWARE);
+}
+
+/* tag: error_functions */
+
+// Syslog constants:
+// level: LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG
+// facility: LOG_MAIL, LOG_DAEMON, LOG_USER, LOG_LOCALn(0-7)
+
+static inline void * alloc_or_exit(size_t s) /*@allocates result@*/ {
+ void * m;
+ m=malloc(s);
+ if (m==NULL) {
+#ifdef USE_STDERR
+ fprintf(stderr,"foot_filter: %s\n","out of memory");
+#endif
+#ifdef USE_SYSLOG
+ syslog(LOG_ERR|LOG_MAIL,"%s\n","out of memory");
+#endif
+ exit(EX_OSERR);
+ }
+ return m;
+}
+static inline void /*noreturnwhentrue*/
+ resort_to_exit(bool when,const char * message,int status) {
+ if (when) {
+#ifdef USE_STDERR
+ fprintf(stderr,"foot_filter: %s\n",message);
+#endif
+#ifdef USE_SYSLOG
+ syslog(LOG_ERR|LOG_MAIL,"%s\n",message);
+#endif
+ exit(status);
+ }
+}
+static inline void /*noreturnwhentrue*/
+ resort_to_errno(bool when,const char * message,int status) {
+ if (when) {
+#ifdef USE_STDERR
+ fprintf(stderr,"foot_filter: %s (%s)\n",message,strerror(errno));
+#endif
+#ifdef USE_SYSLOG
+ syslog(LOG_ERR|LOG_MAIL,"%s (%m)\n",message);
+#endif
+ exit(status);
+ }
+}
+static inline void resort_to_warning(bool when,const char * message) {
+ if (when) warning(message);
+}
+static inline void warning(const char * message) {
+#ifdef USE_STDERR
+ fprintf(stderr,"foot_filter: %s\n",message);
+#endif
+#ifdef USE_SYSLOG
+ syslog(LOG_WARNING|LOG_MAIL,"%s\n",message);
+#endif
+}
+
+/* tag: helper_functions */
+
+// The program was written following all the specs using CRLF for newlines,
+// but we get them from Postfix with LF only, so these wrapper functions
+// do the translation in such a way that it can easily be disabled if desired.
+static inline int get() {
+ int c;
+#ifdef UNIX_EOL
+ static bool got_nl=false;
+ if (got_nl) {
+ got_nl=false;
+ return 10;
+ }
+#endif
+ c=getchar();
+#ifdef UNIX_EOL
+ if (c==10) {
+ got_nl=true;
+ return 13;
+ }
+#endif
+ return c;
+}
+static inline int put(int c) {
+#ifdef UNIX_EOL
+ if (c==13) return c;
+#endif
+ return putchar(c);
+}
+static inline int putstr(const char * s) {
+ while (*s!='\0') if (put((int)(unsigned int)*s++)==EOF) return EOF;
+ return 0;
+}
+
+static inline bool case_insensitively_heads(const char * head,const char * buffer) {
+ const char * s1=head;
+ const char * s2=buffer;
+ for (;;) {
+ if (*s1=='\0') return true; /* for equality return *s2=='\0'; */
+ else if (*s2=='\0') return false;
+ if (*s1!=*s2&&
+ (*s1<'A'||*s1>'Z'||*s2!=*s1-'A'+'a')&&
+ (*s2<'A'||*s2>'Z'||*s1!=*s2-'A'+'a')) return false;
+ ++s1; ++s2;
+ }
+}
+
--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.