Browse code

bb11548 fix clamsubmit.

Steven Morgan authored on 2017/02/08 03:27:15
Showing 2 changed files
... ...
@@ -10,21 +10,33 @@
10 10
 #include "shared/misc.h"
11 11
 #include "shared/getopt.h"
12 12
 
13
-#define OPTS "e:p:n:N:H:h?v"
13
+#define OPTS "e:p:n:N:V:H:h?v"
14 14
 
15 15
 char *read_stream(void);
16 16
 void usage(char *name);
17 17
 void version(void);
18 18
 
19
+typedef struct _header_data {
20
+    int len;
21
+    char * cfduid;
22
+    char * session;
23
+} header_data;
24
+
25
+typedef struct _write_data {
26
+    int len;
27
+    char * str;
28
+} write_data;
29
+
19 30
 void usage(char *name)
20 31
 {
21
-    fprintf(stderr, "USAGE: %s -hHinp?\n", name);
32
+    fprintf(stderr, "USAGE: %s -hHinpVv?\n", name);
22 33
     fprintf(stderr, "OPTIONS:\n");
23 34
     fprintf(stderr, "    -e [EMAIL]\tYour email address (required)\n");
24 35
     fprintf(stderr, "    -h or -?\tShow the help text\n");
25 36
     fprintf(stderr, "    -n [FILE]\tSubmit a false negative (FN)\n");
26
-    fprintf(stderr, "    -N [NAME]\tYour name (required)\n");
27
-    fprintf(stderr, "    -p [FILE]\tSubmit a fase positive (FP)\n");
37
+    fprintf(stderr, "    -N [NAME]\tYour name contained in quotation marks (required)\n");
38
+    fprintf(stderr, "    -p [FILE]\tSubmit a false positive (FP)\n");
39
+    fprintf(stderr, "    -V [NAME] \tDetected virus name(required with -p)\n");
28 40
     fprintf(stderr, "    -v\t\tShow version number and exit\n");
29 41
     fprintf(stderr, "You must specify -n or -p. Both are mutually exclusive. Pass in - as the filename for stdin.\n");
30 42
     exit(0);
... ...
@@ -36,21 +48,106 @@ void version(void)
36 36
     exit(0);
37 37
 }
38 38
 
39
+size_t header_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
40
+{
41
+    int len = size*nmemb;
42
+    char *sp, *ep, *mem;
43
+    header_data *hd = (header_data *) userdata;
44
+    const char *set_cookie = "Set-Cookie:";
45
+    int clen = strlen(set_cookie);
46
+
47
+    if (len > clen) {
48
+        if (strncmp(ptr, set_cookie, clen))
49
+            return len;
50
+        sp = ptr + clen + 1;
51
+        ep = strchr(sp, ';');
52
+        if (ep == NULL) {
53
+            fprintf(stderr, "header_cb(): malformed cookie\n");
54
+            return 0;
55
+        }
56
+        mem = malloc(ep-sp+1);
57
+        if (mem == NULL) {
58
+            fprintf(stderr, "header_cb(): malloc failed\n");
59
+            return 0;
60
+        }
61
+        memcpy(mem, sp, ep-sp);
62
+        mem[ep-sp] = '\0';
63
+        if (!strncmp(mem, "__cfduid", 8))
64
+            hd->cfduid = mem;
65
+        else if (!strncmp(mem, "_clamav-net_session", strlen("_clamav-net_session")))
66
+            hd->session = mem;
67
+        else
68
+            fprintf(stderr, "header_cb(): unrecognized cookie\n");
69
+    }
70
+    return len;
71
+}
72
+
73
+size_t write_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
74
+{
75
+    int len = size*nmemb;
76
+    char * str;
77
+    write_data *wd = (write_data *) userdata;
78
+
79
+    if (len) {
80
+        str = realloc(wd->str, wd->len + len + 1);
81
+        if (str == NULL) {
82
+            fprintf (stderr, "write_cb() realloc failure\n");
83
+            return 0;
84
+        }
85
+        memcpy(str + wd->len, ptr, len);
86
+        str[wd->len + len] = '\0';
87
+        wd->str = str;
88
+        wd->len += len;
89
+    }
90
+    return len;
91
+}
92
+
93
+const char* presigned_get_string(json_object * ps_json_obj, char * key)
94
+{
95
+    json_object * json_obj = NULL;
96
+    const char * json_str = NULL;
97
+
98
+    if (json_object_object_get_ex(ps_json_obj, key, &json_obj)) {
99
+        json_str = json_object_get_string(json_obj);
100
+        if (json_str == NULL) {
101
+            fprintf(stderr, "Error: json_object_get_string() for %s.\n", key);
102
+            exit(1);
103
+        }
104
+    } else {
105
+        fprintf(stderr, "Error: json_object_object_get_ex() for %s.\n", key);
106
+        exit(1);
107
+    }
108
+    return json_str;
109
+}
110
+
39 111
 int main(int argc, char *argv[])
40 112
 {
41
-    CURL *curl;
113
+    CURL *clam_curl, *aws_curl;
42 114
     CURLcode res;
43 115
     int ch;
44 116
     struct curl_httppost *post=NULL, *last=NULL;
45 117
     struct curl_slist *slist = NULL;
46 118
     char *name=NULL, *email=NULL, *filename=NULL;
47 119
     int setURL=0, fromStream=0;
120
+    const char * json_str;
121
+    write_data wd = {0, NULL};
122
+    header_data hd_malware = {0, NULL, NULL};
123
+    header_data hd_presigned = {0, NULL, NULL};
124
+    json_object * ps_json_obj = NULL;
125
+    json_object * json_obj = NULL;
126
+    int malware = 0;
127
+    int len = 0;
128
+    char * submissionID = NULL;
129
+    char * fpvname = NULL;
130
+    char *sp, *ep, *str;
131
+    char * authenticity_token;
132
+    char * urlp;
48 133
 
49 134
     curl_global_init(CURL_GLOBAL_ALL);
50 135
 
51
-    curl = curl_easy_init();
52
-    if (!(curl)) {
53
-        fprintf(stderr, "ERROR: Could not initialize libcurl\n");
136
+    clam_curl = curl_easy_init();
137
+    if (clam_curl == NULL) {
138
+        fprintf(stderr, "ERROR: Could not initialize libcurl.\n");
54 139
         exit(1);
55 140
     }
56 141
 
... ...
@@ -58,6 +155,7 @@ int main(int argc, char *argv[])
58 58
         switch (ch) {
59 59
             case 'v':
60 60
                 version();
61
+                break;
61 62
             case 'e':
62 63
                 email = optarg;
63 64
                 break;
... ...
@@ -67,20 +165,16 @@ int main(int argc, char *argv[])
67 67
             case 'p':
68 68
                 if (setURL)
69 69
                     usage(argv[0]);
70
-
71 70
                 filename = optarg;
72
-
73
-                curl_easy_setopt(curl, CURLOPT_URL, "http://cgi.clamav.net/sendfp.cgi");
74
-                setURL=1;
75 71
                 break;
76 72
             case 'n':
77 73
                 if (setURL)
78 74
                     usage(argv[0]);
79
-
75
+                malware = 1;
80 76
                 filename = optarg;
81
-
82
-                curl_easy_setopt(curl, CURLOPT_URL, "http://cgi.clamav.net/sendmalware.cgi");
83
-                setURL=1;
77
+                break;
78
+            case 'V':
79
+                fpvname = optarg;
84 80
                 break;
85 81
             case 'h':
86 82
             case '?':
... ...
@@ -92,47 +186,258 @@ int main(int argc, char *argv[])
92 92
     if (!(name) || !(email) || !(filename))
93 93
         usage(argv[0]);
94 94
 
95
+    if (malware == 0 && fpvname == NULL) {
96
+        fprintf(stderr, "Detected virus name(-V) required for false positive submissions.\n");
97
+        usage(argv[0]);
98
+    }
95 99
     if (strlen(filename) == 1 && filename[0] == '-') {
96 100
         filename = read_stream();
97 101
         if (!(filename)) {
98 102
             fprintf(stderr, "ERROR: Unable to read stream\n");
99 103
             exit(1);
100 104
         }
101
-
102 105
         fromStream=1;
103 106
     }
104 107
 
105
-    slist = curl_slist_append(slist, "Expect:");
106
-    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
107 108
 
108
-    if (curl_formadd(&post, &last, CURLFORM_COPYNAME, "sendername", CURLFORM_COPYCONTENTS, name, CURLFORM_END)) {
109
-        fprintf(stderr, "Unable to specify name in libcurl form for file %s\n", optarg);
110
-        goto end;
109
+    /*** The GET malware|fp ***/
110
+    if (malware == 1)
111
+        urlp = "http://www.clamav.net/reports/malware";
112
+    else
113
+        urlp = "http://www.clamav.net/reports/fp";
114
+    curl_easy_setopt(clam_curl, CURLOPT_URL, urlp);
115
+    curl_easy_setopt(clam_curl, CURLOPT_HTTPGET, 1);
116
+    curl_easy_setopt(clam_curl, CURLOPT_WRITEDATA, &wd);
117
+    curl_easy_setopt(clam_curl, CURLOPT_WRITEFUNCTION, write_cb);
118
+    curl_easy_setopt(clam_curl, CURLOPT_HEADERDATA, &hd_malware);
119
+    curl_easy_setopt(clam_curl, CURLOPT_HEADERFUNCTION, header_cb);
120
+    res = curl_easy_perform(clam_curl);
121
+    if (res) {
122
+        fprintf(stderr, "Error in GET %s: %s\n", urlp , curl_easy_strerror(res));
123
+        exit(1);
111 124
     }
112
-
113
-    if (curl_formadd(&post, &last, CURLFORM_COPYNAME, "email", CURLFORM_COPYCONTENTS, email, CURLFORM_END)) {
114
-        fprintf(stderr, "Unable to specify email in libcurl form for file %s\n", optarg);
115
-        goto end;
125
+    if (wd.str != NULL) {
126
+        sp = strstr(wd.str, "name=\"authenticity_token\"");
127
+        if (sp == NULL) {
128
+            fprintf (stderr, "Authenticity token element not found.\n");
129
+            exit(1);
130
+        }
131
+        sp = strstr(sp, "value=");
132
+        if (sp == NULL) {
133
+            fprintf (stderr, "Authenticity token value not found.\n");
134
+            exit(1);
135
+        }
136
+        sp += 7;
137
+        ep = strchr(sp, '"');
138
+        if (ep == NULL) {
139
+            fprintf (stderr, "Authenticity token malformed.\n");
140
+            exit(1);
141
+        }
142
+        authenticity_token = malloc(ep-sp+1);
143
+        if (authenticity_token == NULL) {
144
+            fprintf (stderr, "no memory for authenticity token.\n");
145
+            exit(1);
146
+        }
147
+        memcpy(authenticity_token, sp, ep-sp);
148
+        authenticity_token[ep-sp] = '\0';
149
+        free (wd.str);
150
+        wd.str = NULL;
116 151
     }
117
-
118
-    if (curl_formadd(&post, &last, CURLFORM_COPYNAME, "file", CURLFORM_FILE, filename, CURLFORM_END)) {
119
-        fprintf(stderr, "Unable to specify file path in libcurl form for file %s\n", optarg);
120
-        goto end;
152
+    wd.len = 0;
153
+    urlp = NULL;
154
+
155
+
156
+    /*** The GET presigned ***/
157
+    if (malware == 1)
158
+        curl_easy_setopt(clam_curl, CURLOPT_URL, "http://www.clamav.net/presigned?type=malware");
159
+    else
160
+        curl_easy_setopt(clam_curl, CURLOPT_URL, "http://www.clamav.net/presigned?type=fp");
161
+    curl_easy_setopt(clam_curl, CURLOPT_HTTPGET, 1);
162
+    len = strlen(hd_malware.cfduid) + strlen(hd_malware.session) + 3;
163
+    str = malloc(len);
164
+    if (str == NULL) {
165
+        fprintf(stderr, "No memory for GET presigned cookies\n");
166
+        exit(1);
121 167
     }
168
+    if (snprintf(str, len, "%s; %s;", hd_malware.cfduid, hd_malware.session) > len) {
169
+        fprintf(stderr, "snprintf() failed formatting GET presigned cookies\n");
170
+        exit(1);
171
+    }
172
+    curl_easy_setopt(clam_curl, CURLOPT_COOKIE, str);
173
+    free(str);
174
+    len = strlen(authenticity_token) + 15;
175
+    str = malloc(len);
176
+    if (str == NULL) {
177
+        fprintf(stderr, "No memory for GET presigned X-CSRF-Token\n");
178
+        exit(1);
179
+    }
180
+    if (snprintf(str, len, "X-CSRF-Token: %s", authenticity_token) > len) {
181
+        fprintf(stderr, "snprintf() failed for GET presigned X-CSRF-Token\n");
182
+        exit(1);
183
+    }
184
+    slist = curl_slist_append(slist, str);
185
+    free(str);
186
+    curl_easy_setopt(clam_curl, CURLOPT_HTTPHEADER, slist);
187
+    curl_easy_setopt(clam_curl, CURLOPT_HEADERDATA, &hd_presigned);
188
+    curl_easy_setopt(clam_curl, CURLOPT_HEADERFUNCTION, header_cb);
189
+    if (malware ==1)
190
+        curl_easy_setopt(clam_curl, CURLOPT_REFERER, "http://www.clamav.net/reports/malware");
191
+    else
192
+        curl_easy_setopt(clam_curl, CURLOPT_REFERER, "http://www.clamav.net/reports/fp");
193
+
194
+    res = curl_easy_perform(clam_curl);
195
+    if (res) {
196
+        fprintf(stderr, "Error in GET presigned: %s\n", curl_easy_strerror(res));
197
+        exit(1);
198
+    }
199
+    curl_slist_free_all(slist);
200
+    slist = NULL;
122 201
 
123
-    curl_formadd(&post, &last, CURLFORM_COPYNAME, "action", CURLFORM_COPYCONTENTS, "submit", CURLFORM_END);
124
-    curl_formadd(&post, &last, CURLFORM_COPYNAME, "privacy", CURLFORM_COPYCONTENTS, "yes", CURLFORM_END);
125
-    curl_formadd(&post, &last, CURLFORM_COPYNAME, "notify", CURLFORM_COPYCONTENTS, "yes", CURLFORM_END);
126 202
 
127
-    curl_easy_setopt(curl, CURLOPT_HTTPPOST, post);
128
-    res = curl_easy_perform(curl);
203
+    /*** The POST to AWS ***/
204
+    ps_json_obj = json_tokener_parse(wd.str);
205
+    if (ps_json_obj == NULL) {
206
+        fprintf(stderr, "Error in json_tokener_parse of %.*s\n", wd.len, wd.str);
207
+        exit(1);
208
+    }
209
+    json_str = presigned_get_string(ps_json_obj, "key");
210
+    sp = strchr(json_str, '/');
211
+    if (sp == NULL) {
212
+        fprintf(stderr, "Error: malformed 'key' string in GET presigned response (missing '/'.\n");
213
+        exit (1);
214
+    }
215
+    sp++;
216
+    ep = strchr(sp, '-');
217
+    if (ep == NULL) {
218
+        fprintf(stderr, "Error: malformed 'key' string in GET presigned response (missing '-'.\n");
219
+        exit (1);
220
+    }
221
+    submissionID = malloc(ep-sp+1);
222
+    if (submissionID == NULL) {
223
+        fprintf(stderr, "Error: malloc submissionID.\n");
224
+        exit (1);
225
+    }
226
+    memcpy(submissionID, sp, ep-sp);
227
+    submissionID[ep-sp] = '\0';
228
+    aws_curl = curl_easy_init();
229
+    if (!(aws_curl)) {
230
+        fprintf(stderr, "ERROR: Could not initialize libcurl POST presigned\n");
231
+        exit(1);
232
+    }
233
+    submissionID[ep-sp] = '\0';
234
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "key", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
235
+    json_str = presigned_get_string(ps_json_obj, "acl");
236
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "acl", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
237
+    json_str = presigned_get_string(ps_json_obj, "policy");
238
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "policy", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
239
+    json_str = presigned_get_string(ps_json_obj, "x-amz-meta-original-filename");
240
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "x-amz-meta-original-filename", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
241
+    json_str = presigned_get_string(ps_json_obj, "x-amz-credential");
242
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "x-amz-credential", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
243
+    json_str = presigned_get_string(ps_json_obj, "x-amz-algorithm");
244
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "x-amz-algorithm", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
245
+    json_str = presigned_get_string(ps_json_obj, "x-amz-date");
246
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "x-amz-date", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
247
+    json_str = presigned_get_string(ps_json_obj, "x-amz-signature");
248
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "x-amz-signature", CURLFORM_COPYCONTENTS, json_str, CURLFORM_END);
249
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "file", CURLFORM_FILE, filename, CURLFORM_END);
250
+    slist = curl_slist_append(slist, "Expect:");
251
+    curl_easy_setopt(aws_curl, CURLOPT_HTTPHEADER, slist);
252
+    curl_easy_setopt(aws_curl, CURLOPT_URL, "http://clamav-site.s3.amazonaws.com/");
253
+    curl_easy_setopt(aws_curl, CURLOPT_HTTPPOST, post);
129 254
 
255
+    res = curl_easy_perform(aws_curl);
130 256
     if (res) {
131
-        fprintf(stderr, "Error: %s\n", curl_easy_strerror(res));
257
+        fprintf(stderr, "Error in POST AWS: %s\n", curl_easy_strerror(res));
258
+        exit(1);
132 259
     }
133
-
134
-end:
135
-    curl_easy_cleanup(curl);
260
+    curl_slist_free_all(slist);
261
+    slist = NULL;
262
+    curl_formfree(post);
263
+    post = NULL;
264
+    last = NULL;
265
+    curl_easy_cleanup(aws_curl);
266
+    json_object_put(ps_json_obj);
267
+    free(wd.str);
268
+    wd.str = NULL;
269
+    wd.len = 0;
270
+
271
+
272
+    /*** The POST submit to clamav.net ***/
273
+    slist = curl_slist_append(slist, "Expect:");
274
+    len = strlen(hd_malware.cfduid) + strlen(hd_presigned.session) + 3;
275
+    str = malloc(len);
276
+    if (str == NULL) {
277
+        fprintf(stderr, "No memory for POST submit cookies.\n");
278
+        exit(1);
279
+    }
280
+    if (snprintf(str, len, "%s; %s;", hd_malware.cfduid, hd_presigned.session) > len) {
281
+        fprintf(stderr, "snprintf() failed formatting POST submit cookies\n");
282
+        exit(1);
283
+    }
284
+    curl_easy_setopt(clam_curl, CURLOPT_COOKIE, str);
285
+    free(str);
286
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "utf8", CURLFORM_COPYCONTENTS, "\x27" "\x13", CURLFORM_END);
287
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "authenticity_token", CURLFORM_COPYCONTENTS, authenticity_token, CURLFORM_END);
288
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "submissionID", CURLFORM_COPYCONTENTS, submissionID, CURLFORM_END);
289
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "type", CURLFORM_COPYCONTENTS, malware?"malware":"fp", CURLFORM_END);
290
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "sendername", CURLFORM_COPYCONTENTS, name, CURLFORM_END);
291
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "email", CURLFORM_COPYCONTENTS, email, CURLFORM_END);
292
+    if (malware == 0) {
293
+        curl_formadd(&post, &last, CURLFORM_COPYNAME, "virusname", CURLFORM_COPYCONTENTS, fpvname, CURLFORM_END);
294
+    } else {
295
+    if (malware == 1)
296
+        curl_formadd(&post, &last, CURLFORM_COPYNAME, "shareSample", CURLFORM_COPYCONTENTS, "on", CURLFORM_END);
297
+    }
298
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "description", CURLFORM_COPYCONTENTS, "clamsubmit", CURLFORM_END);
299
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "notify", CURLFORM_COPYCONTENTS, "on", CURLFORM_END);
300
+    curl_formadd(&post, &last, CURLFORM_COPYNAME, "privacy", CURLFORM_COPYCONTENTS, "on", CURLFORM_END);
301
+    curl_easy_setopt(clam_curl, CURLOPT_HTTPHEADER, slist);
302
+    curl_easy_setopt(clam_curl, CURLOPT_URL, "http://www.clamav.net/reports/submit");
303
+    curl_easy_setopt(clam_curl, CURLOPT_HTTPPOST, post);
304
+    curl_easy_setopt(clam_curl, CURLOPT_HEADERFUNCTION, NULL);
305
+    res = curl_easy_perform(clam_curl);
306
+    if (res) {
307
+        fprintf(stderr, "Error in POST submit: %s\n", curl_easy_strerror(res));
308
+        exit(1);
309
+    } else {
310
+        long response_code;
311
+        curl_easy_getinfo(clam_curl, CURLINFO_RESPONSE_CODE, &response_code);
312
+        if (response_code/100 == 3) {
313
+            curl_easy_getinfo(clam_curl, CURLINFO_REDIRECT_URL, &urlp);
314
+            if (urlp == NULL) {
315
+                fprintf(stderr, "POST submit Location URL is NULL.\n");
316
+                exit(1);
317
+            }
318
+            sp = strstr(urlp, "/reports/");
319
+            if (sp == NULL) {
320
+                fprintf(stderr, "POST submit Location URL is malformed.\n");
321
+                exit(1);
322
+            }
323
+            if (!strcmp(sp, "/reports/success"))
324
+                fprintf(stdout, "Submission success!\n");
325
+            else if (!strcmp(sp, "/reports/failure"))
326
+                fprintf(stdout, "Submission failed\n");
327
+            else
328
+                fprintf(stdout, "Unknown submission status %s\n", sp);
329
+        }
330
+        else
331
+            fprintf(stderr, "Unexpected POST submit response code: %li\n", response_code);
332
+    }
333
+    curl_slist_free_all(slist);
334
+    curl_formfree(post);
335
+    curl_easy_cleanup(clam_curl);
336
+    if (wd.str != NULL) {
337
+        free(wd.str);
338
+        wd.str = NULL;
339
+        wd.len = 0;
340
+    }
341
+    free(hd_malware.cfduid);
342
+    free(hd_malware.session);
343
+    free(hd_presigned.cfduid);
344
+    free(hd_presigned.session);
345
+    free(submissionID);
346
+    free(authenticity_token);
136 347
     if (fromStream) {
137 348
         remove(filename);
138 349
         free(filename);
... ...
@@ -141,6 +446,7 @@ end:
141 141
     return 0;
142 142
 }
143 143
 
144
+
144 145
 char *read_stream(void)
145 146
 {
146 147
     char *filename;
... ...
@@ -26,6 +26,9 @@ Required option for setting the name of the sender for the submission.
26 26
 .TP
27 27
 \fB-p FILE\fR
28 28
 Submit a file that reports as a false positive (ClamAV flags the file as virus). FILE can be \- to specify stdin. Mutually exclusive with \-n.
29
+.TP
30
+\fB-V VIRUS\fR
31
+The name of the virus detected as false positive. This option is required for false positive submissions.
29 32
 .SH "AUTHOR"
30 33
 .LP 
31 34
 Shawn Webb <swebb@sourcefire.com>