Browse code

bb#1549

git-svn: trunk@5041

aCaB authored on 2009/04/16 01:56:17
Showing 5 changed files
... ...
@@ -1,3 +1,12 @@
1
+Tue Apr 15 18:47:22 CEST 2009 (acab)
2
+------------------------------------
3
+ * clamav-milter: add support for action QuarantineReject (bb#1549)
4
+
5
+Tue Apr 15 17:03:30 CEST 2009 (acab)
6
+------------------------------------
7
+ * clamav-milter: add support for AddHeader=Add, properly remove
8
+		  existing headers (bb#1549)
9
+
1 10
 Mon Apr 13 11:48:33 EEST 2009 (edwin)
2 11
 -------------------------------------
3 12
  * contrib/split-tarball.sh: Apply modifications by Michael
... ...
@@ -56,7 +56,7 @@ int main(int argc, char **argv) {
56 56
     memset(&descr, 0, sizeof(struct smfiDesc));
57 57
     descr.xxfi_name = "ClamAV";			/* filter name */
58 58
     descr.xxfi_version = SMFI_VERSION;		/* milter version */
59
-    descr.xxfi_flags = SMFIF_CHGHDRS|SMFIF_QUARANTINE; /* flags */
59
+    descr.xxfi_flags = SMFIF_QUARANTINE;	/* flags */
60 60
     descr.xxfi_connect = clamfi_connect;	/* connection info filter */
61 61
     descr.xxfi_envfrom = clamfi_envfrom;	/* envelope sender filter */
62 62
     descr.xxfi_envrcpt = clamfi_envrcpt;	/* envelope recipient filter */
... ...
@@ -219,7 +219,8 @@ int main(int argc, char **argv) {
219 219
 	return 1;
220 220
     }
221 221
 
222
-    if(optget(opts, "AddHeader")->enabled) {
222
+    pt = optget(opts, "AddHeader")->strarg;
223
+    if(strcasecmp(pt, "No")) {
223 224
 	char myname[255];
224 225
 
225 226
 	if(!gethostname(myname, sizeof(myname))) {
... ...
@@ -230,7 +231,15 @@ int main(int argc, char **argv) {
230 230
 	    snprintf(xvirushdr, sizeof(xvirushdr), "clamav-milter %s", get_version());
231 231
 	    xvirushdr[sizeof(xvirushdr)-1] = '\0';
232 232
 	}
233
-	addxvirus = 1;
233
+
234
+	descr.xxfi_flags |= SMFIF_ADDHDRS;
235
+
236
+	if(strcasecmp(pt, "Add")) { /* Replace or Yes */
237
+	    descr.xxfi_flags |= SMFIF_CHGHDRS;
238
+	    addxvirus = 1;
239
+	} else { /* Add */
240
+	    addxvirus = 2;
241
+	}
234 242
     }
235 243
     
236 244
     if(!(my_socket = optget(opts, "MilterSocket")->strarg)) {
... ...
@@ -52,7 +52,7 @@ static sfsistat (*CleanAction)(SMFICTX *ctx);
52 52
 static sfsistat (*InfectedAction)(SMFICTX *ctx);
53 53
 static char *rejectfmt = NULL;
54 54
 
55
-int addxvirus = 0;
55
+int addxvirus = 0; /* 0 - don't add | 1 - replace | 2 - add */
56 56
 char xvirushdr[255];
57 57
 enum {
58 58
     LOGINF_NONE,
... ...
@@ -76,12 +76,24 @@ struct CLAMFI {
76 76
     unsigned int bufsz;
77 77
     unsigned int all_whitelisted;
78 78
     unsigned int gotbody;
79
+    unsigned int scanned_count;
80
+    unsigned int status_count;
79 81
 };
80 82
 
81 83
 
82
-static void add_x_header(SMFICTX *ctx, char *st) {
83
-    smfi_chgheader(ctx, (char *)"X-Virus-Scanned", 1, xvirushdr);
84
-    smfi_chgheader(ctx, (char *)"X-Virus-Status", 1, st);
84
+static void add_x_header(SMFICTX *ctx, char *st, unsigned int scanned, unsigned int status) {
85
+    if(addxvirus == 1) {
86
+	while(scanned)
87
+	    if(smfi_chgheader(ctx, (char *)"X-Virus-Scanned", scanned--, NULL) != MI_SUCCESS)
88
+		logg("^Failed to remove existing X-Virus-Scanned header\n");
89
+	while(status)
90
+	    if(smfi_chgheader(ctx, (char *)"X-Virus-Status", status--, NULL) != MI_SUCCESS)
91
+		logg("^Failed to remove existing X-Virus-Status header\n");
92
+    }
93
+    if(smfi_addheader(ctx, (char *)"X-Virus-Scanned", xvirushdr) != MI_SUCCESS)
94
+	logg("^Failed to add X-Virus-Scanned header\n");
95
+    if(smfi_addheader(ctx, (char *)"X-Virus-Status", st) != MI_SUCCESS)
96
+	logg("^Failed to add X-Virus-Status header\n");
85 97
 }
86 98
 
87 99
 enum CFWHAT {
... ...
@@ -201,6 +213,11 @@ sfsistat clamfi_header(SMFICTX *ctx, char *headerf, char *headerv) {
201 201
 	    cf->msg_id = strdup(headerv ? headerv : "");
202 202
     }
203 203
 
204
+    if(addxvirus==1) {
205
+	if(!strcasecmp(headerf, "X-Virus-Scanned")) cf->scanned_count++;
206
+	if(!strcasecmp(headerf, "X-Virus-Status")) cf->status_count++;
207
+    }
208
+
204 209
     if((ret = sendchunk(cf, (unsigned char *)headerf, strlen(headerf), ctx)) != SMFIS_CONTINUE)
205 210
 	return ret;
206 211
     if((ret = sendchunk(cf, (unsigned char *)": ", 2, ctx)) != SMFIS_CONTINUE)
... ...
@@ -290,7 +307,7 @@ sfsistat clamfi_eom(SMFICTX *ctx) {
290 290
 
291 291
     len = strlen(reply);
292 292
     if(len>5 && !strcmp(reply + len - 5, ": OK\n")) {
293
-	if(addxvirus) add_x_header(ctx, "Clean");
293
+	if(addxvirus) add_x_header(ctx, "Clean", cf->scanned_count, cf->status_count);
294 294
 	ret = CleanAction(ctx);
295 295
     } else if (len>7 && !strcmp(reply + len - 7, " FOUND\n")) {
296 296
 	cf->virusname = NULL;
... ...
@@ -309,7 +326,7 @@ sfsistat clamfi_eom(SMFICTX *ctx) {
309 309
 		    char msg[255];
310 310
 		    snprintf(msg, sizeof(msg), "Infected (%s)", vir);
311 311
 		    msg[sizeof(msg)-1] = '\0';
312
-		    add_x_header(ctx, msg);
312
+		    add_x_header(ctx, msg, cf->scanned_count, cf->status_count);
313 313
 		}
314 314
 
315 315
 		if(loginfected) {
... ...
@@ -378,6 +395,8 @@ static int parse_action(char *action) {
378 378
 	return 3;
379 379
     if(!strcasecmp(action, "Quarantine"))
380 380
 	return 4;
381
+    if(!strcasecmp(action, "QuarantineReject"))
382
+	return 5;
381 383
     logg("!Unknown action %s\n", action);
382 384
     return -1;
383 385
 }
... ...
@@ -395,12 +414,18 @@ static sfsistat action_reject(_UNUSED_ SMFICTX *ctx) {
395 395
 static sfsistat action_blackhole(_UNUSED_ SMFICTX *ctx)  {
396 396
     return SMFIS_DISCARD;
397 397
 }
398
-static sfsistat action_quarantine(SMFICTX *ctx) {
398
+static sfsistat quarantine_common(SMFICTX *ctx, sfsistat ret) {
399 399
     if(smfi_quarantine(ctx, "quarantined by clamav-milter") != MI_SUCCESS) {
400 400
 	logg("^Failed to quarantine message\n");
401 401
 	return SMFIS_TEMPFAIL;
402 402
     }
403
-    return SMFIS_ACCEPT;
403
+    return ret;
404
+}
405
+static sfsistat action_quarantine(SMFICTX *ctx) {
406
+    return quarantine_common(ctx, SMFIS_ACCEPT);
407
+}
408
+static sfsistat action_quarantine_reject(SMFICTX *ctx) {
409
+    return quarantine_common(ctx, SMFIS_REJECT);
404 410
 }
405 411
 static sfsistat action_reject_msg(SMFICTX *ctx) {
406 412
     struct CLAMFI *cf;
... ...
@@ -463,6 +488,9 @@ int init_actions(struct optstruct *opts) {
463 463
 	case 4:
464 464
 	    CleanAction = action_quarantine;
465 465
 	    break;
466
+	case 5:
467
+	    CleanAction = action_quarantine_reject;
468
+	    break;
466 469
 	default:
467 470
 	    logg("!Invalid action %s for option OnClean\n", opt->strarg);
468 471
 	    return 1;
... ...
@@ -483,6 +511,9 @@ int init_actions(struct optstruct *opts) {
483 483
 	case 4:
484 484
 	    InfectedAction = action_quarantine;
485 485
 	    break;
486
+	case 5:
487
+	    InfectedAction = action_quarantine_reject;
488
+	    break;
486 489
 	case 2:
487 490
 	    InfectedAction = action_reject_msg;
488 491
 	    if((opt = optget(opts, "RejectMsg"))->enabled) {
... ...
@@ -554,6 +585,10 @@ sfsistat clamfi_envfrom(SMFICTX *ctx, char **argv) {
554 554
     cf->all_whitelisted = 1;
555 555
     cf->gotbody = 0;
556 556
     cf->msg_subj = cf->msg_date = cf->msg_id = NULL;
557
+    if(addxvirus==1) {
558
+	cf->scanned_count = 0;
559
+	cf->status_count = 0;
560
+    }
557 561
     smfi_setpriv(ctx, (void *)cf);
558 562
 
559 563
     return SMFIS_CONTINUE;
... ...
@@ -132,11 +132,15 @@ Example
132 132
 # - Defer
133 133
 #   Return a temporary failure message (4xx) to the peer
134 134
 # - Blackhole (not available for OnFail)
135
-#   Like accept but the message is sent to oblivion
135
+#   Like Accept but the message is sent to oblivion
136 136
 # - Quarantine (not available for OnFail)
137
-#   Like accept but message is quarantined instead of being delivered
138
-#   In sendmail the quarantine queue can be examined via mailq -qQ
139
-#   For Postfix this causes the message to be accepted but placed on hold
137
+#   Like Accept but message is quarantined instead of being delivered
138
+# - QuarantineReject (not available for OnFail)
139
+#   like Reject but a copy of the message is also quarantined
140
+#   Note: in Postfix this acts the same as Quarantine
141
+#
142
+# NOTE: In Sendmail the quarantine queue can be examined via mailq -qQ
143
+# For Postfix this causes the message to be placed on hold
140 144
 # 
141 145
 # Action to be performed on clean messages (mostly useful for testing)
142 146
 # Default Accept
... ...
@@ -158,11 +162,15 @@ Example
158 158
 # Default: MTA specific
159 159
 #RejectMsg 
160 160
 
161
-# If this option is set to Yes, an "X-Virus-Scanned" and an "X-Virus-Status"
162
-# headers will be attached to each processed message, possibly replacing
163
-# existing headers. 
164
-# Default: No
165
-#AddHeader Yes
161
+# If this option is set to "Replace" (or "Yes"), an "X-Virus-Scanned" and an
162
+# "X-Virus-Status" headers will be attached to each processed message, possibly
163
+# replacing existing headers.
164
+# If it is set to Add, the X-Virus headers are added possibly on top of the
165
+# existing ones.
166
+# Note that while "Replace" can potentially break DKIM signatures, "Add" may
167
+# confuse procmail and similar filters.
168
+# Default: no
169
+#AddHeader Replace
166 170
 
167 171
 
168 172
 ##
... ...
@@ -373,15 +373,15 @@ const struct clam_option clam_options[] = {
373 373
 
374 374
     { "LocalNet", NULL, 0, TYPE_STRING, NULL, -1, NULL, FLAG_MULTIPLE, OPT_MILTER, "Messages originating from these hosts/networks will not be scanned\nThis option takes a host(name)/mask pair in CIRD notation and can be\nrepeated several times. If \"/mask\" is omitted, a host is assumed.\nTo specify a locally orignated, non-smtp, email use the keyword \"local\".", "local\n192.168.0.0/24\n1111:2222:3333::/48" },
375 375
 
376
-    { "OnClean", NULL, 0, TYPE_STRING, "^(Accept|Reject|Defer|Blackhole|Quarantine)$", -1, "Accept", 0, OPT_MILTER, "Action to be performed on clean messages (mostly useful for testing).\nThe following actions are available:\nAccept: the message is accepted for delievery;\nReject: immediately refuse delievery (a 5xx error is returned to the peer);\nDefer: return a temporary failure message (4xx) to the peer;\nBlackhole: like accept but the message is sent to oblivion;\nQuarantine: like accept but message is quarantined instead of being delivered,\n    in sendmail the quarantine queue can be examined via mailq -qQ,\n    for Postfix this causes the message to be accepted but placed on hold.", "Accept" },
376
+    { "OnClean", NULL, 0, TYPE_STRING, "^(Accept|Reject|Defer|Blackhole|Quarantine|QuarantineReject)$", -1, "Accept", 0, OPT_MILTER, "Action to be performed on clean messages (mostly useful for testing).\nThe following actions are available:\nAccept: the message is accepted for delievery\nReject: immediately refuse delievery (a 5xx error is returned to the peer)\nDefer: return a temporary failure message (4xx) to the peer\nBlackhole: like Accept but the message is sent to oblivion\nQuarantine: like Accept but message is quarantined instead of being delivered\nQuarantineReject: like Reject but a copy of the message is also quarantined", "Accept" },
377 377
 
378
-    { "OnInfected", NULL, 0, TYPE_STRING, "^(Accept|Reject|Defer|Blackhole|Quarantine)$", -1, "Quarantine", 0, OPT_MILTER, "Action to be performed on infected messages.\nThe following actions are available:\nAccept: the message is accepted for delievery;\nReject: immediately refuse delievery (a 5xx error is returned to the peer);\nDefer: return a temporary failure message (4xx) to the peer;\nBlackhole: like accept but the message is sent to oblivion;\nQuarantine: like accept but message is quarantined instead of being delivered,\n    in sendmail the quarantine queue can be examined via mailq -qQ,\n    for Postfix this causes the message to be accepted but placed on hold.", "Quarantine" },
378
+    { "OnInfected", NULL, 0, TYPE_STRING, "^(Accept|Reject|Defer|Blackhole|Quarantine|QuarantineReject)$", -1, "Quarantine", 0, OPT_MILTER, "Action to be performed on clean messages (mostly useful for testing).\nThe following actions are available:\nAccept: the message is accepted for delievery\nReject: immediately refuse delievery (a 5xx error is returned to the peer)\nDefer: return a temporary failure message (4xx) to the peer\nBlackhole: like Accept but the message is sent to oblivion\nQuarantine: like Accept but message is quarantined instead of being delivered\nQuarantineReject: like Reject but a copy of the message is also quarantined", "Quarantine" },
379 379
 
380
-    { "OnFail", NULL, 0, TYPE_STRING, "^(Accept|Reject|Defer)$", -1, "Defer", 0, OPT_MILTER, "Action to be performed on error conditions (this includes failure to\nallocate data structures, no scanners available, network timeouts, unknown\nscanner replies and the like).\nThe following actions are available:\nAccept: the message is accepted for delievery;\nReject: immediately refuse delievery (a 5xx error is returned to the peer);\nDefer: return a temporary failure message (4xx) to the peer.", "Defer" },
380
+    { "OnFail", NULL, 0, TYPE_STRING, "^(Accept|Reject|Defer)$", -1, "Defer", 0, OPT_MILTER, "Action to be performed on error conditions (this includes failure to\nallocate data structures, no scanners available, network timeouts, unknown\nscanner replies and the like.\nThe following actions are available:\nAccept: the message is accepted for delievery;\nReject: immediately refuse delievery (a 5xx error is returned to the peer);\nDefer: return a temporary failure message (4xx) to the peer.", "Defer" },
381 381
 
382 382
     { "RejectMsg", NULL, 0, TYPE_STRING, NULL, -1, NULL, 0, OPT_MILTER, "This option allows to set a specific rejection reason for infected messages\nand it's therefore only useful together with \"OnInfected Reject\"\nThe string \"%v\", if present, will be replaced with the virus name.", "MTA specific" },
383 383
 
384
-    { "AddHeader", NULL, 0, TYPE_BOOL, MATCH_BOOL, 0, NULL, 0, OPT_MILTER, "If this option is set to Yes, an \"X-Virus-Scanned\" and an \"X-Virus-Status\"\nheaders will be attached to each processed message, possibly replacing\nexisting headers.", "Yes" },
384
+    { "AddHeader", NULL, 0, TYPE_STRING, "^(No|Replace|Yes|Add)$", -1, "no", 0, OPT_MILTER, "If this option is set to \"Replace\" (or \"Yes\"), an \"X-Virus-Scanned\" and an\n\"X-Virus-Status\" headers will be attached to each processed message, possibly\nreplacing existing headers.\nIf it is set to Add, the X-Virus headers are added possibly on top of the\nexisting ones.\nNote that while \"Replace\" can potentially break DKIM signatures, \"Add\" may\nconfuse procmail and similar filters.", "Replace" },
385 385
 
386 386
     { "Chroot", NULL, 0, TYPE_STRING, NULL, -1, NULL, 0, OPT_MILTER, "Chroot to the specified directory.\nChrooting is performed just after reading the config file and before\ndropping privileges.", "/newroot" },
387 387