1 #include <bglibs/sysdeps.h>
2 #include <ctype.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <unistd.h>
7 #include <bglibs/iobuf.h>
8 #include <bglibs/msg.h>
9 #include <bglibs/path.h>
10 #include <bglibs/str.h>
11 #include <bglibs/striter.h>
12 #include <bglibs/systime.h>
13 #include "qmail-autoresponder.h"
14 
15 str boundary = {0,0,0};
16 str copyheaders = {0,0,0};
17 str message_id = {0,0,0};
18 str subject = {0,0,0};
19 
20 static const char* dtline;
21 static size_t dtline_len;
22 static str content_type;
23 static str headers;
24 
ignore_ml(const str * s,const char * header)25 static void ignore_ml(const str* s, const char* header)
26 {
27   unsigned hdrlen = strlen(header);
28   if (strncasecmp(s->s, header, hdrlen) == 0 && s->s[hdrlen] == ':') {
29     fail_msg("Ignoring message:");
30     msgf("{Message appears to be from a mailing list (}s{ header)}", header);
31     exit(0);
32   }
33 }
34 
skip_space(const char * s)35 static const char* skip_space(const char* s)
36 {
37   while(*s && isspace(*s))
38     ++s;
39   return s;
40 }
41 
header_copy_if(const str * src,str * dest,const char * prefix,unsigned prefix_len)42 static void header_copy_if(const str* src, str* dest,
43 			   const char* prefix, unsigned prefix_len)
44 {
45   unsigned int lf;
46   unsigned int end;
47 
48   if (src->len > prefix_len
49       && strncasecmp(src->s, prefix, prefix_len) == 0) {
50     str_copyb(dest, src->s + prefix_len, src->len - prefix_len);
51     str_strip(dest);
52     /* Replace embeded newlines followed by variable whitespace with a
53      * single space. */
54     while ((lf = str_findfirst(dest, '\n')) < dest->len) {
55       end = lf + 1;
56       while (lf > 0 && isspace(dest->s[lf-1]))
57 	--lf;
58       while (end < dest->len && isspace(dest->s[end]))
59 	++end;
60       str_spliceb(dest, lf, end - lf, " ", 1);
61     }
62   }
63 }
64 
header_test(const str * h,const char * list)65 static int header_test(const str* h, const char* list)
66 {
67   static str pattern;
68   const char* colon;
69   size_t len;
70 
71   do {
72     len = ((colon = strchr(list, ':')) == 0)
73       ? strlen(list)
74       : (size_t)(colon - list);
75     str_copyb(&pattern, list, len);
76     str_cats(&pattern, ": *");
77     if (str_case_glob(h, &pattern))
78       return 1;
79     list = colon + 1;
80   } while (colon != 0);
81   return 0;
82 }
83 
parse_header(const str * s)84 static void parse_header(const str* s)
85 {
86   if (opt_copymsg) {
87     if (opt_headerkeep
88 	? header_test(s, opt_headerkeep)
89 	: opt_headerstrip
90 	? !header_test(s, opt_headerstrip)
91 	: 1)
92       str_cat(&copyheaders, s);
93   }
94 
95   ignore_ml(s, "List-ID");
96   ignore_ml(s, "Mailing-List");
97   ignore_ml(s, "X-Mailing-List");
98   ignore_ml(s, "X-ML-Name");
99   ignore_ml(s, "List-Help");
100   ignore_ml(s, "List-Unsubscribe");
101   ignore_ml(s, "List-Subscribe");
102   ignore_ml(s, "List-Post");
103   ignore_ml(s, "List-Owner");
104   ignore_ml(s, "List-Archive");
105 
106   if(!strncasecmp(s->s, "Precedence:", 11)) {
107     const char* start = skip_space(s->s + 11);
108     const char* end = start;
109     while (end < s->s + s->len && !isspace(*end))
110       ++end;
111     if(!strncasecmp(start, "junk", end-start) ||
112        !strncasecmp(start, "bulk", end-start) ||
113        !strncasecmp(start, "list", end-start))
114       ignore("Message has a junk, bulk, or list precedence header");
115   }
116   else if(!strncasecmp(s->s, dtline, dtline_len-1))
117     ignore("Message already has my Delivered-To line");
118   else {
119     header_copy_if(s, &subject, "subject:", 8);
120     header_copy_if(s, &message_id, "message-id:", 11);
121     header_copy_if(s, &content_type, "content-type:", 13);
122   }
123 }
124 
read_headers(void)125 static void read_headers(void)
126 {
127   str_truncate(&headers, 0);
128   while (ibuf_getstr(&inbuf, &tmpstr, LF)) {
129     if (tmpstr.s[0] == LF)
130       break;
131     str_cat(&headers, &tmpstr);
132   }
133 }
134 
find_field(const char * s,const char * field)135 static const char* find_field(const char* s, const char* field)
136 {
137   int fpos;
138   for (fpos = -1; *s != 0; ++s) {
139     if (isspace(*s) || *s == ';')
140       fpos = 0;
141     else if (fpos >= 0) {
142       if (tolower(*s) != field[fpos])
143         fpos = -1;
144       else if (field[++fpos] == 0) {
145         do { ++s; } while(isspace(*s));
146         if (*s != '=')
147           fpos = -1;
148         else {
149           do { ++s; } while(isspace(*s));
150           return s;
151         }
152       }
153     }
154   }
155   return 0;
156 }
157 
extract_field(const char * s,const char * field,str * value)158 static int extract_field(const char* s, const char* field, str* value)
159 {
160   char quote;
161   const char* start;
162   const char* end;
163 
164   if ((start = find_field(s, field)) != 0) {
165     if (*start == '"' || *start == '\'')
166       for (quote = *start++, end = start; *end != 0 && *end != quote; ++end)
167 	;
168     else
169       for (end = start; *end != 0 && !isspace(*end) && (*end != ';'); ++end)
170 	;
171     str_copyb(value, start, end - start);
172     return 1;
173   }
174   return 0;
175 }
176 
parse_content_type(void)177 static void parse_content_type(void)
178 {
179   if (str_starts(&content_type, "multipart/")) {
180     if (extract_field(content_type.s + 10, "boundary", &boundary))
181       str_splices(&boundary, 0, 0, "--");
182   }
183 }
184 
parse_headers(void)185 static void parse_headers(void)
186 {
187   striter i;
188 
189   striter_loop(&i, &headers, LF) {
190     unsigned next = i.start + i.len + 1;
191 
192     str_catb(&tmpstr, headers.s + i.start, i.len + 1);
193 
194     if (next >= headers.len
195 	|| !isspace(headers.s[next])) {
196       parse_header(&tmpstr);
197       str_truncate(&tmpstr, 0);
198     }
199   }
200   parse_content_type();
201 }
202 
read_parse_headers(void)203 void read_parse_headers(void)
204 {
205   dtline = getenv("DTLINE");
206   if(!dtline)
207     usage("DTLINE is not set; must be run from qmail.");
208   dtline_len = strlen(dtline);
209 
210   // Read and parse header
211   if(lseek(0, 0, SEEK_SET) == -1)
212     fail_temp("Could not rewind input message.");
213   read_headers();
214   parse_headers();
215 }
216