~ubuntu-branches/ubuntu/feisty/clamav/feisty

« back to all changes in this revision

Viewing changes to libclamav/phishcheck.c

  • Committer: Bazaar Package Importer
  • Author(s): Kees Cook
  • Date: 2007-02-20 10:33:44 UTC
  • mto: This revision was merged to the branch mainline in revision 16.
  • Revision ID: james.westby@ubuntu.com-20070220103344-zgcu2psnx9d98fpa
Tags: upstream-0.90
ImportĀ upstreamĀ versionĀ 0.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
/*
2
2
 *  Detect phishing, based on URL spoofing detection.
3
3
 *
4
 
 *  Copyright (C) 2007-2008 Sourcefire, Inc.
5
 
 *
6
 
 *  Authors: Tƶrƶk Edvin
 
4
 *  Copyright (C) 2006 Tļæ½rļæ½k Edvin <edwintorok@gmail.com>
7
5
 *
8
6
 *  This program is free software; you can redistribute it and/or modify
9
 
 *  it under the terms of the GNU General Public License version 2 as
10
 
 *  published by the Free Software Foundation.
 
7
 *  it under the terms of the GNU General Public License as published by
 
8
 *  the Free Software Foundation; either version 2 of the License, or
 
9
 *  (at your option) any later version.
11
10
 *
12
11
 *  This program is distributed in the hope that it will be useful,
13
12
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18
17
 *  along with this program; if not, write to the Free Software
19
18
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20
19
 *  MA 02110-1301, USA.
 
20
 *
 
21
 *  $Log: phishcheck.c,v $
 
22
 *  Revision 1.22  2007/02/12 20:34:31  tkojm
 
23
 *  fix hexurl regex (bb#288)
 
24
 *
 
25
 *  Revision 1.21  2007/02/11 00:41:13  tkojm
 
26
 *  remove some gcc warnings
 
27
 *
 
28
 *  Revision 1.20  2007/01/30 18:29:21  tkojm
 
29
 *  fix some includes
 
30
 *
 
31
 *  Revision 1.19  2007/01/13 19:39:21  tkojm
 
32
 *  phishing fixes (bb#157)
 
33
 *
 
34
 *  Revision 1.18  2007/01/12 17:36:53  tkojm
 
35
 *  add img url link-type filtering
 
36
 *
 
37
 *  Revision 1.17  2007/01/12 17:29:09  tkojm
 
38
 *  phishing patch from Edwin (closes bb#157, #174, #222, #224)
 
39
 *
 
40
 *  Revision 1.16  2006/12/20 01:23:50  tkojm
 
41
 *  options cleanup
 
42
 *
 
43
 *  Revision 1.15  2006/12/19 20:30:17  tkojm
 
44
 *  fix some compiler warnings
 
45
 *
 
46
 *  Revision 1.14  2006/10/14 23:52:01  tkojm
 
47
 *  code cleanup
 
48
 *
 
49
 *  Revision 1.13  2006/10/10 23:51:49  tkojm
 
50
 *  apply patches for the anti-phish code from Edwin
 
51
 *
 
52
 *  Revision 1.12  2006/10/08 18:55:15  tkojm
 
53
 *  fix crash in phishing code on database reload (Edvin Torok)
 
54
 *
 
55
 *  Revision 1.11  2006/10/07 11:00:46  tkojm
 
56
 *  make the experimental anti-phishing code more thread safe
 
57
 *
 
58
 *  Revision 1.10  2006/09/27 14:23:14  njh
 
59
 *  Ported to VS2005
 
60
 *
 
61
 *  Revision 1.9  2006/09/26 18:55:36  njh
 
62
 *  Fixed portability issues
 
63
 *
 
64
 *  Revision 1.8  2006/09/19 16:52:09  njh
 
65
 *  Fixed inconsistency between phishcheck.c and phishcheck.h
 
66
 *
 
67
 *  Revision 1.7  2006/09/18 17:10:07  njh
 
68
 *  Fix compilation error on Solaris 10
 
69
 *
 
70
 *  Revision 1.6  2006/09/16 15:49:27  acab
 
71
 *  phishing: fixed bugs and updated docs
 
72
 *
 
73
 *  Revision 1.5  2006/09/16 05:59:14  njh
 
74
 *  Fixed compiler warning
 
75
 *
 
76
 *  Revision 1.4  2006/09/16 05:39:54  njh
 
77
 *  Tidied print statement
 
78
 *
 
79
 *  Revision 1.3  2006/09/15 16:27:50  njh
 
80
 *  Better way to find string length in str_strip
 
81
 *
 
82
 *  Revision 1.2  2006/09/14 08:59:37  njh
 
83
 *  Fixed some NULL pointers
 
84
 *
 
85
 *  Revision 1.1  2006/09/12 19:38:39  acab
 
86
 *  Phishing module merge - libclamav
 
87
 *
 
88
 *  Revision 1.28  2006/09/09 09:49:27  edwin
 
89
 *  Fix Solaris compilation problem
 
90
 *
 
91
 *  Revision 1.27  2006/08/28 08:43:06  edwin
 
92
 *  Fixed a few minor leaks.
 
93
 *  Valgrind now says:"All heap blocks were freed -- no leaks are possible"
 
94
 *
 
95
 *  Revision 1.26  2006/08/20 21:18:11  edwin
 
96
 *  Added the script used to generate iana_tld.sh
 
97
 *  Added checks for phish_domaincheck_db
 
98
 *  Added phishing module design document from wiki (as discussed with aCaB).
 
99
 *  Updated .wdb/.pdb format documentation (in regex_list.c)
 
100
 *  Fixed some memory leaks in regex_list.c
 
101
 *  IOW: cleanups before the deadline.
 
102
 *  I consider my module to be ready for evaluation now.
 
103
 *
 
104
 *  Revision 1.25  2006/08/19 21:08:47  edwin
 
105
 *  Fixed:Forgot to add form tag handling when it contains images.
 
106
 *  Various fixes to get rid of gcc warnings.
 
107
 *
 
108
 *  Revision 1.24  2006/08/19 13:30:34  edwin
 
109
 *  iana_tld.h was missing from the list of header files.
 
110
 *  commentedout network code (unused currently)
 
111
 *
 
112
 *  Revision 1.23  2006/08/17 20:31:43  edwin
 
113
 *  Disable extracting hrefs from mails in mbox, if: we aren't scanning for phish, and mailfollowurls is off.
 
114
 *  Fix a still reachable leak. Remove unneeded build_regex_list export.
 
115
 *
 
116
 *  Revision 1.22  2006/08/12 14:35:34  edwin
 
117
 *  Fix some compiler warnings.
 
118
 *  Fix an assertion failure in regex_list.
 
119
 *  Interpret display links that start with http|https|ftp, always as an URL.
 
120
 *
 
121
 *  Revision 1.21  2006/08/06 20:27:07  edwin
 
122
 *  New option to enable phish scan for all domains (disabled by default).
 
123
 *  You will now have to run clamscan --phish-scan-alldomains to have any phishes detected.
 
124
 *  Updated phishcheck control flow to better incorporate the domainlist.
 
125
 *  Updated manpage with new options.
 
126
 *
 
127
 *  TODO:there is a still-reachable leak in regex_list.c
 
128
 *
 
129
 *  Revision 1.20  2006/08/01 20:19:14  edwin
 
130
 *  Integrate domainlist check into phishcheck. Warning: enabled by default.
 
131
 *  Regex bracket handling update.
 
132
 *  Better regex paranthesized & alternate expression handling.
 
133
 *
 
134
 
 
135
case CL_PHISH_HOST_NOT_LISTED:
 
136
 return "Host not listed in .pdb -> not checked";*  Revision 1.19  2006/07/31 20:12:30  edwin
 
137
 *  Preliminary support for domain databases (domains to check by phishmodule)
 
138
 *  Better memory allocation failure handling in regex_list
 
139
 *
21
140
 */
22
141
 
23
142
#if HAVE_CONFIG_H
24
143
#include "clamav-config.h"
25
144
#endif
26
145
 
 
146
#ifdef CL_EXPERIMENTAL
 
147
 
 
148
#ifndef CL_DEBUG
 
149
#define NDEBUG
 
150
#endif
 
151
 
27
152
#ifdef CL_THREAD_SAFE
28
153
#ifndef _REENTRANT
29
154
#define _REENTRANT
31
156
#endif
32
157
 
33
158
#include <stdio.h>
 
159
#include <stdlib.h>
 
160
#include <errno.h>
34
161
#include <string.h>
 
162
#ifdef  HAVE_STRINGS_H
 
163
#include <strings.h>
 
164
#endif
35
165
#include <ctype.h>
36
 
 
 
166
#include <limits.h>
37
167
#include "clamav.h"
38
 
#include "cltypes.h"
 
168
#ifndef C_WINDOWS
 
169
#include <netdb.h>
 
170
#include <netinet/in.h>
 
171
#endif
 
172
 
 
173
#if defined(HAVE_READDIR_R_3) || defined(HAVE_READDIR_R_2)
 
174
#include <stddef.h>
 
175
#endif
 
176
 
 
177
#include <sys/types.h>
 
178
#ifndef C_WINDOWS
 
179
#include <sys/socket.h>
 
180
#endif
 
181
#ifdef  HAVE_REGEX_H
 
182
#include <regex.h>
 
183
#endif
 
184
 
 
185
#include <pthread.h>
 
186
 
39
187
#include "others.h"
 
188
#include "defaults.h"
 
189
#include "str.h"
 
190
#include "filetypes.h"
 
191
#include "mbox.h"
40
192
#include "htmlnorm.h"
41
193
#include "phishcheck.h"
 
194
#include "phish_whitelist.h"
42
195
#include "phish_domaincheck_db.h"
43
 
#include "phish_whitelist.h"
44
196
#include "regex_list.h"
45
197
#include "iana_tld.h"
46
 
#include "iana_cctld.h"
47
 
#include "scanners.h"
48
 
#include "sha256.h"
49
 
#include <assert.h>
50
 
 
51
 
#include "mpool.h"
52
198
 
53
199
#define DOMAIN_REAL 1
54
200
#define DOMAIN_DISPLAY 0
55
201
 
56
202
#define PHISHY_USERNAME_IN_URL 1
57
203
#define PHISHY_NUMERIC_IP      2
58
 
#define REAL_IS_MAILTO         4
 
204
#define REAL_IS_MAILTO           4
59
205
/* this is just a flag, so that the displayed url will be parsed as mailto too, for example
60
206
 * <a href='mailto:somebody@yahoo.com'>to:somebody@yahoo.com</a>*/
61
207
#define DOMAIN_LISTED            8
62
208
#define PHISHY_CLOAKED_NULL     16
63
 
 
 
209
#define PHISHY_HEX_URL          32
64
210
 
65
211
/*
66
212
* Phishing design documentation,
67
213
(initially written at http://wiki.clamav.net/index.php/phishing_design as discussed with aCaB)
68
214
 
69
 
TODO: update this doc whenever behaviour changes
 
215
*Warning*: if flag *--phish-scan-alldomains* (or equivalent clamd/clamav-milter config option) isn't given, then phishing scanning is done only for domains listed in daily.pdb.
 
216
If your daily.pdb is empty, then by default NO PHISHING is DONE, UNLESS you give the *--phish-scan-alldomains*
 
217
This is just a side-effect, daily.pdb is empty, because it isn't yet officialy in daily.cvd.
70
218
 
71
219
phishingCheck() determines if @displayedLink is  a legit representation of @realLink.
72
220
 
73
221
Steps:
74
222
 
75
 
1. if _realLink_ == _displayLink_ => CLEAN
 
223
1. if _realLink_ *==* _displayLink_ => *CLEAN*
76
224
 
77
225
2. url cleanup (normalization)
78
226
- whitespace elimination
79
 
 strip all spaces, and leading and trailing garbage.
80
 
 When matching we have to keep in account whether we stripped any spaces or not.
81
 
 See str_fixup_spaces.
82
227
- html entity conversion
83
 
- handle hex-encoded characters
84
228
- convert hostname to lowercase
85
229
- normalize \ to /
 
230
If there is a dot after the last space, then all spaces are replaced with dots,
 
231
otherwise spaces are stripped.
 
232
So both: 'Go to yahoo.com', and 'Go to e b a y . c o m', and 'Go to ebay. com' will work.
 
233
 
86
234
 
87
235
3. Matched the urls against a _whitelist_:
88
236
a _realLink_, _displayedLink_ pair is matched against the _whitelist_.
89
237
the _whitelist_ is a list of pairs of realLink, displayedLink. Any of the elements of those pairs can be a _regex_.
90
238
 if url *is found* in _whitelist_ --> *CLEAN*
91
239
 
92
 
4. URL is looked up in the _domainlist_
 
240
4. URL is looked up in the _domainlist_, unless disabled via flags (_--phish-scan-alldomains_).
93
241
The _domainlist_ is a list of pairs of realLink, displayedLink (any of which can be regex).
94
242
This is the list of domains we do phishing detection for (such as ebay,paypal,chase,....)
95
243
We can't decide to stop processing here or not, so we just set a flag.
115
263
 
116
264
10. Hostname of real URL is extracted.
117
265
 
 
266
11. Skip cid: displayedLink urls (images embedded in mails).
 
267
 
118
268
12. Numeric IP detection.
119
269
If url is a numeric IP, then -> phish.
120
270
Maybe we should do DNS lookup?
 
271
Maybe we should disable numericIP checks for --phish-scan-alldomains?
121
272
 
122
273
13. isURL(displayedLink).
123
274
Checks if displayedLink is really a url.
136
287
/* Constant strings and tables */ 
137
288
static char empty_string[]="";
138
289
 
 
290
#define ANY_CLOAK "(((0[xX])?[a-fA-F0-9])+\\.?)+"
 
291
#define CLOAK_REGEX_HEXURL "("ANY_CLOAK")?0[xX][a-fA-F0-9]+\\.?"ANY_CLOAK
 
292
#define OCTAL_CLOAK "("ANY_CLOAK")?000[0-9]+\\.?"ANY_CLOAK
 
293
#define DWORD_CLOAK "[0-9]{8,}"
 
294
 
 
295
static const char cloaked_host_regex[] = "^(("CLOAK_REGEX_HEXURL")|("OCTAL_CLOAK")|("DWORD_CLOAK"))$";
 
296
 
 
297
 
 
298
static const char tld_regex[] = "^"iana_tld"$";
 
299
static const char cctld_regex[] = "^"iana_cctld"$";
139
300
static const char dotnet[] = ".net";
140
301
static const char adonet[] = "ado.net";
141
302
static const char aspnet[] = "asp.net";
142
 
/* ; is replaced by ' ' so omit it here*/
143
 
static const char lt[]="&lt";
144
 
static const char gt[]="&gt";
 
303
static const char lt[]="&lt;";
 
304
static const char gt[]="&gt;";
 
305
static const char cid[] = "cid:";
145
306
static const char src_text[] = "src";
146
307
static const char href_text[] = "href";
147
 
static const char mailto[] = "mailto:";
148
 
static const char mailto_proto[] = "mailto://";
149
 
static const char https[]="https:";
150
 
static const char http[]="http:";
151
 
static const char ftp[] = "ftp:";
152
 
 
153
308
static const size_t href_text_len = sizeof(href_text);
154
309
static const size_t src_text_len = sizeof(src_text);
 
310
static const size_t cid_len = sizeof(cid)-1;
155
311
static const size_t dotnet_len = sizeof(dotnet)-1;
156
312
static const size_t adonet_len = sizeof(adonet)-1;
157
313
static const size_t aspnet_len = sizeof(aspnet)-1;
158
314
static const size_t lt_len = sizeof(lt)-1;
159
315
static const size_t gt_len = sizeof(gt)-1;
160
 
static const size_t mailto_len = sizeof(mailto)-1;
161
 
static const size_t mailto_proto_len = sizeof(mailto_proto)-1;
162
 
static const size_t https_len  = sizeof(https)-1;
163
 
static const size_t http_len  = sizeof(http)-1;
164
 
static const size_t ftp_len  = sizeof(ftp)-1;
165
316
 
 
317
/*static const char* url_regex="^ *([[:alnum:]%_-]+:(//)?)?([[:alnum:]%_-]@)*[[:alnum:]%_-]+\\.([[:alnum:]%_-]+\\.)*[[:alnum:]_%-]+(/[[:alnum:];:@$=?&/.,%_-]+) *$";*/
166
318
/* for urls, including mailto: urls, and (broken) http:www... style urls*/
167
319
/* refer to: http://www.w3.org/Addressing/URL/5_URI_BNF.html
168
320
 * Modifications: don't allow empty domains/subdomains, such as www..com <- that is no url
169
321
 * So the 'safe' char class has been split up
170
322
 * */
171
323
/* character classes */
 
324
#define URI_alpha       "a-zA-Z"
172
325
#define URI_digit       "0-9"
 
326
#define URI_safe_nodot  "-$_@&"
 
327
#define URI_safe        "-$_@.&"
 
328
#define URI_extra       "!*\"'(),"
 
329
#define URI_reserved    "=;/#?: "
 
330
#define URI_national    "{}|[]\\^~"
 
331
#define URI_punctuation "<>"
 
332
 
 
333
#define URI_hex          "[0-9a-fA-f]"
 
334
#define URI_escape      "%"URI_hex"{2}"
 
335
#define URI_xalpha "([" URI_safe URI_alpha URI_digit  URI_extra "]|"URI_escape")" /* URI_safe has to be first, because it contains - */
 
336
#define URI_xalpha_nodot "([" URI_safe_nodot URI_alpha URI_digit URI_extra "]|"URI_escape")"
 
337
 
 
338
#define URI_xalphas URI_xalpha"+"
 
339
#define URI_xalphas_nodot URI_xalpha_nodot"*"
 
340
 
 
341
#define URI_ialpha  "["URI_alpha"]"URI_xalphas_nodot""
 
342
#define URI_xpalpha URI_xalpha"|\\+"
 
343
#define URI_xpalpha_nodot URI_xalpha_nodot"|\\+"
 
344
#define URI_xpalphas "("URI_xpalpha")+"
 
345
#define URI_xpalphas_nodot "("URI_xpalpha_nodot")+"
 
346
 
 
347
#define URI_scheme URI_ialpha
 
348
#define URI_tld iana_tld
 
349
#define URI_path1 URI_xpalphas_nodot"\\.("URI_xpalphas_nodot"\\.)*"
 
350
#define URI_path2 URI_tld
 
351
#define URI_path3 "(/("URI_xpalphas"/?)*)?"
 
352
 
 
353
#define URI_search "("URI_xalphas"\\+)*"
 
354
#define URI_fragmentid URI_xalphas
 
355
 
173
356
#define URI_IP_digits "["URI_digit"]{1,3}"
174
 
#define URI_path_start "[/?:]?"
175
 
#define URI_numeric_path URI_IP_digits"(\\."URI_IP_digits"){3}"URI_path_start
176
 
#define URI_numeric_URI "(http|https|ftp:(//)?)?"URI_numeric_path
177
 
#define URI_numeric_fragmentaddress URI_numeric_URI
178
 
 
 
357
#define URI_numeric_path URI_IP_digits"(\\."URI_IP_digits"){3}(:"URI_xpalphas_nodot")?(/("URI_xpalphas"/?)*)?"
 
358
#define URI_numeric_URI "("URI_scheme":(//)?)?"URI_numeric_path"(\\?" URI_search")?"
 
359
#define URI_numeric_fragmentaddress URI_numeric_URI"(#"URI_fragmentid")?"
 
360
 
 
361
#define URI_URI1 "("URI_scheme":(//)?)?"URI_path1
 
362
#define URI_URI2 URI_path2
 
363
#define URI_URI3 URI_path3"(\\?" URI_search")?"
 
364
 
 
365
#define URI_fragmentaddress1 URI_URI1
 
366
#define URI_fragmentaddress2 URI_URI2
 
367
#define URI_fragmentaddress3 URI_URI3"(#"URI_fragmentid")?"
 
368
 
 
369
#define URI_CHECK_PROTOCOLS "(http|https|ftp)://.+"
179
370
 
180
371
/*Warning: take care when modifying this regex, it has been tweaked, and tuned, just don't break it please.
181
372
 * there is fragmentaddress1, and 2  to work around the ISO limitation of 509 bytes max length for string constants*/
183
374
 
184
375
/* generated by contrib/phishing/generate_tables.c */
185
376
static const short int hextable[256] = {
186
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
187
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
188
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
189
 
       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
190
 
       0x0, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
191
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
192
 
       0x0, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
193
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
194
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
195
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
196
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
197
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
198
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
199
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
200
 
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
 
377
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
378
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
379
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
380
       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
381
       0x0, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
382
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
383
       0x0, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
384
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
385
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
386
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
387
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
388
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
389
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
390
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
 
391
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 
201
392
       0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
202
393
};
203
394
 
204
395
/* Prototypes*/
205
 
static void string_init_c(struct string* dest,char* data);
206
 
static int string_assign_concatenated(struct string* dest, const char* prefix, const char* begin, const char* end);
207
 
static void string_assign_null(struct string* dest);
208
 
static char *rfind(char *start, char c, size_t len);
209
 
static char hex2int(const unsigned char* src);
 
396
static  inline  void string_init_c(struct string* dest,char* data);
 
397
static  void    string_assign_null(struct string* dest);
 
398
static  char    *rfind(char *start, char c, size_t len);
 
399
static inline char hex2int(const unsigned char* src);
 
400
static int isTLD(const struct phishcheck* pchk,const char* str,int len);
210
401
static enum phish_status phishingCheck(const struct cl_engine* engine,struct url_check* urls);
211
402
static const char* phishing_ret_toString(enum phish_status rc);
212
403
 
213
404
static void url_check_init(struct url_check* urls)
214
405
{
215
 
        string_init_c(&urls->realLink, NULL);
216
 
        string_init_c(&urls->displayLink, NULL);
217
 
        string_init_c(&urls->pre_fixup.pre_displayLink, NULL);
 
406
        urls->realLink.refcount=0;
 
407
        urls->realLink.data=empty_string;
 
408
        urls->realLink.ref=NULL;
 
409
        urls->displayLink.refcount=0;
 
410
        urls->displayLink.data=empty_string;
 
411
        urls->displayLink.ref=NULL;
218
412
}
219
413
 
220
414
/* string reference counting implementation,
238
432
        }
239
433
}
240
434
 
241
 
/* always use the string_assign when assigning to a string, this makes sure the old one's reference count is incremented*/
 
435
/* always use the string_assign when assigning to a string, this makes sure the old one's refcount is decremented*/
242
436
static void string_assign(struct string* dest,struct string* src)
243
437
{
244
438
        string_free(dest);
252
446
/* it doesn't free old string, use only for initialization
253
447
 * Doesn't allow NULL pointers, they are replaced by pointer to empty string
254
448
 * */
255
 
static void string_init_c(struct string* dest,char* data)
 
449
static inline void string_init_c(struct string* dest,char* data)
256
450
{
257
 
        dest->refcount = data ? 1 : 0;
 
451
        dest->refcount = 1;
258
452
        dest->data = data ? data : empty_string;
259
453
        dest->ref = NULL;
260
454
}
261
455
 
262
 
/* assigns to @dest the string made from concatenating @prefix with the string between @begin and @end */
263
 
static int string_assign_concatenated(struct string* dest, const char* prefix, const char* begin, const char* end)
264
 
{
265
 
        const size_t prefix_len = strlen(prefix);
266
 
        char* ret = cli_malloc(prefix_len + end - begin + 1);
267
 
        if(!ret)
268
 
                return CL_EMEM;
269
 
        strncpy(ret, prefix, prefix_len);
270
 
        strncpy(ret+prefix_len, begin, end-begin);
271
 
        ret[prefix_len+end-begin]='\0';
272
 
        string_free(dest);
273
 
        string_init_c(dest, ret);
274
 
        return CL_SUCCESS;
275
 
}
276
 
 
277
456
/* make a copy of the string between start -> end*/
278
457
static int string_assign_dup(struct string* dest,const char* start,const char* end)
279
458
{
280
 
        char* ret  = cli_malloc(end-start+1);
 
459
        char*       ret  = cli_malloc(end-start+1);
281
460
        if(!ret)
282
461
                return CL_EMEM;
283
462
        strncpy(ret,start,end-start);
284
463
        ret[end-start]='\0';
285
464
 
286
465
        string_free(dest);
287
 
        string_init_c(dest, ret);
 
466
        dest->data=ret;
 
467
        dest->refcount=1;
 
468
        dest->ref=NULL;
288
469
        return CL_SUCCESS;
289
470
}
290
471
 
291
 
static void string_assign_null(struct string* dest)
 
472
static inline void string_assign_null(struct string* dest)
292
473
{
293
 
        if(dest) {
294
 
                string_free(dest);
295
 
                dest->data=empty_string;
296
 
                dest->refcount=-1;/* don't free it! */
297
 
                dest->ref=NULL;
298
 
        }
 
474
        string_free(dest);
 
475
        dest->data=empty_string;
 
476
        dest->refcount=-1;/* don't free it! */
 
477
        dest->ref=NULL;
299
478
}
300
479
 
301
480
/* this string uses portion of another string*/
312
491
{
313
492
        string_free(&url->realLink);
314
493
        string_free(&url->displayLink);
315
 
        string_free(&url->pre_fixup.pre_displayLink);
316
494
}
317
495
 
318
496
static int build_regex(regex_t* preg,const char* regex,int nosub)
319
497
{
320
498
        int rc;
321
 
        cli_dbgmsg("Phishcheck: Compiling regex: %s\n",regex);
322
 
        rc = cli_regcomp(preg,regex,REG_EXTENDED|REG_ICASE|(nosub ? REG_NOSUB :0));
 
499
        cli_dbgmsg("Compiling regex:%s\n",regex);
 
500
        rc = regcomp(preg,regex,REG_EXTENDED|REG_ICASE|(nosub ? REG_NOSUB :0));
323
501
        if(rc) {
324
 
 
325
 
                size_t buflen = cli_regerror(rc,preg,NULL,0);
 
502
        
 
503
#ifdef  C_WINDOWS
 
504
                cli_errmsg("Error in compiling regex, disabling phishing checks\n");
 
505
#else
 
506
                size_t buflen = regerror(rc,preg,NULL,0);
326
507
                char *errbuf = cli_malloc(buflen);
327
 
 
 
508
                
328
509
                if(errbuf) {
329
 
                        cli_regerror(rc,preg,errbuf,buflen);
330
 
                        cli_errmsg("Phishcheck: Error in compiling regex:%s\nDisabling phishing checks\n",errbuf);
 
510
                        regerror(rc,preg,errbuf,buflen);
 
511
                        cli_errmsg("Error in compiling regex:%s\nDisabling phishing checks\n",errbuf);
331
512
                        free(errbuf);
332
513
                } else
333
 
                        cli_errmsg("Phishcheck: Error in compiling regex, disabling phishing checks. Additionally an Out-of-memory error was encountered while generating a detailed error message\n");
 
514
                        cli_errmsg("Error in compiling regex, disabling phishing checks. Additionaly an Out-of-memory error was encountered while generating a detailed error message\n");
 
515
#endif
334
516
                return 1;
335
517
        }
336
518
        return CL_SUCCESS;
337
519
}
338
520
 
 
521
/*static regex_t* host_preg = NULL;
 
522
static const char* host_regex="cid:.+|mailto:(.+)|([[:alpha:]]+://)?(([^:/?]+@)+([^:/?]+)([:/?].+)?|([^@:/?]+)([:/?].+)?)"; <- this is slower than the function below
 
523
*/
339
524
/* allocates memory */
340
 
static int get_host(const char* URL,int isReal,int* phishy,const char **hstart, const char **hend)
 
525
static int get_host(const struct phishcheck* s,struct string* dest,const char* URL,int isReal,int* phishy)
341
526
{
 
527
        const char mailto[] = "mailto:";
342
528
        int rc,ismailto = 0;
343
529
        const char* start;
344
530
        const char* end=NULL;
345
531
        if(!URL) {
346
 
                *hstart=*hend=NULL;
 
532
                string_assign_null(dest);
347
533
                return 0;
348
534
        }
349
535
        start = strstr(URL,"://");
350
536
        if(!start) {
351
 
                if(!strncmp(URL,mailto,mailto_len)) {
352
 
                        start = URL + mailto_len;
 
537
                if(!strncmp(URL,mailto,sizeof(mailto)-1)) {
 
538
                        start = URL + sizeof(mailto)-1;
353
539
                        ismailto = 1;
354
540
                }
355
541
                else if (!isReal && *phishy&REAL_IS_MAILTO) {
361
547
                        ismailto = 1;
362
548
                }
363
549
                else {
 
550
/*                      if(!strncmp(URL,"cid:",4)) {handled in phishcheck
 
551
                                string_assign_null(dest);
 
552
                                return;* cid: image, nothing to verify
 
553
                        }
 
554
*/
364
555
                        start=URL;/*URL without protocol*/
365
556
                        if(isReal)
366
 
                                cli_dbgmsg("Phishcheck: Real URL without protocol: %s\n",URL);
 
557
                                cli_dbgmsg("PH:Real URL without protocol:%s\n",URL);
367
558
                        else ismailto=2;/*no-protocol, might be mailto, @ is no problem*/
368
559
                }
369
560
        }
371
562
                start += 3;     /* :// */
372
563
 
373
564
        if(!ismailto || !isReal) {
374
 
                const char *realhost,*tld;
 
565
                const char *realhost;
375
566
 
376
567
                do {
377
568
                        end  = start + strcspn(start,":/?");
378
569
                        realhost = strchr(start,'@');
379
570
 
380
 
                        if(realhost == NULL || (start!=end && realhost>end)) {
381
 
                                /*don't check beyond end of hostname*/ 
 
571
                        if(realhost == NULL)
382
572
                                break;
 
573
 
 
574
                        if(start!=end && realhost>end)
 
575
                                /*don't check beyond end of hostname*/
 
576
                                realhost = NULL;
 
577
 
 
578
                        if(realhost) {
 
579
                                const char* tld = strrchr(realhost,'.');
 
580
                                rc = tld ? isTLD(s,tld,tld-realhost-1) : 0;
 
581
                                if(rc < 0)
 
582
                                        return rc;
 
583
                                if(rc)
 
584
                                        *phishy |= PHISHY_USERNAME_IN_URL;/* if the url contains a username that is there just to fool people,
 
585
                                        like http://www.ebay.com@somevilplace.someevildomain.com/ */
 
586
                                start=realhost+1;/*skip the username*/
383
587
                        }
384
 
 
385
 
                        tld = strrchr(realhost,'.');
386
 
                        rc = tld ? !!in_tld_set(tld,strlen(tld)) : 0;
387
 
                        if(rc < 0)
388
 
                                return rc;
389
 
                        if(rc)
390
 
                                *phishy |= PHISHY_USERNAME_IN_URL;/* if the url contains a username that is there just to fool people,
391
 
                                                                     like http://banksite@example.com/ */
392
 
                        start = realhost+1;/*skip the username*/
393
588
                } while(realhost);/*skip over multiple @ characters, text following last @ character is the real host*/
394
589
        }
395
 
        else if (ismailto && isReal)
 
590
        else
 
591
        if (ismailto && isReal)
396
592
                *phishy |= REAL_IS_MAILTO;
397
593
 
398
594
        if(!end) {
399
 
                end  = start + strcspn(start,":/?");/*especially important for mailto:somebody@yahoo.com?subject=...*/
 
595
                end  = start+strcspn(start,":/?");/*especially important for mailto:somebody@yahoo.com?subject=...*/
400
596
                if(!end)
401
597
                        end  = start + strlen(start);
402
598
        }
403
 
        *hstart = start;
404
 
        *hend = end;
 
599
 
 
600
        if(( rc = string_assign_dup(dest,start,end) ))
 
601
                return rc;
405
602
        return 0;
406
603
}
407
604
 
 
605
static int isCountryCode(const struct phishcheck* s,const char* str)
 
606
{
 
607
        return str ? !regexec(&s->preg_cctld,str,0,NULL,0) : 0;
 
608
}
 
609
 
 
610
static int isTLD(const struct phishcheck* pchk,const char* str,int len)
 
611
{
 
612
        if (!str)
 
613
                return 0;
 
614
        else {
 
615
                char*   s  = cli_malloc(len+1);
 
616
                int rc;
 
617
 
 
618
                if(!s)
 
619
                        return CL_EMEM;
 
620
                strncpy(s,str,len);
 
621
                s[len]='\0';
 
622
                rc = !regexec(&pchk->preg_tld,s,0,NULL,0);
 
623
                free(s);
 
624
                return rc ? 1 : 0;
 
625
        }
 
626
}
408
627
 
409
628
/*
410
629
 * memrchr isn't standard, so I use this
422
641
        return (p < start) ? NULL : p;
423
642
}
424
643
 
425
 
static void get_domain(struct string* dest,struct string* host)
 
644
static void get_domain(const struct phishcheck* pchk,struct string* dest,struct string* host)
426
645
{
427
646
        char* domain;
428
647
        char* tld = strrchr(host->data,'.');
429
648
        if(!tld) {
430
 
                cli_dbgmsg("Phishcheck: Encountered a host without a tld? (%s)\n",host->data);
 
649
                cli_dbgmsg("PH:What? A host without a tld? (%s)\n",host->data);
431
650
                string_assign(dest,host);
432
651
                return;
433
652
        }
434
 
        if(in_cctld_set(tld+1, strlen(tld+1))) {
435
 
                const char* countrycode = tld+1;
 
653
        if(isCountryCode(pchk,tld+1)) {
 
654
                const char* countrycode=tld+1;
436
655
                tld = rfind(host->data,'.',tld-host->data-1);
437
656
                if(!tld) {
438
 
                        cli_dbgmsg("Phishcheck: Weird, a name with only 2 levels (%s)\n",
439
 
                                host->data);
 
657
                        cli_dbgmsg("PH:Weird, a name with only 2 levels (%s)\n",host->data);
440
658
                        string_assign(dest,host);
441
659
                        return;
442
660
                }
443
 
                if(!in_tld_set(tld+1, countrycode-tld-2)) {
 
661
                if(!isTLD(pchk,tld+1,countrycode-tld-1)) {
444
662
                        string_assign_ref(dest,host,tld+1);
445
663
                        return;/*it was a name like: subdomain.domain.uk, return domain.uk*/
446
664
                }
454
672
        string_assign_ref(dest,host,domain+1);
455
673
}
456
674
 
 
675
 
 
676
/*
 
677
int ip_reverse(struct url_check* urls,int isReal)
 
678
{
 
679
        const char* host = isReal ? urls->realLink.data : urls->displayLink.data;
 
680
        struct hostent *he = gethostbyname (host);
 
681
        if (he)
 
682
        {
 
683
                char *addr = 0;
 
684
                switch (he->h_addrtype)
 
685
                {
 
686
                        case AF_INET:
 
687
                          addr = inet_ntoa (*(struct in_addr *) he->h_addr);
 
688
                          break;
 
689
                }
 
690
                if (addr && strcmp (he->h_name, addr) == 0)
 
691
                {
 
692
                        char *h_addr_copy = strdup (he->h_addr);
 
693
                        if (h_addr_copy == NULL)
 
694
                            he = NULL;
 
695
                        else
 
696
                        {
 
697
                              he = gethostbyaddr (h_addr_copy, he->h_length, he->h_addrtype);
 
698
                              free (h_addr_copy);
 
699
                        }
 
700
                }
 
701
             if (he)
 
702
                string_assign_dup(isReal ? &urls->realLink : &urls->displayLink,he->h_name,he->h_name+strlen(he->h_name));
 
703
    }
 
704
    return 0;
 
705
}
 
706
* frees its argument, and allocates memory*
 
707
void reverse_lookup(struct url_check* url,int isReal)
 
708
{
 
709
        ip_reverse(url,isReal);
 
710
}
 
711
*/
457
712
static int isNumeric(const char* host)
458
713
{
459
714
        int len = strlen(host);
471
726
 
472
727
static int isSSL(const char* URL)
473
728
{
474
 
        return URL ? !strncmp(https,URL,https_len) : 0;
 
729
        const char https[]="https://";
 
730
        return URL ? !strncmp(https,URL,sizeof(https)-1) : 0;
475
731
}
476
732
 
477
733
/* deletes @what from the string @begin.
479
735
static void
480
736
str_hex_to_char(char **begin, const char **end)
481
737
{
482
 
        char *firsthex, *sbegin_;
483
738
        char *sbegin = *begin;
484
739
        const char *str_end = *end;
485
740
 
486
 
        if(str_end <= &sbegin[1])
 
741
        if(str_end <= sbegin)
 
742
                return;
 
743
 
 
744
        if(strlen(sbegin) <= 2)
487
745
                return;
488
746
 
489
747
        /* convert leading %xx*/
492
750
                sbegin += 2;
493
751
        }
494
752
        *begin = sbegin++;
495
 
        do {
496
 
            sbegin_ = sbegin;
497
 
            firsthex = NULL;
498
 
            while(sbegin+3 <= str_end) {
499
 
                if (sbegin+3<=str_end && sbegin[0]=='%') {
500
 
                    const char* src = sbegin+3;
501
 
                    if (isxdigit(sbegin[1]) && isxdigit(sbegin[2])) {
 
753
        while(sbegin+3 < str_end) {
 
754
                while(sbegin+3<str_end && sbegin[0]=='%') {
 
755
                        const char* src = sbegin+3;
502
756
                        *sbegin = hex2int((unsigned char*)sbegin+1);
503
 
                        if (*sbegin == '%' && !firsthex)
504
 
                            firsthex = sbegin;
505
757
                        /* move string */
506
758
                        memmove(sbegin+1,src,str_end-src+1);
507
759
                        str_end -= 2;
508
 
                    }
509
760
                }
510
761
                sbegin++;
511
 
            }
512
 
            sbegin = sbegin_;
513
 
        } while (firsthex);
 
762
        }
514
763
        *end = str_end;
515
764
}
516
765
 
568
817
}
569
818
 
570
819
 
571
 
/* replace every occurrence of @c in @str with @r*/
572
 
static void str_replace(char* str,const char* end,char c,char r)
 
820
/* replace every occurence of @c in @str with @r*/
 
821
static inline void str_replace(char* str,const char* end,char c,char r)
573
822
{
574
 
        for(;str<=end;str++) {
 
823
        for(;str<end;str++) {
575
824
                if(*str==c)
576
825
                        *str=r;
577
826
        }
578
827
}
579
 
static void str_make_lowercase(char* str,size_t len)
 
828
static inline void str_make_lowercase(char* str,size_t len)
580
829
{
581
830
        for(;len;str++,len--) {
582
831
                *str = tolower(*str);
584
833
}
585
834
 
586
835
#define fix32(x) ((x)<32 ? 32 : (x))
587
 
static void clear_msb(char* begin)
 
836
static inline void clear_msb(char* begin)
588
837
{
589
838
        for(;*begin;begin++)
590
839
                *begin = fix32((*begin)&0x7f);
592
841
 
593
842
/*
594
843
 * Particularly yahoo puts links like this in mails:
595
 
 * http:/ /www.example.com
 
844
 * http:/ /mail.yahoo.com
596
845
 * So first step: delete space between / /
597
846
 *
598
847
 * Next there could be possible links like this:
603
852
 * <a href="www.yahoo.com">Check out yahoo.com</a>
604
853
 * Here we add a ., so we get: check.out.yahoo.com (it won't trigger)
605
854
 *
606
 
 * Old Rule for adding .: if substring from right contains dot, then add dot,
 
855
 * Rule for adding .: if substring from right contains dot, then add dot,
607
856
 *      otherwise strip space
608
 
 * New Rule: strip all spaces
609
 
 *  strip leading and trailing garbage
610
857
 *
611
858
 */
612
 
static void
 
859
static inline void
613
860
str_fixup_spaces(char **begin, const char **end)
614
861
{
615
 
        char* sbegin = *begin;
616
 
        const char* send = *end;
617
 
        if(!sbegin || !send || send < sbegin)
 
862
        char *space = strchr(*begin, ' ');
 
863
 
 
864
        if(space == NULL)
618
865
                return;
619
 
        /* strip spaces */
620
 
        str_strip(&sbegin, &send, " ",1);
621
 
        /* strip leading/trailing garbage */
622
 
        while(!isalnum(sbegin[0]&0xff) && sbegin <= send) sbegin++;
623
 
        while(!isalnum(send[0]&0xff) && send >= sbegin) send--;
624
 
 
625
 
        /* keep terminating slash character*/
626
 
        if(send[1] == '/') send++;
627
 
        *begin = sbegin;
628
 
        *end = send;
 
866
 
 
867
        /* strip any number of spaces after / */
 
868
        while((space > *begin) && (space[-1] == '/') && (space[0] == ' ') && (space < *end)) {
 
869
                memmove(space, space+1, *end-space+1);
 
870
                (*end)--;
 
871
        }
 
872
 
 
873
        for(space = rfind(*begin,' ',*end-*begin);space && space[0]!='.' && space<*end;space++)
 
874
                ;
 
875
        if(space && space[0]=='.')
 
876
                str_replace(*begin,*end,' ','.');
 
877
        else
 
878
                str_strip(begin,end," ",1);
629
879
}
630
880
 
631
881
/* allocates memory */
632
882
static int
633
 
cleanupURL(struct string *URL,struct string *pre_URL, int isReal)
 
883
cleanupURL(struct string *URL, int isReal)
634
884
{
635
885
        char *begin = URL->data;
636
886
        const char *end;
646
896
        len = strlen(begin);
647
897
        if(len == 0) {
648
898
                string_assign_null(URL);
649
 
                string_assign_null(pre_URL);
650
899
                return 0;
651
900
        }
652
901
 
654
903
        /*cli_dbgmsg("%d %d\n", end-begin, len);*/
655
904
        if(begin >= end) {
656
905
                string_assign_null(URL);
657
 
                string_assign_null(pre_URL);
658
906
                return 0;
659
907
        }
660
908
        while(isspace(*end))
661
909
                end--;
 
910
        /*TODO: convert \ to /, and stuff like that*/
662
911
        /* From mailscanner, my comments enclosed in {} */
663
 
        if(!strncmp(begin,dotnet,dotnet_len) || !strncmp(begin,adonet,adonet_len) || !strncmp(begin,aspnet,aspnet_len)) {
 
912
        if(!strncmp(begin,dotnet,dotnet_len) || !strncmp(begin,adonet,adonet_len) || !strncmp(begin,aspnet,aspnet_len))
664
913
                string_assign_null(URL);
665
 
                string_assign_null(pre_URL);
666
 
        }
667
914
        else {
668
915
                size_t host_len;
669
916
                char* host_begin;
670
917
                int rc;
671
918
 
672
919
                str_replace(begin,end,'\\','/');
673
 
                /* find beginning of hostname, because:
674
 
                 * - we want to keep only protocol, host, and 
675
 
                 *  strip path & query parameter(s) 
676
 
                 * - we want to make hostname lowercase*/
 
920
                str_strip(&begin,&end,"\"",1);
 
921
                str_strip(&begin,&end,lt,lt_len);
 
922
                str_strip(&begin,&end,gt,gt_len);
 
923
                /* convert hostname to lowercase, but only hostname! */
677
924
                host_begin = strchr(begin,':');
678
 
                while(host_begin && (host_begin < end) && (host_begin[1] == '/'))  host_begin++;
 
925
                while(host_begin && host_begin[1]=='/') host_begin++;
679
926
                if(!host_begin) host_begin=begin;
680
927
                else host_begin++;
681
 
                host_len = strcspn(host_begin,":/?");
682
 
                if(host_begin + host_len > end + 1) {
683
 
                        /* prevent hostname extending beyond end, it can happen
684
 
                         * if we have spaces at the end, we don't want those part of 
685
 
                         * the hostname */
686
 
                        host_len = end - host_begin + 1;
687
 
                } else {
688
 
                        /* cut the URL after the hostname */
689
 
                        /* @end points to last character we want to be part of the URL */
690
 
                        end = host_begin + host_len - 1;
691
 
                }
692
 
                host_begin[host_len] = '\0';
693
 
                /* convert hostname to lowercase, but only hostname! */
694
 
                str_make_lowercase(host_begin, host_len);
695
 
                /* some broken MUAs put > in the href, and then
696
 
                 * we get a false positive, so remove them */
697
 
                str_replace(begin,end,'<',' ');
698
 
                str_replace(begin,end,'>',' ');
699
 
                str_replace(begin,end,'\"',' ');
700
 
                str_replace(begin,end,';',' ');
701
 
                str_strip(&begin,&end,lt,lt_len);
702
 
                str_strip(&begin,&end,gt,gt_len);
 
928
                host_len = strcspn(host_begin,"/?");
 
929
                str_make_lowercase(host_begin,host_len);
703
930
                /* convert %xx to real value */
704
931
                str_hex_to_char(&begin,&end);
705
 
                if(isReal) {
706
 
                        /* htmlnorm converts \n to space, so we have to strip spaces */
707
 
                        str_strip(&begin, &end, " ", 1);
708
 
                }
709
 
                else {
710
 
                        /* trim space */
711
 
                        while((begin <= end) && (begin[0]==' '))  begin++;
712
 
                        while((begin <= end) && (end[0]==' ')) end--;
713
 
                }
714
 
                if (( rc = string_assign_dup(isReal ? URL : pre_URL,begin,end+1) )) {
715
 
                        string_assign_null(URL);
 
932
                str_fixup_spaces(&begin,&end);
 
933
                if (( rc = string_assign_dup(URL,begin,end+1) ))
716
934
                        return rc;
717
 
                }
718
 
                if(!isReal) {
719
 
                        str_fixup_spaces(&begin,&end);
720
 
                        if (( rc = string_assign_dup(URL, begin, end+1) )) {
721
 
                                return rc;
722
 
                        }
723
 
                }
 
935
                /*cli_dbgmsg("%p::%s\n",URL->data,URL->data);*/
724
936
        }
725
937
        return 0;
726
938
}
727
939
 
 
940
 
 
941
/* ---- runtime disable ------*/
 
942
void phish_disable(struct cl_engine* engine, const char* reason)
 
943
{
 
944
        cli_warnmsg("Disabling phishing checks, reason:%s\n",reason);
 
945
        phishing_done(engine);/* sets is_disabled, and frees allocated mem for phishcheck */
 
946
}
728
947
/* -------end runtime disable---------*/
729
 
int phishingScan(cli_ctx* ctx,tag_arguments_t* hrefs)
 
948
 
 
949
int phishingScan(message* m,const char* dir,cli_ctx* ctx,tag_arguments_t* hrefs)
730
950
{
731
 
        /* TODO: get_host and then apply regex, etc. */
732
951
        int i;
733
952
        struct phishcheck* pchk = (struct phishcheck*) ctx->engine->phishcheck;
734
 
        /* check for status of whitelist fatal error, etc. */
735
953
        if(!pchk || pchk->is_disabled)
736
954
                return CL_CLEAN;
737
955
 
738
 
        if(!ctx->found_possibly_unwanted)
739
 
                *ctx->virname=NULL;
740
 
#if 0
741
 
        FILE *f = fopen("/home/edwin/quarantine/urls","r");
742
 
        if(!f)
743
 
                abort();
744
 
        while(!feof(f)) {
745
 
                struct url_check urls;
746
 
                char line1[4096];
747
 
                char line2[4096];
748
 
                char line3[4096];
749
 
 
750
 
                fgets(line1, sizeof(line1), f);
751
 
                fgets(line2, sizeof(line2), f);
752
 
                fgets(line3, sizeof(line3), f);
753
 
                if(strcmp(line3, "\n") != 0) {
754
 
                        strcpy(line1, line2);
755
 
                        strcpy(line2, line3);
756
 
                        fgets(line3, sizeof(line3), f);
757
 
                        while(strcmp(line3, "\n") != 0) {
758
 
                                fgets(line3, sizeof(line3),f);
759
 
                        }
760
 
                }
761
 
                urls.flags = CL_PHISH_ALL_CHECKS;
762
 
                urls.link_type = 0;
763
 
                string_init_c(&urls.realLink, line1);
764
 
                string_init_c(&urls.displayLink, line2);
765
 
                string_init_c(&urls.pre_fixup.pre_displayLink, NULL);
766
 
                urls.realLink.refcount=-1;
767
 
                urls.displayLink.refcount=-1;
768
 
                int rc = phishingCheck(ctx->engine, &urls);
769
 
        }
770
 
        fclose(f);
771
 
        return 0;
772
 
#endif
773
 
        for(i=0;i<hrefs->count;i++) {
 
956
        *ctx->virname=NULL;
 
957
        for(i=0;i<hrefs->count;i++)
 
958
                if(hrefs->contents[i]) {
774
959
                        struct url_check urls;
775
960
                        enum phish_status rc;
 
961
                        urls.always_check_flags = DOMAINLIST_REQUIRED;/* required to work correctly */
776
962
                        urls.flags       = strncmp((char*)hrefs->tag[i],href_text,href_text_len)? (CL_PHISH_ALL_CHECKS&~CHECK_SSL): CL_PHISH_ALL_CHECKS;
777
963
                        urls.link_type   = 0;
778
964
                        if(!strncmp((char*)hrefs->tag[i],src_text,src_text_len)) {
779
965
                                if (!(urls.flags&CHECK_IMG_URL))
780
966
                                continue;
781
 
                                urls.link_type |= LINKTYPE_IMAGE;
 
967
                                urls.link_type |= LINKTYPE_IMAGE; 
782
968
                        }
783
 
                        urls.always_check_flags = 0;
 
969
                        if (ctx->options&CL_SCAN_PHISHING_DOMAINLIST)
 
970
                                urls.flags |= DOMAINLIST_REQUIRED;
784
971
                        if (ctx->options & CL_SCAN_PHISHING_BLOCKSSL) {
785
972
                                urls.always_check_flags |= CHECK_SSL;
786
973
                        }
788
975
                                urls.always_check_flags |= CHECK_CLOAKING;
789
976
                        }
790
977
                        string_init_c(&urls.realLink,(char*)hrefs->value[i]);
791
 
                        string_init_c(&urls.displayLink, (char*)hrefs->contents[i]);
792
 
                        string_init_c(&urls.pre_fixup.pre_displayLink, NULL);
 
978
/*                      if(!hrefs->contents[i]->isClosed) {
 
979
                                blobAddData(hrefs->contents[i],empty_string,1);
 
980
                                blobClose(hrefs->contents[i]);
 
981
                        }*/
 
982
                        string_init_c(&urls.displayLink,(char*)blobGetData(hrefs->contents[i]));
793
983
 
 
984
                        if (urls.displayLink.data[blobGetDataSize(hrefs->contents[i])-1]) {
 
985
                                cli_warnmsg("urls.displayLink.data[...]");
 
986
                                return CL_CLEAN;
 
987
                        }
 
988
/*                      massert(strlen(urls.displayLink.data) < blobGetDataSize(hrefs->contents[i]));*/
794
989
                        urls.realLink.refcount=-1;
795
990
                        urls.displayLink.refcount=-1;/*don't free these, caller will free*/
796
991
                        if(strcmp((char*)hrefs->tag[i],"href")) {
804
999
                        if(pchk->is_disabled)
805
1000
                                return CL_CLEAN;
806
1001
                        free_if_needed(&urls);
807
 
                        cli_dbgmsg("Phishcheck: Phishing scan result: %s\n",phishing_ret_toString(rc));
 
1002
                        cli_dbgmsg("Phishing scan result:%s\n",phishing_ret_toString(rc));
808
1003
                        switch(rc)/*TODO: support flags from ctx->options,*/
809
 
                        {
810
 
                                case CL_PHISH_CLEAN:
811
 
                                        continue;
812
 
                                case CL_PHISH_NUMERIC_IP:
813
 
                                        *ctx->virname="Heuristics.Phishing.Email.Cloaked.NumericIP";
814
 
                                        break;
815
 
                                case CL_PHISH_CLOAKED_NULL:
816
 
                                        *ctx->virname="Heuristics.Phishing.Email.Cloaked.Null";/*fakesite%01%00@fake.example.com*/
817
 
                                        break;
818
 
                                case CL_PHISH_SSL_SPOOF:
819
 
                                        *ctx->virname="Heuristics.Phishing.Email.SSL-Spoof";
820
 
                                        break;
821
 
                                case CL_PHISH_CLOAKED_UIU:
822
 
                                        *ctx->virname="Heuristics.Phishing.Email.Cloaked.Username";/*http://banksite@fake.example.com*/
823
 
                                        break;
824
 
                                case CL_PHISH_HASH0:
825
 
                                        *ctx->virname="Heuristics.Safebrowsing.Suspected-malware_safebrowsing.clamav.net";
826
 
                                        break;
827
 
                                case CL_PHISH_HASH1:
828
 
                                        *ctx->virname="Heuristics.Phishing.URL.Blacklisted";
829
 
                                        break;
830
 
                                case CL_PHISH_HASH2:
831
 
                                        *ctx->virname="Heuristics.Safebrowsing.Suspected-phishing_safebrowsing.clamav.net";
832
 
                                        break;
833
 
                                case CL_PHISH_NOMATCH:
834
 
                                default:
835
 
                                        *ctx->virname="Heuristics.Phishing.Email.SpoofedDomain";
836
 
                                        break;
837
 
                        }
838
 
                        return cli_found_possibly_unwanted(ctx);
839
 
        }
840
 
        return CL_CLEAN;
841
 
}
842
 
 
843
 
static char hex2int(const unsigned char* src)
 
1004
                                {
 
1005
                                        case CL_PHISH_CLEAN:
 
1006
                                        case CL_PHISH_CLEANUP_OK:
 
1007
                                        case CL_PHISH_HOST_OK:
 
1008
                                        case CL_PHISH_DOMAIN_OK:
 
1009
                                        case CL_PHISH_REDIR_OK:
 
1010
                                        case CL_PHISH_HOST_REDIR_OK:
 
1011
                                        case CL_PHISH_DOMAIN_REDIR_OK:
 
1012
                                        case CL_PHISH_HOST_REVERSE_OK:
 
1013
                                        case CL_PHISH_DOMAIN_REVERSE_OK:
 
1014
                                        case CL_PHISH_WHITELISTED:
 
1015
                                        case CL_PHISH_HOST_WHITELISTED:
 
1016
                                        case CL_PHISH_MAILTO_OK:
 
1017
                                        case CL_PHISH_TEXTURL:
 
1018
                                        case CL_PHISH_HOST_NOT_LISTED:
 
1019
                                        case CL_PHISH_CLEAN_CID:
 
1020
                                                continue;
 
1021
/*                                              break;*/
 
1022
                                        case CL_PHISH_HEX_URL:
 
1023
                                                *ctx->virname="Phishing.Email.HexURL";
 
1024
                                                return CL_VIRUS;
 
1025
/*                                              break;*/
 
1026
                                        case CL_PHISH_NUMERIC_IP:
 
1027
                                                *ctx->virname="Phishing.Email.Cloaked.NumericIP";
 
1028
                                                return CL_VIRUS;
 
1029
                                        case CL_PHISH_CLOAKED_NULL:
 
1030
                                                *ctx->virname="Phishing.Email.Cloaked.Null";/*http://www.real.com%01%00@www.evil.com*/
 
1031
                                                return CL_VIRUS;
 
1032
                                        case CL_PHISH_SSL_SPOOF:
 
1033
                                                *ctx->virname="Phishing.Email.SSL-Spoof";
 
1034
                                                return CL_VIRUS;
 
1035
                                        case CL_PHISH_CLOAKED_UIU:
 
1036
                                                *ctx->virname="Phishing.Email.Cloaked.Username";/*http://www.ebay.com@www.evil.com*/
 
1037
                                                return CL_VIRUS;
 
1038
                                        case CL_PHISH_NOMATCH:
 
1039
                                        default:
 
1040
                                                *ctx->virname="Phishing.Email";
 
1041
                                                return CL_VIRUS;
 
1042
                                }
 
1043
                }
 
1044
                else
 
1045
                        if(strcmp((char*)hrefs->tag[i],"href"))
 
1046
                                        cli_dbgmsg("PH:href with no contents?\n");
 
1047
        return CL_CLEAN;/*texturlfound?CL_VIRUS:0;*/
 
1048
}
 
1049
 
 
1050
static char* str_compose(const char* a,const char* b,const char* c)
 
1051
{
 
1052
        const size_t a_len = strlen(a);
 
1053
        const size_t b_len = strlen(b);
 
1054
        const size_t c_len = strlen(c);
 
1055
        const size_t r_len = a_len+b_len+c_len+1;
 
1056
        char* concated = cli_malloc(r_len);
 
1057
        if(!concated)
 
1058
                return NULL;
 
1059
        strncpy(concated,a,a_len);
 
1060
        strncpy(concated+a_len,b,b_len);
 
1061
        strncpy(concated+a_len+b_len,c,c_len);
 
1062
        concated[r_len-1]='\0';
 
1063
        return concated;
 
1064
}
 
1065
 
 
1066
static inline char hex2int(const unsigned char* src)
844
1067
{
845
1068
        return (src[0] == '0' && src[1] == '0') ? 
846
1069
                0x1 :/* don't convert %00 to \0, use 0x1
851
1074
static void free_regex(regex_t* p)
852
1075
{
853
1076
        if(p) {
854
 
                cli_regfree(p);
 
1077
                regfree(p);
855
1078
        }
856
1079
}
857
1080
 
859
1082
{
860
1083
        struct phishcheck* pchk;
861
1084
        if(!engine->phishcheck) {
862
 
                pchk = engine->phishcheck = mpool_malloc(engine->mempool, sizeof(struct phishcheck));
 
1085
                pchk = engine->phishcheck = cli_malloc(sizeof(struct phishcheck));
863
1086
                if(!pchk)
864
1087
                        return CL_EMEM;
865
 
                pchk->is_disabled=1;
 
1088
                pchk->is_disabled = 1;
866
1089
        }
867
1090
        else {
868
1091
                pchk = engine->phishcheck;
876
1099
 
877
1100
        cli_dbgmsg("Initializing phishcheck module\n");
878
1101
 
 
1102
        if(build_regex(&pchk->preg_hexurl,cloaked_host_regex,1)) {
 
1103
                free(pchk);
 
1104
                engine->phishcheck = NULL;
 
1105
                return CL_EFORMAT;
 
1106
        }
 
1107
 
 
1108
        if(build_regex(&pchk->preg_cctld,cctld_regex,1)) {
 
1109
                free(pchk);
 
1110
                engine->phishcheck = NULL;
 
1111
                return CL_EFORMAT;
 
1112
        }
 
1113
        if(build_regex(&pchk->preg_tld,tld_regex,1)) {
 
1114
                free_regex(&pchk->preg_cctld);
 
1115
                free(pchk);
 
1116
                engine->phishcheck = NULL;
 
1117
                return CL_EFORMAT;      
 
1118
        }
 
1119
        pchk->url_regex = str_compose("^ *("URI_fragmentaddress1,URI_fragmentaddress2,URI_fragmentaddress3"|"URI_CHECK_PROTOCOLS") *$");
 
1120
        if(build_regex(&pchk->preg,pchk->url_regex,1)) {
 
1121
                free_regex(&pchk->preg_cctld);
 
1122
                free_regex(&pchk->preg_tld);
 
1123
                free(pchk->url_regex);
 
1124
                free(pchk);
 
1125
                engine->phishcheck = NULL;
 
1126
                return CL_EFORMAT;
 
1127
        }
879
1128
        if(build_regex(&pchk->preg_numeric,numeric_url_regex,1)) {
880
 
                mpool_free(engine->mempool, pchk);
 
1129
                free_regex(&pchk->preg_cctld);
 
1130
                free_regex(&pchk->preg_tld);
 
1131
                free_regex(&pchk->preg);
 
1132
                free(pchk->url_regex);
 
1133
                free(pchk);
881
1134
                engine->phishcheck = NULL;
882
1135
                return CL_EFORMAT;
883
1136
        }
891
1144
        struct phishcheck* pchk = engine->phishcheck;
892
1145
        cli_dbgmsg("Cleaning up phishcheck\n");
893
1146
        if(pchk && !pchk->is_disabled) {
 
1147
                free_regex(&pchk->preg);
 
1148
                free_regex(&pchk->preg_hexurl);
 
1149
                free_regex(&pchk->preg_cctld);
 
1150
                free_regex(&pchk->preg_tld);
894
1151
                free_regex(&pchk->preg_numeric);
 
1152
                if(pchk->url_regex) {
 
1153
                        free(pchk->url_regex);
 
1154
                        pchk->url_regex = NULL;
 
1155
                }
 
1156
                pchk->is_disabled = 1;
895
1157
        }
896
1158
        whitelist_done(engine);
897
1159
        domainlist_done(engine);
898
1160
        if(pchk) {
899
1161
                cli_dbgmsg("Freeing phishcheck struct\n");
900
 
                mpool_free(engine->mempool, pchk);
901
 
        }
 
1162
                free(pchk);
 
1163
                engine->phishcheck = NULL;
 
1164
        }               
902
1165
        cli_dbgmsg("Phishcheck cleaned up\n");
903
1166
}
904
1167
 
905
 
 
906
 
/*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*/
907
 
static const uint8_t URI_alpha[256] = {
908
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
909
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
910
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
911
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
912
 
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
913
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
914
 
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
915
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
916
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
917
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
918
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
919
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
920
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
921
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
922
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
923
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
924
 
};
925
 
 
926
 
/*!"$%&'()*,-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz*/
927
 
static const uint8_t URI_xalpha_nodot[256] = {
928
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
929
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
930
 
        0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0,
931
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
932
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
933
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
934
 
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
935
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
936
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
937
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
938
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
939
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
940
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
941
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
942
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
943
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
944
 
};
945
 
 
946
 
/*!"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz*/
947
 
static const uint8_t URI_xpalpha_nodot[256] = {
948
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
949
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
950
 
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
951
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
952
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
953
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
954
 
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
955
 
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
956
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
957
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
958
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
959
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
960
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
961
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
962
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
963
 
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
964
 
};
965
 
 
966
 
static inline int validate_uri_xalphas_nodot(const char *start, const char *end)
967
 
{
968
 
        const unsigned char *p;
969
 
        for(p=(const unsigned char*)start;p < (const unsigned char*)end; p++) {
970
 
                if(!URI_xalpha_nodot[*p])
971
 
                        return 0;
972
 
        }
973
 
        return 1;
974
 
}
975
 
 
976
 
static inline int validate_uri_xpalphas_nodot(const char *start, const char *end)
977
 
{
978
 
        const unsigned char *p;
979
 
        for(p=(const unsigned char*)start;p < (const unsigned char*)end; p++) {
980
 
                if(!URI_xpalpha_nodot[*p])
981
 
                        return 0;
982
 
        }
983
 
        /* must have at least on char */
984
 
        return p > (const unsigned char*)start;
985
 
}
986
 
 
987
 
 
988
 
static inline int validate_uri_ialpha(const char *start, const char *end)
989
 
{
990
 
        const unsigned char *p = (const unsigned char*) start;
991
 
        if(start >= end || !URI_alpha[*p])
992
 
                return 0;
993
 
        return validate_uri_xalphas_nodot(start + 1, end);
994
 
}
995
 
 
996
1168
/*
997
1169
 * Only those URLs are identified as URLs for which phishing detection can be performed.
998
 
 */
999
 
static int isURL(char* URL, int accept_anyproto)
1000
 
{
1001
 
        char *last_tld_end = NULL, *q;
1002
 
        const char *start = NULL, *p, *end;
1003
 
        int has_proto = 0;
1004
 
        if(!URL)
1005
 
                return 0;
1006
 
 
1007
 
        while (*URL == ' ') URL++;
1008
 
        switch (URL[0]) {
1009
 
                case 'h':
1010
 
                        if (strncmp(URL, https, https_len) == 0)
1011
 
                                start = URL + https_len - 1;
1012
 
                        else if (strncmp(URL, http, http_len) == 0)
1013
 
                                start = URL + http_len - 1;
1014
 
                        break;
1015
 
                case 'f':
1016
 
                       if (strncmp(URL, ftp, ftp_len) == 0)
1017
 
                               start = URL + ftp_len - 1;
1018
 
                       break;
1019
 
                case 'm':
1020
 
                       if (strncmp(URL, mailto_proto, mailto_proto_len) == 0)
1021
 
                               start = URL + mailto_proto_len - 1;
1022
 
                       break;
1023
 
        }
1024
 
        if(start && start[1] == '/' && start[2] == '/') {
1025
 
                /* has a valid protocol, it is a URL */
1026
 
                return 1;
1027
 
        }
1028
 
        start = accept_anyproto ?  strchr(URL, ':') : start;
1029
 
        if(start) {
1030
 
                /* validate URI scheme */
1031
 
                if(validate_uri_ialpha(URL, start)) {
1032
 
                        /* skip :// */
1033
 
                        if (start[1] == '/') {
1034
 
                            start += 2;
1035
 
                            if (*start == '/')
1036
 
                                start++;
1037
 
                        } else
1038
 
                            start++;
1039
 
                        has_proto = 1;
1040
 
                }
1041
 
                else
1042
 
                        start = URL; /* scheme invalid */
1043
 
        } else
1044
 
                start = URL;
1045
 
        p = start;
1046
 
        end = strchr(p, '/');
1047
 
        if (!end)
1048
 
                end = p + strlen(p);
1049
 
 
1050
 
        if (!has_proto && (q = memchr(p, '@', end-p))) {
1051
 
            /* don't phishcheck if displayed URL is email, but do phishcheck if
1052
 
             * foo.TLD@host is used */
1053
 
            const char *q2 = q-1;
1054
 
            while (q2 > p && *q2 != '.') q2--;
1055
 
            if (q2 == p || !in_tld_set(q2+1, q-q2-1))
1056
 
                return 0;
1057
 
        }
1058
 
 
1059
 
        do {
1060
 
                q = strchr(p, '.');
1061
 
                if (q > end)
1062
 
                        break;
1063
 
                if(q) {
1064
 
                        if(!validate_uri_xpalphas_nodot(p, q))
1065
 
                                return 0;
1066
 
                        if (accept_anyproto && in_tld_set(p, q-p))
1067
 
                            last_tld_end = q;
1068
 
                        p = q+1;
1069
 
                }
1070
 
        } while(q);
1071
 
        if (p == start) /* must have at least one dot in the URL */
1072
 
                return 0;
1073
 
        if (end < p)
1074
 
                end = p;
1075
 
        while (*end == ' ' && end > p) --end;
1076
 
 
1077
 
        if (in_tld_set(p, end - p))
1078
 
            return 1;
1079
 
        if (!accept_anyproto)
1080
 
            return 0;
1081
 
        if (last_tld_end) {
1082
 
            *last_tld_end = '\0';
1083
 
            return 1;
1084
 
        }
1085
 
        return 0;
1086
 
}
1087
 
 
1088
 
/*
1089
 
 * Check if this is a real URL, which basically means to check if it has a known URL scheme (http,https,ftp).
1090
 
 * This prevents false positives with outbind:// and blocked:: links.
1091
 
 */
1092
 
#if 0
1093
 
static int isRealURL(const struct phishcheck* pchk,const char* URL)
1094
 
{
1095
 
        return URL ? !cli_regexec(&pchk->preg_realurl,URL,0,NULL,0) : 0;
1096
 
}
1097
 
#endif
 
1170
 * This means that no attempt is made to properly recognize 'cid:' URLs
 
1171
 */
 
1172
static int isURL(const struct phishcheck* pchk,const char* URL)
 
1173
{
 
1174
        return URL ? !regexec(&pchk->preg,URL,0,NULL,0) : 0;
 
1175
}
1098
1176
 
1099
1177
static int isNumericURL(const struct phishcheck* pchk,const char* URL)
1100
1178
{
1101
 
        return URL ? !cli_regexec(&pchk->preg_numeric,URL,0,NULL,0) : 0;
 
1179
        return URL ? !regexec(&pchk->preg_numeric,URL,0,NULL,0) : 0;
1102
1180
}
1103
1181
 
1104
1182
/* Cleans up @urls
1107
1185
static enum phish_status cleanupURLs(struct url_check* urls)
1108
1186
{
1109
1187
        if(urls->flags&CLEANUP_URL) {
1110
 
                cleanupURL(&urls->realLink,NULL,1);
1111
 
                cleanupURL(&urls->displayLink,&urls->pre_fixup.pre_displayLink,0);
 
1188
                cleanupURL(&urls->realLink,1);
 
1189
                cleanupURL(&urls->displayLink,0);
1112
1190
                if(!urls->displayLink.data || !urls->realLink.data)
1113
1191
                        return CL_PHISH_NODECISION;
1114
1192
                if(!strcmp(urls->realLink.data,urls->displayLink.data))
1115
 
                        return CL_PHISH_CLEAN;
 
1193
                        return CL_PHISH_CLEANUP_OK;
1116
1194
        }
1117
1195
        return CL_PHISH_NODECISION;
1118
1196
}
1119
1197
 
1120
 
static int url_get_host(struct url_check* url,struct url_check* host_url,int isReal,int* phishy)
 
1198
static int url_get_host(const struct phishcheck* pchk, struct url_check* url,struct url_check* host_url,int isReal,int* phishy)
1121
1199
{
1122
 
        const char *start, *end;
1123
1200
        struct string* host = isReal ? &host_url->realLink : &host_url->displayLink;
1124
 
        const char* URL = isReal ? url->realLink.data : url->displayLink.data;
1125
 
        int rc;
1126
 
        if ((rc = get_host(URL, isReal, phishy, &start, &end))) {
1127
 
                return rc;
1128
 
        }
1129
 
        if(!start || !end) {
1130
 
                string_assign_null(host);
1131
 
        }
1132
 
        else if(( rc = string_assign_concatenated(host, ".", start, end) )) {
1133
 
                return rc;
1134
 
        }
1135
 
 
1136
 
        cli_dbgmsg("Phishcheck:host:%s\n", host->data);
1137
 
 
1138
 
        if(!host->data || (isReal && (host->data[0]=='\0' || strstr(host->data,".."))) || *phishy&REAL_IS_MAILTO || strchr(host->data,' ')) {
1139
 
                /* no host,
1140
 
                 * link without domain, such as: href="/isapi.dll?...
1141
 
                 * mailto:
1142
 
                 * spaces in hostname
1143
 
                 * double dots
1144
 
                 */
1145
 
                cli_dbgmsg("Phishcheck:skipping invalid host\n");
1146
 
                return CL_PHISH_CLEAN;
1147
 
        }
 
1201
        get_host(pchk, host, isReal ? url->realLink.data : url->displayLink.data, isReal, phishy);
 
1202
        if(!host->data)
 
1203
                return CL_PHISH_CLEANUP_OK;
 
1204
        if(*phishy&REAL_IS_MAILTO)
 
1205
                return CL_PHISH_MAILTO_OK;
 
1206
        if(strchr(host->data,' ')) {
 
1207
                string_free(host);
 
1208
                return CL_PHISH_TEXTURL;
 
1209
        }
 
1210
        if(url->flags&CHECK_CLOAKING && !regexec(&pchk->preg_hexurl,host->data,0,NULL,0)) {
 
1211
                /* use a regex here, so that we don't accidentally block 0xacab.net style hosts */
 
1212
                string_free(host);
 
1213
                return CL_PHISH_HEX_URL;
 
1214
        }
 
1215
        if(isReal && host->data[0]=='\0')
 
1216
                return CL_PHISH_CLEAN;/* link without domain, such as: href="/isapi.dll?... */
1148
1217
        if(isNumeric(host->data)) {
1149
1218
                *phishy |= PHISHY_NUMERIC_IP;
1150
 
        }
1151
 
        if(!isReal) {
1152
 
                url->pre_fixup.host_start = start - URL;
1153
 
                url->pre_fixup.host_end = end - URL;
1154
 
                url->pre_fixup.pre_displayLink.data[url->pre_fixup.host_end] = '\0';
 
1219
/*              if(url->flags&DO_REVERSE_LOOKUP)
 
1220
                        reverse_lookup(host_url,isReal);*/
1155
1221
        }
1156
1222
        return CL_PHISH_NODECISION;
1157
1223
}
1158
1224
 
1159
 
static void url_get_domain(struct url_check* url,struct url_check* domains)
 
1225
static void url_get_domain(const struct phishcheck* pchk, struct url_check* url,struct url_check* domains)
1160
1226
{
1161
 
        get_domain(&domains->realLink, &url->realLink);
1162
 
        get_domain(&domains->displayLink, &url->displayLink);
 
1227
        get_domain(pchk, &domains->realLink, &url->realLink);
 
1228
        get_domain(pchk, &domains->displayLink, &url->displayLink);
1163
1229
        domains->flags = url->flags;
1164
1230
}
1165
1231
 
1173
1239
                return fallback;
1174
1240
}
1175
1241
 
 
1242
static int isEncoded(const char* url)
 
1243
{
 
1244
        const char* start=url;
 
1245
        size_t cnt=0;
 
1246
        do{
 
1247
                cnt++;
 
1248
                /*last=start;*/
 
1249
                start=strstr(start,"&#");
 
1250
                if(start)
 
1251
                        start=strstr(start,";");
 
1252
        } while(start);
 
1253
        return (cnt-1 >strlen(url)*7/10);/*more than 70% made up of &#;*/
 
1254
}
 
1255
 
1176
1256
static int whitelist_check(const struct cl_engine* engine,struct url_check* urls,int hostOnly)
1177
1257
{
1178
1258
        return whitelist_match(engine,urls->realLink.data,urls->displayLink.data,hostOnly);
1179
1259
}
1180
1260
 
1181
 
static int hash_match(const struct regex_matcher *rlist, const char *host, size_t hlen, const char *path, size_t plen, int *prefix_matched)
1182
 
{
1183
 
        const char *virname;
1184
 
#if 0
1185
 
        char s[1024];
1186
 
        strncpy(s, host, hlen);
1187
 
        strncpy(s+hlen, path, plen);
1188
 
        s[hlen+plen] = '\0';
1189
 
        cli_dbgmsg("hash lookup for: %s\n",s);
1190
 
#endif
1191
 
        if(rlist->sha256_hashes.bm_patterns) {
1192
 
            const char hexchars[] = "0123456789ABCDEF";
1193
 
            unsigned char h[65];
1194
 
            unsigned char sha256_dig[32];
1195
 
            unsigned i;
1196
 
            SHA256_CTX sha256;
1197
 
 
1198
 
            sha256_init(&sha256);
1199
 
            sha256_update(&sha256, host, hlen);
1200
 
            sha256_update(&sha256, path, plen);
1201
 
            sha256_final(&sha256, sha256_dig);
1202
 
            for(i=0;i<32;i++) {
1203
 
                h[2*i] = hexchars[sha256_dig[i]>>4];
1204
 
                h[2*i+1] = hexchars[sha256_dig[i]&0xf];
1205
 
            }
1206
 
            h[64]='\0';
1207
 
            cli_dbgmsg("Looking up hash %s for %s(%u)%s(%u)\n", h, host, (unsigned)hlen, path, (unsigned)plen);
1208
 
#if 0
1209
 
            if (prefix_matched) {
1210
 
                if (cli_bm_scanbuff(sha256_dig, 4, &virname, NULL, &rlist->hostkey_prefix,0,NULL,NULL) == CL_VIRUS) {
1211
 
                    cli_dbgmsg("prefix matched\n");
1212
 
                    *prefix_matched = 1;
1213
 
                } else
1214
 
                    return CL_SUCCESS;
1215
 
            }
1216
 
#endif
1217
 
            if (cli_bm_scanbuff(sha256_dig, 32, &virname, NULL, &rlist->sha256_hashes,0,NULL,NULL) == CL_VIRUS) {
1218
 
                cli_dbgmsg("This hash matched: %s\n", h);
1219
 
                switch(*virname) {
1220
 
                    case 'W':
1221
 
                        cli_dbgmsg("Hash is whitelisted, skipping\n");
1222
 
                        break;
1223
 
                    case '1':
1224
 
                        return CL_PHISH_HASH1;
1225
 
                    case '2':
1226
 
                        return CL_PHISH_HASH2;
1227
 
                    default:
1228
 
                        return CL_PHISH_HASH0;
1229
 
                }
1230
 
            }
1231
 
        }
1232
 
        return CL_SUCCESS;
1233
 
}
1234
 
 
1235
 
#define URL_MAX_LEN 1024
1236
 
#define COMPONENTS 4
1237
 
int cli_url_canon(const char *inurl, size_t len, char *urlbuff, size_t dest_len, char **host, size_t *hostlen, const char **path, size_t *pathlen)
1238
 
{
1239
 
        char *url, *p, *last;
1240
 
        char *host_begin, *path_begin;
1241
 
        const char *urlend = urlbuff + len;
1242
 
        size_t host_len, path_len;
1243
 
 
1244
 
        dest_len -= 3;
1245
 
        strncpy(urlbuff, inurl, dest_len);
1246
 
        urlbuff[dest_len] = urlbuff[dest_len+1] = urlbuff[dest_len+2] = '\0';
1247
 
        url = urlbuff;
1248
 
 
1249
 
        /* canonicalize only real URLs, with a protocol */
1250
 
        host_begin = strchr(url, ':');
1251
 
        if(!host_begin)
1252
 
                return CL_PHISH_CLEAN;
1253
 
        ++host_begin;
1254
 
 
1255
 
        /* ignore username in URL */
1256
 
        p = strchr(host_begin, '@');
1257
 
        if (p)
1258
 
            host_begin = p+1;
1259
 
        url = host_begin;
1260
 
        /* repeatedly % unescape characters */
1261
 
        str_hex_to_char(&url, &urlend);
1262
 
        host_begin = url;
1263
 
        len = urlend - url;
1264
 
        /* skip to beginning of hostname */
1265
 
        while((host_begin < urlend) && *host_begin == '/') ++host_begin;
1266
 
        while(*host_begin == '.' && host_begin < urlend) ++host_begin;
1267
 
 
1268
 
        last = strchr(host_begin, '/');
1269
 
        p = host_begin;
1270
 
        while (p < urlend) {
1271
 
            if (p+2 < urlend && *p == '/' && p[1] == '.' ) {
1272
 
                if (p[2] == '/') {
1273
 
                    /* remove /./ */
1274
 
                    if (p + 3 < urlend)
1275
 
                        memmove(p+1, p+3, urlend - p - 3);
1276
 
                    urlend -= 2;
1277
 
                }
1278
 
                else if (p[2] == '.' && (p[3] == '/' || p[3] == '\0') && last) {
1279
 
                    /* remove /component/../ */
1280
 
                    if (p+4 < urlend)
1281
 
                        memmove(last+1, p+4, urlend - p - 4);
1282
 
                    urlend -= 3 + (p - last);
1283
 
                }
1284
 
            }
1285
 
            if (*p == '/')
1286
 
                last = p;
1287
 
            p++;
1288
 
        }
1289
 
        p = &url[urlend - url];
1290
 
        *p = '\0';
1291
 
 
1292
 
        p = host_begin;
1293
 
        while (p < urlend && p+2 < url + dest_len && urlend < urlbuff+dest_len) {
1294
 
            unsigned char c = *p;
1295
 
            if (c <= 32 || c >= 127 || c == '%' || c == '#') {
1296
 
                /* convert non-ascii characters back to % escaped */
1297
 
                const char hexchars[] = "0123456789ABCDEF";
1298
 
                memmove(p+3, p+1, urlend - p - 1);
1299
 
                *p++ = '%';
1300
 
                *p++ = hexchars[c>>4];
1301
 
                *p = hexchars[c&0xf];
1302
 
                urlend += 2;
1303
 
            }
1304
 
            p++;
1305
 
        }
1306
 
        *p = '\0';
1307
 
        urlend = p;
1308
 
        len = urlend - url;
1309
 
        /* determine end of hostname */
1310
 
        host_len = strcspn(host_begin, ":/?");
1311
 
        path_begin = host_begin + host_len;
1312
 
        if(host_len <= len) {
1313
 
                /* url without path, use a single / */
1314
 
                memmove(path_begin + 2, path_begin + 1, len - host_len);
1315
 
                *path_begin++ = '/';
1316
 
                *path_begin++ = '\0';
1317
 
        } else path_begin = url+len;
1318
 
        if(url + len >= path_begin) {
1319
 
                path_len = url + len - path_begin + 1;
1320
 
                p = strchr(path_begin, '#');
1321
 
                if (p) {
1322
 
                    /* ignore anchor */
1323
 
                    *p = '\0';
1324
 
                    path_len = p - path_begin;
1325
 
                }
1326
 
                *path = path_begin;
1327
 
        } else {
1328
 
                path_len = 0;
1329
 
                *path = "";
1330
 
        }
1331
 
        /* lowercase entire URL */
1332
 
        str_make_lowercase(host_begin, host_len);
1333
 
        *host = host_begin;
1334
 
        *hostlen = host_len;
1335
 
        *pathlen = path_len;
1336
 
        return CL_PHISH_NODECISION;
1337
 
}
1338
 
 
1339
 
static int url_hash_match(const struct regex_matcher *rlist, const char *inurl, size_t len)
1340
 
{
1341
 
        size_t j, k, ji, ki;
1342
 
        char *host_begin;
1343
 
        const char *path_begin;
1344
 
        const char *component;
1345
 
        size_t path_len;
1346
 
        size_t host_len;
1347
 
        char *p;
1348
 
        int rc, prefix_matched=0;
1349
 
        const char *lp[COMPONENTS+1];
1350
 
        size_t pp[COMPONENTS+2];
1351
 
        char urlbuff[URL_MAX_LEN+3];/* htmlnorm truncates at 1024 bytes + terminating null + slash + host end null */
1352
 
        unsigned count;
1353
 
 
1354
 
        if(!rlist || !rlist->sha256_hashes.bm_patterns) {
1355
 
                /* no hashes loaded -> don't waste time canonicalizing and
1356
 
                 * looking up */
1357
 
                return CL_SUCCESS;
1358
 
        }
1359
 
        if(!inurl)
1360
 
                return CL_EMEM;
1361
 
 
1362
 
        rc = cli_url_canon(inurl, len, urlbuff, sizeof(urlbuff), &host_begin, &host_len, &path_begin, &path_len);
1363
 
        if (rc == CL_PHISH_CLEAN)
1364
 
            return rc;
1365
 
 
1366
 
        /* get last 5 components of hostname */
1367
 
        j=COMPONENTS;
1368
 
        component = strrchr(host_begin, '.');
1369
 
        while(component && j > 0) {
1370
 
                do {
1371
 
                        --component;
1372
 
                } while(*component != '.' && component > host_begin);
1373
 
                if(*component != '.')
1374
 
                        component = NULL;
1375
 
                if(component)
1376
 
                        lp[j--] = component + 1;
1377
 
        }
1378
 
        lp[j] = host_begin;
1379
 
 
1380
 
        /* get first 5 components of path */
1381
 
        pp[0] = path_len;
1382
 
        if(path_len) {
1383
 
                pp[1] = strcspn(path_begin, "?");
1384
 
                if(pp[1] != pp[0]) k = 2;
1385
 
                else k = 1;
1386
 
                pp[k++] = 0;
1387
 
                while(k < COMPONENTS+2) {
1388
 
                        p = strchr(path_begin + pp[k-1] + 1, '/');
1389
 
                        if(p && p > path_begin) {
1390
 
                                pp[k++] = p - path_begin;
1391
 
                        } else
1392
 
                                break;
1393
 
                }
1394
 
        } else
1395
 
                k = 1;
1396
 
        count = 0;
1397
 
        for(ki=k;ki > 0;) {
1398
 
            --ki;
1399
 
            for(ji=COMPONENTS+1;ji > j;) {
1400
 
                /* lookup last 2 and 3 components of host, as hostkey prefix,
1401
 
                 * if not matched, shortcircuit lookups */
1402
 
                int need_prefixmatch = (count<2 && !prefix_matched) &&
1403
 
                                       rlist->hostkey_prefix.bm_patterns;
1404
 
                --ji;
1405
 
                assert(pp[ki] <= path_len);
1406
 
                /* lookup prefix/suffix hashes of URL */
1407
 
                rc = hash_match(rlist, lp[ji], host_begin + host_len - lp[ji] + 1, path_begin, pp[ki], 
1408
 
                                need_prefixmatch ? &prefix_matched : NULL);
1409
 
                if(rc) {
1410
 
                    return rc;
1411
 
                }
1412
 
                count++;
1413
 
#if 0
1414
 
                if (count == 2 && !prefix_matched && rlist->hostkey_prefix.bm_patterns) {
1415
 
                    /* if hostkey is not matched, don't bother calculating
1416
 
                     * hashes for other parts of the URL, they are not in the DB
1417
 
                     */
1418
 
                    cli_dbgmsg("hostkey prefix not matched, short-circuiting lookups\n");
1419
 
                    return CL_SUCCESS;
1420
 
                }
1421
 
#endif
1422
 
            }
1423
 
        }
1424
 
        return CL_SUCCESS;
1425
 
}
1426
1261
 
1427
1262
/* urls can't contain null pointer, caller must ensure this */
1428
1263
static enum phish_status phishingCheck(const struct cl_engine* engine,struct url_check* urls)
1429
1264
{
1430
1265
        struct url_check host_url;
1431
 
        int rc = CL_PHISH_NODECISION;
 
1266
        enum phish_status rc=CL_PHISH_NODECISION;
1432
1267
        int phishy=0;
1433
1268
        const struct phishcheck* pchk = (const struct phishcheck*) engine->phishcheck;
1434
1269
 
1435
1270
        if(!urls->realLink.data)
1436
1271
                return CL_PHISH_CLEAN;
1437
1272
 
1438
 
        cli_dbgmsg("Phishcheck:Checking url %s->%s\n", urls->realLink.data,
 
1273
        cli_dbgmsg("PH:Checking url %s->%s\n", urls->realLink.data,
1439
1274
                urls->displayLink.data);
1440
1275
 
1441
1276
        if(!strcmp(urls->realLink.data,urls->displayLink.data))
1442
1277
                return CL_PHISH_CLEAN;/* displayed and real URL are identical -> clean */
1443
1278
 
1444
 
        if(!isURL(urls->realLink.data, 0)) {
1445
 
                cli_dbgmsg("Real 'url' is not url:%s\n",urls->realLink.data);
1446
 
                return CL_PHISH_CLEAN;
1447
 
        }
1448
 
 
1449
 
        if(( rc = url_hash_match(engine->domainlist_matcher, urls->realLink.data, strlen(urls->realLink.data)) )) {
1450
 
            if (rc == CL_PHISH_CLEAN) {
1451
 
                cli_dbgmsg("not analyzing, not a real url: %s\n", urls->realLink.data);
1452
 
                return CL_PHISH_CLEAN;
1453
 
            } else {
1454
 
                cli_dbgmsg("Hash matched for: %s\n", urls->realLink.data);
1455
 
                return rc;
1456
 
            }
1457
 
        }
1458
 
 
1459
 
        if (urls->displayLink.data[0] == '\0') {
1460
 
            return CL_PHISH_CLEAN;
1461
 
        }
1462
 
 
1463
1279
        if((rc = cleanupURLs(urls))) {
1464
 
                /* it can only return an error, or say its clean;
1465
 
                 * it is not allowed to decide it is phishing */
1466
 
                return rc < 0 ? rc : CL_PHISH_CLEAN;
1467
 
        }
1468
 
 
1469
 
        cli_dbgmsg("Phishcheck:URL after cleanup: %s->%s\n", urls->realLink.data,
1470
 
                urls->displayLink.data);
1471
 
 
1472
 
        if((!isURL(urls->displayLink.data, 1) ) &&
1473
 
                        ( (phishy&PHISHY_NUMERIC_IP && !isNumericURL(pchk, urls->displayLink.data)) ||
1474
 
                          !(phishy&PHISHY_NUMERIC_IP))) {
1475
 
                cli_dbgmsg("Displayed 'url' is not url:%s\n",urls->displayLink.data);
1476
 
                return CL_PHISH_CLEAN;
1477
 
        }
1478
 
 
1479
 
        if(whitelist_check(engine, urls, 0))
1480
 
                return CL_PHISH_CLEAN;/* if url is whitelisted don't perform further checks */
1481
 
 
 
1280
                if(isPhishing(rc))/* not allowed to decide this is phishing */
 
1281
                        return CL_PHISH_CLEAN;
 
1282
                return rc;/* URLs identical after cleanup */
 
1283
        }
 
1284
 
 
1285
        if(whitelist_check(engine,urls,0))
 
1286
                return CL_PHISH_WHITELISTED;/* if url is whitelist don't perform further checks */
 
1287
 
 
1288
        if(urls->flags&DOMAINLIST_REQUIRED && domainlist_match(engine,urls->realLink.data,urls->displayLink.data,0,&urls->flags))
 
1289
                phishy |= DOMAIN_LISTED;
 
1290
        else {
 
1291
                /* although entire url is not listed, the host might be,
 
1292
                 * so defer phishing decisions till we know if host is listed*/
 
1293
        }
 
1294
 
 
1295
        
1482
1296
        url_check_init(&host_url);
1483
1297
 
1484
 
        if((rc = url_get_host(urls, &host_url, DOMAIN_DISPLAY, &phishy))) {
 
1298
        if((rc = url_get_host(pchk, urls,&host_url,DOMAIN_DISPLAY,&phishy))) {
1485
1299
                free_if_needed(&host_url);
1486
 
                return rc < 0 ? rc : CL_PHISH_CLEAN;
1487
 
        }
1488
 
 
1489
 
        if (domainlist_match(engine, host_url.displayLink.data,host_url.realLink.data,&urls->pre_fixup,1)) {
1490
 
                phishy |= DOMAIN_LISTED;
1491
 
        } else {
 
1300
                if(isPhishing(rc))
 
1301
                        return CL_PHISH_CLEAN;
 
1302
                return rc;
 
1303
        }
 
1304
 
 
1305
 
 
1306
        if(urls->flags&DOMAINLIST_REQUIRED) {
 
1307
                if(!(phishy&DOMAIN_LISTED)) {
 
1308
                        if(domainlist_match(engine,host_url.displayLink.data,host_url.realLink.data,1,&urls->flags))
 
1309
                                phishy |= DOMAIN_LISTED;
 
1310
                        else {
 
1311
                        }
 
1312
                }
 
1313
        }
 
1314
 
 
1315
        /* link type filtering must occur after last domainlist_match */
 
1316
        if(urls->link_type & LINKTYPE_IMAGE && !(urls->flags&CHECK_IMG_URL))
 
1317
                return CL_PHISH_HOST_NOT_LISTED;/* its listed, but this link type is filtered */
 
1318
 
 
1319
        if(urls->flags & DOMAINLIST_REQUIRED && !(phishy & DOMAIN_LISTED) ) {
1492
1320
                urls->flags &= urls->always_check_flags;
1493
 
                /* don't return, we may need to check for ssl/cloaking */
1494
 
        }
1495
 
 
1496
 
        /* link type filtering must occur after last domainlist_match */
1497
 
        if(urls->link_type & LINKTYPE_IMAGE && !(urls->flags&CHECK_IMG_URL)) {
1498
 
                free_if_needed(&host_url);
1499
 
                return CL_PHISH_CLEAN;/* its listed, but this link type is filtered */
1500
 
        }
 
1321
                if(!urls->flags) {
 
1322
                                free_if_needed(&host_url);
 
1323
                                return CL_PHISH_HOST_NOT_LISTED;
 
1324
                        }
 
1325
                }
1501
1326
 
1502
1327
        if(urls->flags&CHECK_CLOAKING) {
1503
1328
                /*Checks if URL is cloaked.
1504
 
                Should we check if it contains another http://, https://?
 
1329
                Should we check if it containts another http://, https://?
1505
1330
                No because we might get false positives from redirect services.*/
1506
1331
                if(strchr(urls->realLink.data,0x1)) {
1507
1332
                        free_if_needed(&host_url);
1508
1333
                        return CL_PHISH_CLOAKED_NULL;
1509
1334
                }
 
1335
                if(isEncoded(urls->displayLink.data)) {
 
1336
                        free_if_needed(&host_url);
 
1337
                        return CL_PHISH_HEX_URL;
 
1338
                }
 
1339
        }
 
1340
 
 
1341
 
 
1342
        if(urls->displayLink.data[0]=='\0') {
 
1343
                free_if_needed(&host_url);
 
1344
                return CL_PHISH_CLEAN;
1510
1345
        }
1511
1346
 
1512
1347
        if(urls->flags&CHECK_SSL && isSSL(urls->displayLink.data) && !isSSL(urls->realLink.data)) {
1514
1349
                return CL_PHISH_SSL_SPOOF;
1515
1350
        }
1516
1351
 
1517
 
        if (!(phishy & DOMAIN_LISTED)) {
 
1352
        if(!urls->flags&CHECK_CLOAKING && urls->flags & DOMAINLIST_REQUIRED && !(phishy&DOMAIN_LISTED) ) {
1518
1353
                free_if_needed(&host_url);
1519
 
                return CL_PHISH_CLEAN;
 
1354
                return CL_PHISH_HOST_NOT_LISTED;
1520
1355
        }
1521
1356
 
1522
 
        if((rc = url_get_host(urls,&host_url,DOMAIN_REAL,&phishy)))
 
1357
        if((rc = url_get_host(pchk, urls,&host_url,DOMAIN_REAL,&phishy)))
1523
1358
        {
1524
1359
                free_if_needed(&host_url);
1525
 
                return rc < 0 ? rc : CL_PHISH_CLEAN;
 
1360
                return rc;
 
1361
        }
 
1362
 
 
1363
        if(urls->flags&DOMAINLIST_REQUIRED && !(phishy&DOMAIN_LISTED)) {
 
1364
                free_if_needed(&host_url);
 
1365
                return CL_PHISH_HOST_NOT_LISTED;
 
1366
        }
 
1367
 
 
1368
        if(!strncmp(urls->displayLink.data,cid,cid_len))/* cid: image */{
 
1369
                free_if_needed(&host_url);
 
1370
                return CL_PHISH_CLEAN_CID;
1526
1371
        }
1527
1372
 
1528
1373
        if(whitelist_check(engine,&host_url,1)) {
1529
1374
                free_if_needed(&host_url);
1530
 
                return CL_PHISH_CLEAN;
 
1375
                return CL_PHISH_HOST_WHITELISTED;
1531
1376
        }
1532
1377
 
1533
 
        if(!strcmp(urls->realLink.data,urls->displayLink.data)) {
 
1378
        if(!isURL(pchk, urls->displayLink.data) &&
 
1379
                        ( (phishy&PHISHY_NUMERIC_IP && !isNumericURL(pchk, urls->displayLink.data)) ||
 
1380
                          !(phishy&PHISHY_NUMERIC_IP))) {
1534
1381
                free_if_needed(&host_url);
1535
 
                return CL_PHISH_CLEAN;
 
1382
                return CL_PHISH_TEXTURL;
1536
1383
        }
1537
1384
 
1538
 
        {
1539
 
                struct url_check domain_url;
1540
 
                url_check_init(&domain_url);
1541
 
                url_get_domain(&host_url,&domain_url);
1542
 
                if(!strcmp(domain_url.realLink.data,domain_url.displayLink.data)) {
 
1385
        if(urls->flags&HOST_SUFFICIENT) {
 
1386
                if(!strcmp(urls->realLink.data,urls->displayLink.data)) {
1543
1387
                        free_if_needed(&host_url);
 
1388
                        return CL_PHISH_HOST_OK;
 
1389
                }
 
1390
 
 
1391
 
 
1392
                if(urls->flags&DOMAIN_SUFFICIENT) {
 
1393
                        struct url_check domain_url;
 
1394
                        url_check_init(&domain_url);
 
1395
                        url_get_domain(pchk, &host_url,&domain_url);
 
1396
                        if(!strcmp(domain_url.realLink.data,domain_url.displayLink.data)) {
 
1397
                                free_if_needed(&host_url);
 
1398
                                free_if_needed(&domain_url);
 
1399
                                return CL_PHISH_DOMAIN_OK;
 
1400
                        }
1544
1401
                        free_if_needed(&domain_url);
1545
 
                        return CL_PHISH_CLEAN;
1546
 
                }
1547
 
                free_if_needed(&domain_url);
1548
 
        }
1549
 
 
1550
 
        free_if_needed(&host_url);
1551
 
        /*we failed to find a reason why the 2 URLs are different, this is definitely phishing*/
 
1402
                }
 
1403
 
 
1404
                /*if(urls->flags&CHECK_REDIR) {
 
1405
                        //see where the realLink redirects, and compare that with the displayed Link
 
1406
                        const uchar* redirectedURL  = getRedirectedURL(urls->realLink);
 
1407
                        if(urls->needsfree)
 
1408
                                free(urls->realLink);
 
1409
                        urls->realLink = redirectedURL;
 
1410
 
 
1411
                        if(!strcmp(urls->realLink,urls->displayLink))
 
1412
                                return CL_PHISH_REDIR_OK;
 
1413
 
 
1414
                        if(urls->flags&HOST_SUFFICIENT) {
 
1415
                                if(rc = url_get_host(urls,&host_url,DOMAIN_REAL))
 
1416
                                if(!strcmp(host_url.realLink,host_url.displayLink)) {
 
1417
                                        free_if_needed(&host_url);
 
1418
                                        return CL_PHISH_HOST_REDIR_OK;
 
1419
                                }
 
1420
                                if(urls->flags&DOMAIN_SUFFICIENT) {
 
1421
                                        struct url_check domain_url;
 
1422
                                        url_get_domain(&host_url,&domain_url);
 
1423
                                        if(!strcmp(domain_url.realLink,domain_url.displayLink)) {
 
1424
                                                free_if_needed(&host_url);
 
1425
                                                free_if_needed(&domain_url);
 
1426
                                                return CL_PHISH_DOMAIN_REDIR_OK;
 
1427
                                        }
 
1428
                                }
 
1429
                        }//HOST_SUFFICIENT&CHECK_REDIR
 
1430
                }
 
1431
                free_if_needed(&host_url);*/
 
1432
        /*      if(urls->flags&CHECK_DOMAIN_REVERSE) {
 
1433
                        //do a DNS lookup of the domain, and see what IP it corresponds to
 
1434
                        //then do a reverse lookup on the IP, and see what domain you get
 
1435
                        //There are some corporate signatures that mix different domains belonging to same company
 
1436
                        struct url_check domain_url;
 
1437
                        url_check_init(&domain_url);
 
1438
                        if(!dns_to_ip_and_reverse(&host_url,DOMAIN_DISPLAY)) {
 
1439
                                if(!strcmp(host_url.realLink.data,host_url.displayLink.data)) {
 
1440
                                        free_if_needed(&host_url);
 
1441
                                        return CL_PHISH_HOST_REVERSE_OK;
 
1442
                                }
 
1443
                                if(urls->flags&DOMAIN_SUFFICIENT) {
 
1444
                                        url_get_domain(&host_url,&domain_url);
 
1445
                                        if(!strcmp(domain_url.realLink.data,domain_url.displayLink.data)) {
 
1446
                                                free_if_needed(&host_url);
 
1447
                                                free_if_needed(&domain_url);
 
1448
                                                return CL_PHISH_DOMAIN_REVERSE_OK;
 
1449
                                        }
 
1450
                                        free_if_needed(&domain_url);
 
1451
                                }
 
1452
                        }
 
1453
                }*/
 
1454
                free_if_needed(&host_url);
 
1455
        }/*HOST_SUFFICIENT*/
 
1456
        /*we failed to find a reason why the 2 URLs are different, this is definetely phishing*/
 
1457
        if(urls->flags&DOMAINLIST_REQUIRED && !(phishy&DOMAIN_LISTED))
 
1458
                return CL_PHISH_HOST_NOT_LISTED;
1552
1459
        return phishy_map(phishy,CL_PHISH_NOMATCH);
1553
1460
}
1554
1461
 
1557
1464
        switch(rc) {
1558
1465
                case CL_PHISH_CLEAN:
1559
1466
                        return "Clean";
 
1467
                case CL_PHISH_CLEANUP_OK:
 
1468
                        return "URLs match after cleanup";
 
1469
                case CL_PHISH_WHITELISTED:
 
1470
                        return "URL is whitelisted";
 
1471
                case CL_PHISH_HOST_WHITELISTED:
 
1472
                        return "host part of URL is whitelist";
 
1473
                case CL_PHISH_HOST_OK:
 
1474
                        return "Hosts match";
 
1475
                case CL_PHISH_DOMAIN_OK:
 
1476
                        return "Domains match";
 
1477
                case CL_PHISH_REDIR_OK:
 
1478
                        return "After redirecting realURL, they match";
 
1479
                case CL_PHISH_HOST_REDIR_OK:
 
1480
                        return "After redirecting realURL, hosts match";
 
1481
                case CL_PHISH_DOMAIN_REDIR_OK:
 
1482
                        return "After redirecting the domains match";
 
1483
                case CL_PHISH_MAILTO_OK:
 
1484
                        return "URL is mailto";
 
1485
                case CL_PHISH_NUMERIC_IP:
 
1486
                        return "IP address encountered in hostname";
 
1487
                case CL_PHISH_TEXTURL:
 
1488
                        return "Displayed link is not an URL, can't check if phishing or not";
1560
1489
                case CL_PHISH_CLOAKED_NULL:
1561
1490
                        return "Link URL is cloaked (null byte %00)";
1562
1491
                case CL_PHISH_CLOAKED_UIU:
1566
1495
                        return "Visible links is SSL, real link is not";
1567
1496
                case CL_PHISH_NOMATCH:
1568
1497
                        return "URLs are way too different";
1569
 
                case CL_PHISH_HASH0:
1570
 
                case CL_PHISH_HASH1:
1571
 
                case CL_PHISH_HASH2:
1572
 
                        return "Blacklisted";
 
1498
                case CL_PHISH_HOST_NOT_LISTED:
 
1499
                        return "Host not listed in .pdb -> not checked";
 
1500
                case CL_PHISH_CLEAN_CID:
 
1501
                        return "Embedded image in mail -> clean";
 
1502
                case CL_PHISH_HEX_URL:
 
1503
                        return "Embedded hex urls";
1573
1504
                default:
1574
1505
                        return "Unknown return code";
1575
1506
        }
1576
1507
}
1577
1508
 
 
1509
#endif