~ubuntu-branches/ubuntu/raring/clamav/raring

« back to all changes in this revision

Viewing changes to libclamav/phishcheck.c

  • Committer: Bazaar Package Importer
  • Author(s): Stephen Gran
  • Date: 2008-09-05 17:25:34 UTC
  • mfrom: (0.35.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080905172534-yi3f8fkye1o7u1r3
* New upstream version (closes: #497662, #497773)
  - lots of new options for clamd.conf
  - fixes CVEs CVE-2008-3912, CVE-2008-3913, CVE-2008-3914, and
    CVE-2008-1389
* No longer supports --unzip option, so typo is gone (closes: #496276)
* Translations:
  - sv (thanks Martin Bagge <brother@bsnet.se>) (closes: #491760)

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
#include <ctype.h>
40
40
 
41
41
#include "clamav.h"
 
42
#include "cltypes.h"
42
43
#include "others.h"
43
44
#include "mbox.h"
44
45
#include "message.h"
46
47
#include "phishcheck.h"
47
48
#include "phish_domaincheck_db.h"
48
49
#include "phish_whitelist.h"
 
50
#include "regex_list.h"
49
51
#include "iana_tld.h"
50
 
 
 
52
#include "iana_cctld.h"
 
53
#include "scanners.h"
 
54
#include "md5.h"
 
55
#include <assert.h>
51
56
 
52
57
#define DOMAIN_REAL 1
53
58
#define DOMAIN_DISPLAY 0
59
64
 * <a href='mailto:somebody@yahoo.com'>to:somebody@yahoo.com</a>*/
60
65
#define DOMAIN_LISTED            8
61
66
#define PHISHY_CLOAKED_NULL     16
62
 
#define PHISHY_HEX_URL          32
63
67
 
64
68
/*
65
69
* Phishing design documentation,
135
139
/* Constant strings and tables */ 
136
140
static char empty_string[]="";
137
141
 
138
 
 
139
 
#define ANY_CLOAK "(0[xX][0-9a-fA-F]+|[0-9]+)"
140
 
#define CLOAKED_URL "^"ANY_CLOAK"(\\."ANY_CLOAK"){0,3}$"
141
 
 
142
 
static const char cloaked_host_regex[] = CLOAKED_URL;
143
 
static const char tld_regex[] = "^"iana_tld"$";
144
 
static const char cctld_regex[] = "^"iana_cctld"$";
145
142
static const char dotnet[] = ".net";
146
143
static const char adonet[] = "ado.net";
147
144
static const char aspnet[] = "asp.net";
151
148
static const char src_text[] = "src";
152
149
static const char href_text[] = "href";
153
150
static const char mailto[] = "mailto:";
 
151
static const char mailto_proto[] = "mailto://";
154
152
static const char https[]="https://";
 
153
static const char http[]="http://";
 
154
static const char ftp[] = "ftp://";
155
155
 
156
156
static const size_t href_text_len = sizeof(href_text);
157
157
static const size_t src_text_len = sizeof(src_text);
161
161
static const size_t lt_len = sizeof(lt)-1;
162
162
static const size_t gt_len = sizeof(gt)-1;
163
163
static const size_t mailto_len = sizeof(mailto)-1;
 
164
static const size_t mailto_proto_len = sizeof(mailto_proto)-1;
164
165
static const size_t https_len  = sizeof(https)-1;
 
166
static const size_t http_len  = sizeof(http)-1;
 
167
static const size_t ftp_len  = sizeof(ftp)-1;
165
168
 
166
169
/* for urls, including mailto: urls, and (broken) http:www... style urls*/
167
170
/* refer to: http://www.w3.org/Addressing/URL/5_URI_BNF.html
169
172
 * So the 'safe' char class has been split up
170
173
 * */
171
174
/* character classes */
172
 
#define URI_alpha       "a-zA-Z"
173
175
#define URI_digit       "0-9"
174
 
#define URI_safe_nodot  "-$_@&"
175
 
#define URI_safe        "-$_@.&"
176
 
#define URI_extra       "!*\"'(),"
177
 
 
178
 
#define URI_hex          "[0-9a-fA-f]"
179
 
#define URI_escape      "%"URI_hex"{2}"
180
 
#define URI_xalpha "([" URI_safe URI_alpha URI_digit  URI_extra "]|"URI_escape")" /* URI_safe has to be first, because it contains - */
181
 
#define URI_xalpha_nodot "([" URI_safe_nodot URI_alpha URI_digit URI_extra "]|"URI_escape")"
182
 
 
183
 
#define URI_xalphas_nodot URI_xalpha_nodot"*"
184
 
 
185
 
#define URI_ialpha  "["URI_alpha"]"URI_xalphas_nodot""
186
 
#define URI_xpalpha URI_xalpha"|\\+"
187
 
#define URI_xpalpha_nodot URI_xalpha_nodot"|\\+"
188
 
#define URI_xpalphas_nodot "("URI_xpalpha_nodot")+"
189
 
 
190
 
#define URI_scheme URI_ialpha
191
 
#define URI_tld iana_tld
192
 
#define URI_path1 URI_xpalphas_nodot"\\.("URI_xpalphas_nodot"\\.)*"
193
 
 
194
176
#define URI_IP_digits "["URI_digit"]{1,3}"
195
177
#define URI_path_start "[/?:]?"
196
178
#define URI_numeric_path URI_IP_digits"(\\."URI_IP_digits"){3}"URI_path_start
197
 
#define URI_numeric_URI "("URI_scheme":(//)?)?"URI_numeric_path
 
179
#define URI_numeric_URI "(http|https|ftp:(//)?)?"URI_numeric_path
198
180
#define URI_numeric_fragmentaddress URI_numeric_URI
199
181
 
200
 
#define URI_URI1 "("URI_scheme":(//)?)?"URI_path1
201
 
#define URI_URI2 URI_tld
202
 
 
203
 
#define URI_fragmentaddress1 URI_URI1
204
 
#define URI_fragmentaddress2 URI_URI2""URI_path_start
205
 
 
206
 
#define URI_CHECK_PROTOCOLS "(http|https|ftp|mailto)://.+"
207
182
 
208
183
/*Warning: take care when modifying this regex, it has been tweaked, and tuned, just don't break it please.
209
184
 * there is fragmentaddress1, and 2  to work around the ISO limitation of 509 bytes max length for string constants*/
235
210
static void string_assign_null(struct string* dest);
236
211
static char *rfind(char *start, char c, size_t len);
237
212
static char hex2int(const unsigned char* src);
238
 
static int isTLD(const struct phishcheck* pchk,const char* str,int len);
239
213
static enum phish_status phishingCheck(const struct cl_engine* engine,struct url_check* urls);
240
214
static const char* phishing_ret_toString(enum phish_status rc);
241
215
 
416
390
                        }
417
391
 
418
392
                        tld = strrchr(realhost,'.');
419
 
                        rc = tld ? isTLD(s,tld,tld-realhost-1) : 0;
 
393
                        rc = tld ? !!in_tld_set(tld,strlen(tld)) : 0;
420
394
                        if(rc < 0)
421
395
                                return rc;
422
396
                        if(rc)
438
412
        return 0;
439
413
}
440
414
 
441
 
static int isCountryCode(const struct phishcheck* s,const char* str)
442
 
{
443
 
        return str ? !cli_regexec(&s->preg_cctld,str,0,NULL,0) : 0;
444
 
}
445
 
 
446
 
static int isTLD(const struct phishcheck* pchk,const char* str,int len)
447
 
{
448
 
        if (!str)
449
 
                return 0;
450
 
        else {
451
 
                char*   s  = cli_malloc(len+1);
452
 
                int rc;
453
 
 
454
 
                if(!s)
455
 
                        return CL_EMEM;
456
 
                strncpy(s,str,len);
457
 
                s[len]='\0';
458
 
                rc = !cli_regexec(&pchk->preg_tld,s,0,NULL,0);
459
 
                free(s);
460
 
                return rc ? 1 : 0;
461
 
        }
462
 
}
463
415
 
464
416
/*
465
417
 * memrchr isn't standard, so I use this
486
438
                string_assign(dest,host);
487
439
                return;
488
440
        }
489
 
        if(isCountryCode(pchk,tld+1)) {
 
441
        if(in_cctld_set(tld+1, strlen(tld+1))) {
490
442
                const char* countrycode = tld+1;
491
443
                tld = rfind(host->data,'.',tld-host->data-1);
492
444
                if(!tld) {
495
447
                        string_assign(dest,host);
496
448
                        return;
497
449
                }
498
 
                if(!isTLD(pchk,tld+1,countrycode-tld-2)) {
 
450
                if(!in_tld_set(tld+1, countrycode-tld-2)) {
499
451
                        string_assign_ref(dest,host,tld+1);
500
452
                        return;/*it was a name like: subdomain.domain.uk, return domain.uk*/
501
453
                }
667
619
        /* strip spaces */
668
620
        str_strip(&sbegin, &send, " ",1);
669
621
        /* strip leading/trailing garbage */
670
 
        while(!isalnum(sbegin[0]) && sbegin <= send) sbegin++;
671
 
        while(!isalnum(send[0]) && send >= sbegin) send--;
 
622
        while(!isalnum(sbegin[0]&0xff) && sbegin <= send) sbegin++;
 
623
        while(!isalnum(send[0]&0xff) && send >= sbegin) send--;
672
624
 
673
625
        /* keep terminating slash character*/
674
626
        if(send[1] == '/') send++;
737
689
                        /* @end points to last character we want to be part of the URL */
738
690
                        end = host_begin + host_len - 1;
739
691
                }
740
 
                /* terminate URL with a slash, except when we're at end of string */
741
 
                if(host_begin[host_len]) {
742
 
                        host_begin[host_len] = '/';
743
 
                        end++;
744
 
                }
 
692
                host_begin[host_len] = '\0';
745
693
                /* convert hostname to lowercase, but only hostname! */
746
694
                str_make_lowercase(host_begin, host_len);
747
695
                /* some broken MUAs put > in the href, and then
777
725
        return 0;
778
726
}
779
727
 
780
 
 
781
728
/* -------end runtime disable---------*/
782
 
static int found_possibly_unwanted(cli_ctx* ctx)
783
 
{
784
 
        ctx->found_possibly_unwanted = 1;
785
 
        cli_dbgmsg("Phishcheck: found Possibly Unwanted: %s\n",*ctx->virname);
786
 
        return CL_CLEAN;
787
 
}
788
 
 
789
729
int phishingScan(message* m,const char* dir,cli_ctx* ctx,tag_arguments_t* hrefs)
790
730
{
791
731
        /* TODO: get_host and then apply regex, etc. */
797
737
 
798
738
        if(!ctx->found_possibly_unwanted)
799
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
800
773
        for(i=0;i<hrefs->count;i++)
801
774
                if(hrefs->contents[i]) {
802
775
                        struct url_check urls;
837
810
                        free_if_needed(&urls);
838
811
                        cli_dbgmsg("Phishcheck: Phishing scan result: %s\n",phishing_ret_toString(rc));
839
812
                        switch(rc)/*TODO: support flags from ctx->options,*/
840
 
                                {
841
 
                                        case CL_PHISH_CLEAN:
842
 
                                                continue;
843
 
/*                                              break;*/
844
 
                                        case CL_PHISH_HEX_URL:
845
 
                                                *ctx->virname="Phishing.Heuristics.Email.HexURL";
846
 
                                                return found_possibly_unwanted(ctx);
847
 
/*                                              break;*/
848
 
                                        case CL_PHISH_NUMERIC_IP:
849
 
                                                *ctx->virname="Phishing.Heuristics.Email.Cloaked.NumericIP";
850
 
                                                return found_possibly_unwanted(ctx);
851
 
                                        case CL_PHISH_CLOAKED_NULL:
852
 
                                                *ctx->virname="Phishing.Heuristics.Email.Cloaked.Null";/*http://www.real.com%01%00@www.evil.com*/
853
 
                                                return found_possibly_unwanted(ctx);
854
 
                                        case CL_PHISH_SSL_SPOOF:
855
 
                                                *ctx->virname="Phishing.Heuristics.Email.SSL-Spoof";
856
 
                                                return found_possibly_unwanted(ctx);
857
 
                                        case CL_PHISH_CLOAKED_UIU:
858
 
                                                *ctx->virname="Phishing.Heuristics.Email.Cloaked.Username";/*http://www.ebay.com@www.evil.com*/
859
 
                                                return found_possibly_unwanted(ctx);
860
 
                                        case CL_PHISH_NOMATCH:
861
 
                                        default:
862
 
                                                *ctx->virname="Phishing.Heuristics.Email.SpoofedDomain";
863
 
                                                return found_possibly_unwanted(ctx);
864
 
                                }
 
813
                        {
 
814
                                case CL_PHISH_CLEAN:
 
815
                                        continue;
 
816
                                case CL_PHISH_NUMERIC_IP:
 
817
                                        *ctx->virname="Phishing.Heuristics.Email.Cloaked.NumericIP";
 
818
                                        break;
 
819
                                case CL_PHISH_CLOAKED_NULL:
 
820
                                        *ctx->virname="Phishing.Heuristics.Email.Cloaked.Null";/*http://www.real.com%01%00@www.evil.com*/
 
821
                                        break;
 
822
                                case CL_PHISH_SSL_SPOOF:
 
823
                                        *ctx->virname="Phishing.Heuristics.Email.SSL-Spoof";
 
824
                                        break;
 
825
                                case CL_PHISH_CLOAKED_UIU:
 
826
                                        *ctx->virname="Phishing.Heuristics.Email.Cloaked.Username";/*http://www.ebay.com@www.evil.com*/
 
827
                                        break;
 
828
                                case CL_PHISH_HASH0:
 
829
                                case CL_PHISH_HASH1:
 
830
                                case CL_PHISH_HASH2:
 
831
                                        *ctx->virname="Phishing.URL.Blacklisted";
 
832
                                        break;
 
833
                                case CL_PHISH_NOMATCH:
 
834
                                default:
 
835
                                        *ctx->virname="Phishing.Heuristics.Email.SpoofedDomain";
 
836
                                        break;
 
837
                        }
 
838
                        return cli_found_possibly_unwanted(ctx);
865
839
                }
866
840
                else
867
841
                        if(strcmp((char*)hrefs->tag[i],"href"))
869
843
        return CL_CLEAN;
870
844
}
871
845
 
872
 
static char* str_compose(const char* a,const char* b,const char* c)
873
 
{
874
 
        const size_t a_len = strlen(a);
875
 
        const size_t b_len = strlen(b);
876
 
        const size_t c_len = strlen(c);
877
 
        const size_t r_len = a_len+b_len+c_len+1;
878
 
        char* concated = cli_malloc(r_len);
879
 
        if(!concated)
880
 
                return NULL;
881
 
        strncpy(concated,a,a_len);
882
 
        strncpy(concated+a_len,b,b_len);
883
 
        strncpy(concated+a_len+b_len,c,c_len);
884
 
        concated[r_len-1]='\0';
885
 
        return concated;
886
 
}
887
 
 
888
846
static char hex2int(const unsigned char* src)
889
847
{
890
848
        return (src[0] == '0' && src[1] == '0') ? 
902
860
 
903
861
int phishing_init(struct cl_engine* engine)
904
862
{
905
 
        char *url_regex, *realurl_regex;
906
863
        struct phishcheck* pchk;
907
864
        if(!engine->phishcheck) {
908
865
                pchk = engine->phishcheck = cli_malloc(sizeof(struct phishcheck));
922
879
 
923
880
        cli_dbgmsg("Initializing phishcheck module\n");
924
881
 
925
 
        if(build_regex(&pchk->preg_hexurl,cloaked_host_regex,1)) {
926
 
                free(pchk);
927
 
                engine->phishcheck = NULL;
928
 
                return CL_EFORMAT;
929
 
        }
930
 
 
931
 
        if(build_regex(&pchk->preg_cctld,cctld_regex,1)) {
932
 
                free(pchk);
933
 
                engine->phishcheck = NULL;
934
 
                return CL_EFORMAT;
935
 
        }
936
 
        if(build_regex(&pchk->preg_tld,tld_regex,1)) {
937
 
                free_regex(&pchk->preg_cctld);
938
 
                free(pchk);
939
 
                engine->phishcheck = NULL;
940
 
                return CL_EFORMAT;
941
 
        }
942
 
        url_regex = str_compose("^ *(("URI_CHECK_PROTOCOLS")|(",URI_fragmentaddress1,URI_fragmentaddress2")) *$");
943
 
        if(!url_regex || build_regex(&pchk->preg,url_regex,1)) {
944
 
                free_regex(&pchk->preg_cctld);
945
 
                free_regex(&pchk->preg_tld);
946
 
                free(url_regex);
947
 
                free(pchk);
948
 
                engine->phishcheck = NULL;
949
 
                return CL_EFORMAT;
950
 
        }
951
 
        free(url_regex);
952
 
        realurl_regex = str_compose("^ *(("URI_CHECK_PROTOCOLS")|(",URI_path1,URI_fragmentaddress2")) *$");
953
 
        if(!realurl_regex || build_regex(&pchk->preg_realurl, realurl_regex,1)) {
954
 
                free_regex(&pchk->preg_cctld);
955
 
                free_regex(&pchk->preg_tld);
956
 
                free_regex(&pchk->preg);
957
 
                free(url_regex);
958
 
                free(realurl_regex);
959
 
                free(pchk);
960
 
                engine->phishcheck = NULL;
961
 
                return CL_EFORMAT;
962
 
        }
963
 
        free(realurl_regex);
964
882
        if(build_regex(&pchk->preg_numeric,numeric_url_regex,1)) {
965
 
                free_regex(&pchk->preg_cctld);
966
 
                free_regex(&pchk->preg_tld);
967
 
                free_regex(&pchk->preg);
968
 
                free_regex(&pchk->preg_realurl);
969
883
                free(pchk);
970
884
                engine->phishcheck = NULL;
971
885
                return CL_EFORMAT;
980
894
        struct phishcheck* pchk = engine->phishcheck;
981
895
        cli_dbgmsg("Cleaning up phishcheck\n");
982
896
        if(pchk && !pchk->is_disabled) {
983
 
                free_regex(&pchk->preg);
984
 
                free_regex(&pchk->preg_hexurl);
985
 
                free_regex(&pchk->preg_cctld);
986
 
                free_regex(&pchk->preg_tld);
987
897
                free_regex(&pchk->preg_numeric);
988
 
                free_regex(&pchk->preg_realurl);
989
898
                pchk->is_disabled = 1;
990
899
        }
991
900
        whitelist_done(engine);
998
907
        cli_dbgmsg("Phishcheck cleaned up\n");
999
908
}
1000
909
 
 
910
 
 
911
/*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*/
 
912
static const uint8_t URI_alpha[256] = {
 
913
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
914
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
915
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
916
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
917
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 
918
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
 
919
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 
920
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
925
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
926
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
927
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
928
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 
929
};
 
930
 
 
931
/*!"$%&'()*,-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz*/
 
932
static const uint8_t URI_xalpha_nodot[256] = {
 
933
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
934
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
935
        0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0,
 
936
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
 
937
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 
938
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
 
939
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 
940
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
945
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
946
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
947
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
948
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 
949
};
 
950
 
 
951
/*!"#$%&'()*+,-0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz*/
 
952
static const uint8_t URI_xpalpha_nodot[256] = {
 
953
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
954
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
955
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
 
956
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
 
957
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 
958
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
 
959
        0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 
960
        1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
965
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
966
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
967
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 
968
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 
969
};
 
970
 
 
971
static inline int validate_uri_xalphas_nodot(const char *start, const char *end)
 
972
{
 
973
        const unsigned char *p = start;
 
974
        for(p=start;p < (const unsigned char*)end; p++) {
 
975
                if(!URI_xalpha_nodot[*p])
 
976
                        return 0;
 
977
        }
 
978
        return 1;
 
979
}
 
980
 
 
981
static inline int validate_uri_xpalphas_nodot(const char *start, const char *end)
 
982
{
 
983
        const unsigned char *p = start;
 
984
        for(p=start;p < (const unsigned char*)end; p++) {
 
985
                if(!URI_xpalpha_nodot[*p])
 
986
                        return 0;
 
987
        }
 
988
        /* must have at least on char */
 
989
        return p > (const unsigned char*)start;
 
990
}
 
991
 
 
992
 
 
993
static inline int validate_uri_ialpha(const char *start, const char *end)
 
994
{
 
995
        const unsigned char *p = start;
 
996
        if(start >= end || !URI_alpha[*p])
 
997
                return 0;
 
998
        return validate_uri_xalphas_nodot(start + 1, end);
 
999
}
 
1000
 
1001
1001
/*
1002
1002
 * Only those URLs are identified as URLs for which phishing detection can be performed.
1003
1003
 */
1004
 
static int isURL(const struct phishcheck* pchk,const char* URL)
 
1004
static int isURL(const struct phishcheck* pchk,const char* URL, int accept_anyproto)
1005
1005
{
1006
 
        return URL ? !cli_regexec(&pchk->preg,URL,0,NULL,0) : 0;
 
1006
        const char *start = NULL, *p, *q;
 
1007
        if(!URL)
 
1008
                return 0;
 
1009
 
 
1010
        switch (URL[0]) {
 
1011
                case 'h':
 
1012
                        if (strncmp(URL, https, https_len) == 0)
 
1013
                                start = URL + https_len;
 
1014
                        else if (strncmp(URL, http, http_len) == 0)
 
1015
                                start = URL + http_len;
 
1016
                        break;
 
1017
                case 'f':
 
1018
                       if (strncmp(URL, ftp, ftp_len) == 0)
 
1019
                               start = URL + ftp_len;
 
1020
                       break;
 
1021
                case 'm':
 
1022
                       if (strncmp(URL, mailto_proto, mailto_proto_len) == 0)
 
1023
                               start = URL + mailto_proto_len;
 
1024
                       break;
 
1025
        }
 
1026
        if(start) {
 
1027
                if(start[0] == '\0')
 
1028
                        return 0;/* empty URL */
 
1029
                /* has a valid protocol, it is a URL */
 
1030
                return 1;
 
1031
        }
 
1032
        start = accept_anyproto ?  strchr(URL, ':') : NULL;
 
1033
        if(start) {
 
1034
                /* validate URI scheme */
 
1035
                if(validate_uri_ialpha(URL, start)) {
 
1036
                        if(start[1] == '/' && start[2] == '/')
 
1037
                                start += 3; /* skip :// */
 
1038
                        else
 
1039
                                start++;
 
1040
                }
 
1041
                else
 
1042
                        start = URL; /* scheme invalid */
 
1043
        } else
 
1044
                start = URL;
 
1045
        p = start;
 
1046
        do {
 
1047
                q = strchr(p, '.');
 
1048
                if(q) {
 
1049
                        if(!validate_uri_xpalphas_nodot(p, q))
 
1050
                                return 0;
 
1051
                        p = q+1;
 
1052
                }
 
1053
        } while(q);
 
1054
        if (p == start) /* must have at least one dot in the URL */
 
1055
                return 0;
 
1056
        return !!in_tld_set(p, strlen(p));
1007
1057
}
1008
1058
 
1009
1059
/*
1010
1060
 * Check if this is a real URL, which basically means to check if it has a known URL scheme (http,https,ftp).
1011
1061
 * This prevents false positives with outbind:// and blocked:: links.
1012
1062
 */
 
1063
#if 0
1013
1064
static int isRealURL(const struct phishcheck* pchk,const char* URL)
1014
1065
{
1015
1066
        return URL ? !cli_regexec(&pchk->preg_realurl,URL,0,NULL,0) : 0;
1016
1067
}
 
1068
#endif
1017
1069
 
1018
1070
static int isNumericURL(const struct phishcheck* pchk,const char* URL)
1019
1071
{
1064
1116
                cli_dbgmsg("Phishcheck:skipping invalid host\n");
1065
1117
                return CL_PHISH_CLEAN;
1066
1118
        }
1067
 
        if(url->flags&CHECK_CLOAKING && !cli_regexec(&pchk->preg_hexurl,host->data,0,NULL,0)) {
1068
 
                /* uses a regex here, so that we don't accidentally block 0xacab.net style hosts */
1069
 
                return CL_PHISH_HEX_URL;
1070
 
        }
1071
1119
        if(isNumeric(host->data)) {
1072
1120
                *phishy |= PHISHY_NUMERIC_IP;
1073
1121
        }
1095
1143
                return fallback;
1096
1144
}
1097
1145
 
1098
 
static int isEncoded(const char* url)
1099
 
{
1100
 
        const char* start=url;
1101
 
        size_t cnt=0;
1102
 
        do{
1103
 
                cnt++;
1104
 
                start=strstr(start,"&#");
1105
 
                if(start)
1106
 
                        start=strstr(start,";");
1107
 
        } while(start);
1108
 
        return (cnt-1 >strlen(url)*7/10);/*more than 70% made up of &#;*/
1109
 
}
1110
 
 
1111
1146
static int whitelist_check(const struct cl_engine* engine,struct url_check* urls,int hostOnly)
1112
1147
{
1113
1148
        return whitelist_match(engine,urls->realLink.data,urls->displayLink.data,hostOnly);
1114
1149
}
1115
1150
 
 
1151
static int hash_match(const struct regex_matcher *rlist, const char *host, size_t hlen, const char *path, size_t plen)
 
1152
{
 
1153
        const char *virname;
 
1154
#if 0
 
1155
        char s[1024];
 
1156
        strncpy(s, host, hlen);
 
1157
        strncpy(s+hlen, path, plen);
 
1158
        s[hlen+plen] = '\0';
 
1159
        cli_dbgmsg("hash lookup for: %s\n",s);
 
1160
#endif
 
1161
        if(rlist->md5_hashes.bm_patterns) {
 
1162
                unsigned char md5_dig[16];
 
1163
                cli_md5_ctx md5;
 
1164
 
 
1165
                cli_md5_init(&md5);
 
1166
                cli_md5_update(&md5, host, hlen);
 
1167
                cli_md5_update(&md5, path, plen);
 
1168
                cli_md5_final(md5_dig, &md5);
 
1169
                if(SO_search(&rlist->md5_filter, md5_dig, 16) != -1 &&
 
1170
                                cli_bm_scanbuff(md5_dig, 16, &virname, &rlist->md5_hashes,0,0,-1) == CL_VIRUS) {
 
1171
                        switch(*virname) {
 
1172
                                case '1':
 
1173
                                        return CL_PHISH_HASH1;
 
1174
                                case '2':
 
1175
                                        return CL_PHISH_HASH2;
 
1176
                                default:
 
1177
                                        return CL_PHISH_HASH0;
 
1178
                        }
 
1179
                }
 
1180
        }
 
1181
        return CL_SUCCESS;
 
1182
}
 
1183
 
 
1184
#define URL_MAX_LEN 1024
 
1185
#define COMPONENTS 4
 
1186
static int url_hash_match(const struct regex_matcher *rlist, const char *inurl, size_t len)
 
1187
{
 
1188
        char urlbuff[URL_MAX_LEN+3];/* htmlnorm truncates at 1024 bytes + terminating null + slash + host end null */
 
1189
        char *url, *p;
 
1190
        const char *urlend = urlbuff + len;
 
1191
        char *host_begin;
 
1192
        size_t host_len, path_len;
 
1193
        char *path_begin;
 
1194
        const char *component;
 
1195
        const char *lp[COMPONENTS+1];
 
1196
        size_t pp[COMPONENTS+2];
 
1197
        size_t j, k, ji, ki;
 
1198
        int rc;
 
1199
 
 
1200
        if(!rlist || !rlist->md5_hashes.bm_patterns) {
 
1201
                return CL_SUCCESS;
 
1202
        }
 
1203
        if(!inurl)
 
1204
                return CL_EMEM;
 
1205
        strncpy(urlbuff, inurl, URL_MAX_LEN);
 
1206
        urlbuff[URL_MAX_LEN] = urlbuff[URL_MAX_LEN+1] = urlbuff[URL_MAX_LEN+2] = '\0';
 
1207
        url = urlbuff;
 
1208
        str_hex_to_char(&url, &urlend);
 
1209
        len = urlend - url;
 
1210
        host_begin = strchr(url,':');
 
1211
        if(!host_begin) {
 
1212
                return CL_PHISH_CLEAN;
 
1213
        }
 
1214
        ++host_begin;
 
1215
        while((host_begin < urlend) && *host_begin == '/') ++host_begin;
 
1216
        while(*host_begin == '.' && host_begin < urlend) ++host_begin;
 
1217
        p = strchr(host_begin, '@');
 
1218
        if(p)
 
1219
                host_begin = p+1;
 
1220
 
 
1221
        host_len = strcspn(host_begin, ":/?");
 
1222
        path_begin = host_begin + host_len;
 
1223
        if(host_len < len) {
 
1224
                memmove(path_begin + 2, path_begin + 1, len - host_len);
 
1225
                *path_begin++ = '/';
 
1226
                *path_begin++ = '\0';
 
1227
        } else path_begin = url+len;
 
1228
        if(url + len >= path_begin) {
 
1229
                path_len = url + len - path_begin + 1;
 
1230
        } else
 
1231
                path_len = 0;
 
1232
        str_make_lowercase(host_begin, host_len);
 
1233
 
 
1234
        j=COMPONENTS;
 
1235
        component = strrchr(host_begin, '.');
 
1236
        while(component && j > 0) {
 
1237
                do {
 
1238
                        --component;
 
1239
                } while(*component != '.' && component > host_begin);
 
1240
                if(*component != '.')
 
1241
                        component = NULL;
 
1242
                if(component)
 
1243
                        lp[j--] = component + 1;
 
1244
        }
 
1245
        lp[j] = host_begin;
 
1246
 
 
1247
        pp[0] = path_len;
 
1248
        if(path_len) {
 
1249
                pp[1] = strcspn(path_begin, "?");
 
1250
                if(pp[1] != pp[0]) k = 2;
 
1251
                else k = 1;
 
1252
                pp[k++] = 0;
 
1253
                while(k < COMPONENTS+2) {
 
1254
                        p = strchr(path_begin + pp[k-1] + 1, '/');
 
1255
                        if(p && p > path_begin) {
 
1256
                                pp[k++] = p - path_begin;
 
1257
                        } else
 
1258
                                break;
 
1259
                }
 
1260
        } else
 
1261
                k = 1;
 
1262
 
 
1263
        for(ji=j;ji < COMPONENTS+1; ji++) {
 
1264
                for(ki=0;ki < k; ki++) {
 
1265
                        assert(pp[ki] <= path_len);
 
1266
                        rc = hash_match(rlist, lp[ji], host_begin + host_len - lp[ji] + 1, path_begin, pp[ki]);
 
1267
                        if(rc) {
 
1268
                                return rc;
 
1269
                        }
 
1270
                }
 
1271
        }
 
1272
        return CL_SUCCESS;
 
1273
}
 
1274
 
1116
1275
/* urls can't contain null pointer, caller must ensure this */
1117
1276
static enum phish_status phishingCheck(const struct cl_engine* engine,struct url_check* urls)
1118
1277
{
1130
1289
        if(!strcmp(urls->realLink.data,urls->displayLink.data))
1131
1290
                return CL_PHISH_CLEAN;/* displayed and real URL are identical -> clean */
1132
1291
 
 
1292
        if(!isURL(pchk, urls->realLink.data, 0)) {
 
1293
                cli_dbgmsg("Real 'url' is not url:%s\n",urls->realLink.data);
 
1294
                return CL_PHISH_CLEAN;
 
1295
        }
 
1296
 
 
1297
        if(( rc = url_hash_match(engine->domainlist_matcher, urls->realLink.data, strlen(urls->realLink.data)) )) {
 
1298
                cli_dbgmsg("Hash matched for: %s\n", urls->realLink.data);
 
1299
                return rc;
 
1300
        }
 
1301
 
1133
1302
        if((rc = cleanupURLs(urls))) {
1134
1303
                /* it can only return an error, or say its clean;
1135
1304
                 * it is not allowed to decide it is phishing */
1139
1308
        cli_dbgmsg("Phishcheck:URL after cleanup: %s->%s\n", urls->realLink.data,
1140
1309
                urls->displayLink.data);
1141
1310
 
1142
 
        if((!isURL(pchk, urls->displayLink.data) || !isRealURL(pchk, urls->realLink.data) ) &&
 
1311
        if((!isURL(pchk, urls->displayLink.data, 1) ) &&
1143
1312
                        ( (phishy&PHISHY_NUMERIC_IP && !isNumericURL(pchk, urls->displayLink.data)) ||
1144
1313
                          !(phishy&PHISHY_NUMERIC_IP))) {
1145
1314
                cli_dbgmsg("Displayed 'url' is not url:%s\n",urls->displayLink.data);
1176
1345
                        free_if_needed(&host_url);
1177
1346
                        return CL_PHISH_CLOAKED_NULL;
1178
1347
                }
1179
 
                if(isEncoded(urls->displayLink.data)) {
1180
 
                        free_if_needed(&host_url);
1181
 
                        return CL_PHISH_HEX_URL;
1182
 
                }
1183
1348
        }
1184
1349
 
1185
1350
        if(urls->flags&CHECK_SSL && isSSL(urls->displayLink.data) && !isSSL(urls->realLink.data)) {
1234
1399
                        return "Visible links is SSL, real link is not";
1235
1400
                case CL_PHISH_NOMATCH:
1236
1401
                        return "URLs are way too different";
1237
 
                case CL_PHISH_HEX_URL:
1238
 
                        return "Embedded hex urls";
 
1402
                case CL_PHISH_HASH0:
 
1403
                case CL_PHISH_HASH1:
 
1404
                case CL_PHISH_HASH2:
 
1405
                        return "Blacklisted";
1239
1406
                default:
1240
1407
                        return "Unknown return code";
1241
1408
        }