1 #define HASSNPRINTF 1
2 #ifndef __P
3 #define __P(proto) proto
4 #endif
5
6 #include <ctype.h>
7 #include <fcntl.h>
8 #ifdef linux
9 #include <getopt.h>
10 #include <sys/ioctl.h>
11 #endif
12 #include <stdio.h>
13 #include <string.h>
14 #include <sysexits.h>
15 #include <termios.h>
16 #include <unistd.h>
17 #ifdef __FreeBSD__
18 #include <stdlib.h>
19 #include <netdb.h>
20 #include <unistd.h>
21 #endif
22 #include "libmilter/mfapi.h"
23
24 /*
25 * Al Smith <Al.Smith@aeschi.ch.eu.org> May 9 2000
26 *
27 * Enhancements submitted by Sven Nielsen <dalvenjah@dal.net> May 13 2000
28 * Inline rejection suggested by Paul Yeh <Paul.Yeh@abc.com> May 24 2000
29 *
30 * Please note that this code requires sendmail-8.11.0 or newer.
31 *
32 * This code is distributed under the GNU General Public License.
33 * Share and enjoy.
34 */
35
36 /*
37 * HISTORY
38 * February 6 2004 -- vbsfilter-1.15
39 * Executable name could be in Quoted-Printable format.
40 * Thanks to David Chapeau for reporting this.
41 *
42 * June 26 2003 -- vbsfilter-1.14
43 * New version of sobig, using the .zi extension. Block this.
44 * Thanks to David F. Russell for reporting this.
45 *
46 * January 30 2003
47 * Implement some suggestions from David F. Russell.
48 *
49 * January 14 2003 -- vbsfilter-1.11
50 * Recognise quoted filenames with missing end quote.
51 * Problem (and fix!) supplied by Ian j Hart <ianjhart@ntlworld.com>
52 * and Andrea Adams <andrea@vividimage.com> in response to a virus
53 * termed W32.Sobig.A@mm. Thanks!
54 *
55 * November 12 2002 -- vbsfilter-1.10
56 * Use ioctl(TIOCNOTTY) on /dev/tty.
57 *
58 * Herbert Straub <h.straub@aon.at>
59 * close STDIN, STDOUT and STDERR to avoid hanging processes on exit.
60 *
61 * June 12 2002 -- vbsfilter-1.9
62 * Ross Bergman <rbergman@vividusa.com> noted that some malformed endings,
63 * for example "foo.exe.". Also that a file can have a CLSID as a file
64 * extension, again hidden by windows, and Windows will use the CLSID to
65 * determine how to open the attachment.
66 *
67 * January 30 2002 -- vbsfilter-1.8
68 * Updated vbsfilter to detect uuencoded attachments.
69 * Thanks to Matthew Wong <matthew_wong@aelhk.com.hk> for noting
70 * that the "MyParty" virus spreads itself in this way.
71 *
72 * August 6 2001 -- vbsfilter-1.7
73 * Various fixes for the case where headers and/or body are empty.
74 * Patch sent in by Sergiy Zhuk <serge@yahoo-inc.com>
75 *
76 * Update "dangerous extension" list.
77 * Noted by David F. Russell <David.F.Russell@ncmail.net>
78 *
79 * July 25 2001 -- vbsfilter-1.6
80 * Also recognise 'name=blah' as well as the more usual 'name="blah"'.
81 * Patch sent in by Andrea Adams <andrea@vividimage.com>
82 *
83 * July 9 2001 -- vbsfilter-1.5
84 * Add support to reject arbitrary extensions.
85 *
86 * March 31 2001 -- vbsfilter-1.4
87 * Update for sendmail-8.12
88 *
89 * February 19 2001 -- vbsfilter-1.3a
90 * Non-zero terminated string error, noted by
91 * Dirk Meyer <dirk.meyer@dinoex.sub.org>
92 *
93 * July 21 2000 -- vbsfilter-1.3
94 * Update for sendmail-8.11
95 *
96 * June 20 2000 -- vbsfilter-1.2
97 * Add recognition of .SHS attachments.
98 *
99 * May 24 2000 -- vbsfilter-1.1
100 * Add recognition of inline VBS attachments.
101 * Libmilter doesn't let us modify the headers, so such mails
102 * must be rejected.
103 *
104 * May 9 2000 -- vbsfilter-1.0
105 * Filter incoming visual basic script attachments into text attachments,
106 * by changing the .vbs filename suffix into .txt.
107 */
108
109 /*
110 * TODO
111 *
112 * detect any mime type, such as ms/vb-script or whathaveyou and
113 * also change that into something harmless like text/plain
114 */
115
116 struct mlfiPriv {
117 int len;
118 u_char *body;
119 int addver;
120 };
121
122 /*
123 * Microsoft considers these extensions dangerous:
124 * http://support.microsoft.com/support/kb/articles/Q262/6/17.ASP
125 */
126 static char *exts[] = { "ade", "adp", "bas", "bat", "chm", "cmd", "com",
127 "cpl", "crt", "exe", "hlp", "hta", "inf", "ins", "isp", "js", "jse",
128 "lnk", "mdb", "mde", "msc", "msi", "msp", "mst", "pcd", "pif", "reg",
129 "scr", "sct", "shs", "shb", "url", "vb", "vbe", "vbs", "wsc", "wsf",
130 "wsh", "zi", NULL };
131
132 #define VERTEXT "X-Filter-Version"
133 #define VERSION "1.15"
134
135 #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx))
136
137 extern sfsistat mlfi_cleanup(SMFICTX *, sfsistat);
138
nocase_strncmp(const char * s1,const char * s2,int len)139 static int nocase_strncmp(const char *s1, const char *s2, int len) {
140 register int n = len;
141
142 for (; n; s1++, s2++, n--) {
143 if (tolower(*s1) != tolower(*s2)) return (1);
144 }
145 return(n > 0);
146 }
147
nocase_strstr(const char * s1,const char * s2)148 static char * nocase_strstr(const char *s1, const char *s2) {
149 const char *p, *q, *r;
150
151 for(p = s1; *p; p++) {
152 for(q = s2, r = p; *q && *r; q++, r++) {
153 if (tolower(*q) != tolower(*r)) break;
154 }
155 if (!*q) {
156 return((char *) p);
157 } else if (!*r) {
158 return(NULL);
159 }
160 }
161 return(NULL);
162 }
163
detect_header(u_char * body,char * header,char * subfield,char ** exts,char * ext2)164 static int detect_header(u_char *body, char *header, char *subfield, char **exts, char *ext2) {
165 char *p, *q, *r;
166 register int i;
167 int lfcnt, rc = 0, quoted;
168 char **ext;
169
170 for(p = (char *) body; p && (p = nocase_strstr(p, header)); p++) {
171 for(lfcnt = 0, q = p+strlen(header); *q && lfcnt < 3; q++) {
172 if (*q == '\n') {
173 lfcnt++;
174 } else {
175 if (!nocase_strncmp(q, subfield, strlen(subfield))) {
176 r=q+strlen(subfield);
177 quoted = (*r == '"');
178 if (quoted) r++;
179 for(; *r != '\r' && *r != '\n' && *r != '\0'; r++) {
180 if (quoted && *r == '"') break;
181 }
182 if ((quoted && *r == '"') || (*r == '\n' || *r == '\r')) {
183 if (*(r-1) == '}' && r-q > 39 && *(r-38) == '{' && *(r-39) == '.') {
184 for(i = 39; i > 0; i--) {
185 *(r-i) = ' ';
186 }
187 rc++;
188 }
189
190 /* virus.exe.?= */
191 if (*(r-7) == '.' && *(r-3) == '.' && *(r-2) == '?' && *(r-1) == '=') {
192 for(ext = exts; *ext; ext++) {
193 if (strlen(*ext) != 3) continue;
194 if ( tolower(*(r-6)) == (*ext)[0] &&
195 tolower(*(r-5)) == (*ext)[1] &&
196 tolower(*(r-4)) == (*ext)[2]) {
197
198 *(r-6) = ext2[0];
199 *(r-5) = ext2[1];
200 *(r-4) = ext2[2];
201 rc++;
202 }
203 }
204 }
205
206 /* virus.exe?= */
207 if (*(r-6) == '.' && *(r-2) == '?' && *(r-1) == '=') {
208 for(ext = exts; *ext; ext++) {
209 if (strlen(*ext) != 3) continue;
210 if ( tolower(*(r-5)) == (*ext)[0] &&
211 tolower(*(r-4)) == (*ext)[1] &&
212 tolower(*(r-3)) == (*ext)[2]) {
213
214 *(r-5) = ext2[0];
215 *(r-4) = ext2[1];
216 *(r-3) = ext2[2];
217 rc++;
218 }
219 }
220 }
221
222 /* virus.exe. */
223 if (*(r-5) == '.' && *(r-1) == '.') {
224 for(ext = exts; *ext; ext++) {
225 if (strlen(*ext) != 3) continue;
226 if ( tolower(*(r-4)) == (*ext)[0] &&
227 tolower(*(r-3)) == (*ext)[1] &&
228 tolower(*(r-2)) == (*ext)[2]) {
229
230 *(r-4) = ext2[0];
231 *(r-3) = ext2[1];
232 *(r-2) = ext2[2];
233 rc++;
234 }
235 }
236 }
237
238 /* virus.exe */
239 if (*(r-4) == '.') {
240 for(ext = exts; *ext; ext++) {
241 if (strlen(*ext) != 3) continue;
242 if ( tolower(*(r-3)) == (*ext)[0] &&
243 tolower(*(r-2)) == (*ext)[1] &&
244 tolower(*(r-1)) == (*ext)[2]) {
245
246 *(r-3) = ext2[0];
247 *(r-2) = ext2[1];
248 *(r-1) = ext2[2];
249 rc++;
250 }
251 }
252 }
253
254 /* virus.js */
255 if (*(r-3) == '.') {
256 for(ext = exts; *ext; ext++) {
257 if (strlen(*ext) != 2) continue;
258 if ( tolower(*(r-2)) == (*ext)[0] &&
259 tolower(*(r-1)) == (*ext)[1]) {
260
261 *(r-2) = ext2[0];
262 *(r-1) = ext2[1];
263 rc++;
264 }
265 }
266 }
267 }
268 }
269 }
270 }
271 }
272 return(rc);
273 }
274
mlfi_envfrom(SMFICTX * ctx,char ** envfrom)275 sfsistat mlfi_envfrom(SMFICTX *ctx, char **envfrom) {
276 struct mlfiPriv *priv;
277
278 priv = (struct mlfiPriv *) malloc(sizeof *priv);
279 if (!priv) {
280 /* can't accept this message right now */
281 return SMFIS_TEMPFAIL;
282 }
283 priv->body = (u_char *) strdup("\n");
284 priv->len = 1;
285 priv->addver = 1;
286
287 if (!priv->body) {
288 /* can't accept this message right now */
289 return SMFIS_TEMPFAIL;
290 }
291
292 /* save the private data */
293 smfi_setpriv(ctx, priv);
294
295 /* continue processing */
296 return SMFIS_CONTINUE;
297 }
298
mlfi_header(SMFICTX * ctx,char * headerf,char * headerv)299 sfsistat mlfi_header(SMFICTX *ctx, char *headerf, char *headerv) {
300 struct mlfiPriv *priv = MLFIPRIV;
301 int found = 0;
302
303 if (!strcmp(headerf, VERTEXT)) {
304 priv->addver = 0;
305 }
306
307 if (!nocase_strncmp(headerf, "Content-Disposition", strlen("Content-Disposition"))) {
308 found = detect_header((u_char *) headerv, "", "filename=", exts, "txt");
309 }
310 if (!nocase_strncmp(headerf, "Content-Type", strlen("Content-Type"))) {
311 found += detect_header((u_char *) headerv, "", "name=", exts, "txt");
312 }
313 if (found) {
314 smfi_setreply(ctx, "554", "5.6.1", "Rejecting inline executable attachment");
315 return mlfi_cleanup(ctx, SMFIS_REJECT);
316 }
317 return SMFIS_CONTINUE;
318 }
319
mlfi_eoh(SMFICTX * ctx)320 sfsistat mlfi_eoh(SMFICTX *ctx) {
321 return SMFIS_CONTINUE;
322 }
323
mlfi_body(SMFICTX * ctx,u_char * bodyp,size_t bodylen)324 sfsistat mlfi_body(SMFICTX *ctx, u_char *bodyp, size_t bodylen) {
325 struct mlfiPriv *priv = MLFIPRIV;
326
327 priv->body = (u_char *) realloc(priv->body, priv->len + bodylen + 1);
328
329 if (!priv->body) {
330 /* can't accept this message right now */
331 return SMFIS_TEMPFAIL;
332 }
333
334 memcpy((u_char*)(priv->body+priv->len), bodyp, bodylen);
335 priv->len += bodylen;
336 priv->body[priv->len] = '\0';
337
338 /* continue processing */
339 return SMFIS_CONTINUE;
340 }
341
mlfi_eom(SMFICTX * ctx)342 sfsistat mlfi_eom(SMFICTX *ctx) {
343 struct mlfiPriv *priv = MLFIPRIV;
344 int found_disp = 0, found_type = 0, found_uuencode = 0;
345 int found;
346 char buf[1024];
347 char host[512];
348
349 if (!priv->body) {
350 return mlfi_cleanup(ctx, SMFIS_CONTINUE);
351 }
352
353 /*
354 * here's an example of what netscape sends out:
355 *
356 * Content-Type: application/octet-stream; name="test.vbs"
357 * Content-Transfer-Encoding: base64
358 * Content-Disposition: inline; filename="test.vbs"
359 */
360 found_disp = detect_header(priv->body, "\nContent-Disposition", "filename=", exts, "txt");
361 found_type = detect_header(priv->body, "\nContent-Type", "name=", exts, "txt");
362 found_uuencode = detect_header(priv->body, "\nbegin ", " ", exts, "txt");
363
364 found = (found_type > found_disp) ? found_type : found_disp;
365 found = (found_uuencode > found) ? found_uuencode : found;
366
367 if (gethostname(host, sizeof(host)) < 0) {
368 strncpy(host, "localhost", sizeof host);
369 }
370
371 sprintf(buf, "%s (%s)", VERSION, host);
372
373 if (priv->addver) smfi_addheader(ctx, VERTEXT, buf);
374 if (found) {
375 sprintf(buf, "%d attachment%s changed to TXT",
376 found, (found == 1) ? "" : "s");
377 smfi_addheader(ctx, "X-Filter", buf);
378 smfi_replacebody (ctx, priv->body+1, priv->len-1);
379 }
380
381 return mlfi_cleanup(ctx, SMFIS_CONTINUE);
382 }
383
mlfi_close(SMFICTX * ctx)384 sfsistat mlfi_close(SMFICTX *ctx) {
385 return mlfi_cleanup(ctx, SMFIS_ACCEPT);
386 }
387
mlfi_abort(SMFICTX * ctx)388 sfsistat mlfi_abort(SMFICTX *ctx) {
389 return mlfi_cleanup(ctx, SMFIS_CONTINUE);
390 }
391
mlfi_cleanup(SMFICTX * ctx,sfsistat rc)392 sfsistat mlfi_cleanup(SMFICTX *ctx, sfsistat rc) {
393 struct mlfiPriv *priv = MLFIPRIV;
394
395 if (priv) {
396 if (priv->body) free(priv->body);
397 free(priv);
398 smfi_setpriv(ctx, NULL);
399 }
400
401 return(rc);
402 }
403
404 struct smfiDesc smfilter = {
405 "VBFilter", /* filter name */
406 SMFI_VERSION, /* version code -- do not change */
407 SMFIF_ADDHDRS | SMFIF_CHGBODY, /* flags */
408 NULL, /* connection info filter */
409 NULL, /* SMTP HELO command filter */
410 mlfi_envfrom, /* envelope sender filter */
411 NULL, /* envelope recipient filter */
412 mlfi_header, /* header filter */
413 mlfi_eoh, /* end of header */
414 mlfi_body, /* body block filter */
415 mlfi_eom, /* end of message */
416 mlfi_abort, /* message aborted */
417 mlfi_close /* connection cleanup */
418 };
419
420
main(int argc,char * argv[])421 int main(int argc, char *argv[]) {
422 int c, fd;
423 const char *args = "p:";
424
425 /* Process command line options */
426 while ((c = getopt(argc, argv, args)) != -1) {
427 switch (c) {
428 case 'p':
429 if (optarg == NULL || *optarg == '\0') {
430 (void) fprintf(stderr, "Illegal conn: %s\n",
431 optarg);
432 exit(EX_USAGE);
433 }
434 (void) smfi_setconn(optarg);
435 break;
436
437 }
438 }
439
440 if (smfi_register(smfilter) == MI_FAILURE) {
441 fprintf(stderr, "smfi_register failed\n");
442 exit(EX_UNAVAILABLE);
443 }
444
445 if (fork() == 0) {
446 close(STDIN_FILENO);
447 close(STDOUT_FILENO);
448 close(STDERR_FILENO);
449
450 fd = open("/dev/tty", O_RDWR);
451 if (fd >= 0) {
452 ioctl(fd, TIOCNOTTY, 0);
453 close(fd);
454 }
455
456 return smfi_main();
457 }
458 return 0;
459 }
460
461