/*
 * Unless otherwise noted, this code is in the public domain.
 */

/* Credit card number prefix and length verification code is a derivate
 * of "Credit Card Validation Solution" available on 
 * http://www.analysisandsolutions.com/software/ccvs/ccvs.htm
 * 
 *
 * <pre>
 * ======================================================================
 * SIMPLE PUBLIC LICENSE                        VERSION 1.1   2003-01-21
 *
 * Copyright (c) The Analysis and Solutions Company
 * http://www.analysisandsolutions.com/
 *
 * 1.  Permission to use, copy, modify, and distribute this software and
 * its documentation, with or without modification, for any purpose and
 * without fee or royalty is hereby granted, provided that you include
 * the following on ALL copies of the software and documentation or
 * portions thereof, including modifications, that you make:
 *
 *     a.  The full text of this license in a location viewable to users
 *     of the redistributed or derivative work.
 *
 *     b.  Notice of any changes or modifications to the files,
 *     including the date changes were made.
 *
 * 2.  The name, servicemarks and trademarks of the copyright holders
 * may NOT be used in advertising or publicity pertaining to the
 * software without specific, written prior permission.
 *
 * 3.  Title to copyright in this software and any associated
 * documentation will at all times remain with copyright holders.
 *
 * 4.  THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND
 * COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY
 * OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE
 * OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS,
 * COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
 *
 * 5.  COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DAMAGES, INCLUDING
 * BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL,
 * ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION.
 * ======================================================================
 * </pre>
 */

#include "sf_snort_plugin_api.h"
#include "sf_snort_packet.h"

#define MIN_CCN 13
#define MAX_CCN 19

/* declare detection functions */
int ruleCCNeval(void *p);

RuleOption *ruleCCNoptions[] =
{
    NULL
};

Rule ruleCCN = {
   /* rule header, akin to => tcp any any -> any any               */
   {
       IPPROTO_IP, /* proto */
       HOME_NET,   /* SRCIP     */
       "any",      /* SRCPORT   */
       0,          /* DIRECTION */
       "any",      /* DSTIP     */
       "any",      /* DSTPORT   */
   },
   /* metadata */
   {
       3,
       33000,
       1,
       "policy-violation",
       0,
       "POLICY-VIOLATION: Possible unencrypted Credit Card Number data detected",
       0 /* ptr to references */
   },
   ruleCCNoptions, /* ptr to rule options */
   &ruleCCNeval, /* ptr to rule detection function */
   0, /* am I initialized yet? */
   0, /* number of options */
   0  /* don't alert */
};

int luhn_check (unsigned long long ccn) {
    unsigned int sum = 0, i;
    int odd = 0;
    unsigned long long ccn_saved = ccn;

    while (ccn != 0) {
        switch (odd) {
            case 0:
                sum += ccn % 10;
                odd = 1;
            break;
            
            case 1:
                i = (ccn % 10) * 2;
                if (i > 9)
                    sum += i - 9;
                else 
                    sum += i;
                odd = 0;
            break;
        }
        ccn /= 10;
    }

    if ((sum % 10) == 0) {
        /*printf("Checking %llu: OK\n", ccn_saved);*/
        return 1;
    }
    
    return 0;
}

int prefix_length_check(unsigned long prefix, unsigned int len) {
    if ((prefix >= 3000) && (prefix <= 3059)) {
        // Issuer should be: 'Diners Club';
        if (len == 14) return 1;
    } else if ((prefix >= 3600) && (prefix <= 3699)) {
        // Issuer should be: 'Diners Club';
        if (len == 14) return 1;
    } else if ((prefix >= 3800) && (prefix <= 3889)) {
        // Issuer should be: 'Diners Club';
        if (len == 14) return 1;
    }

    else if ((prefix >= 3400) && (prefix <= 3499)) {
        // Issuer should be: 'American Express';
        if (len == 15) return 1;
    } else if ((prefix >= 3700) && (prefix <= 3799)) {
        // Issuer should be: 'American Express';
        if (len == 15) return 1;
    }

    else if ((prefix >= 3088) && (prefix <= 3094)) {
        // Issuer should be: 'JCB';
        if (len == 16) return 1;
    } else if ((prefix >= 3096) && (prefix <= 3102)) {
        // Issuer should be: 'JCB';
        if (len == 16) return 1;
    } else if ((prefix >= 3112) && (prefix <= 3120)) {
        // Issuer should be: 'JCB';
        if (len == 16) return 1;
    } else if ((prefix >= 3158) && (prefix <= 3159)) {
        // Issuer should be: 'JCB';
        if (len == 16) return 1;
    } else if ((prefix >= 3337) && (prefix <= 3349)) {
        // Issuer should be: 'JCB';
        if (len == 16) return 1;
    } else if ((prefix >= 3528) && (prefix <= 3589)) {
        // Issuer should be: 'JCB';
        if (len == 16) return 1;
    }

    else if ((prefix >= 3890) && (prefix <= 3899)) {
        // Issuer should be: 'Carte Blanche';
        if (len == 14) return 1;
    }

    else if ((prefix >= 4000) && (prefix <= 4999)) {
        // Issuer should be: 'Visa';
        if (len == 16) return 1;
        else if (len == 13) return 1;
    }
    
    else if ((prefix >= 5100) && (prefix <= 5599)) {
        // Issuer should be: 'MasterCard';
        if (len == 16) return 1;
    }

    else if (prefix == 5610) {
        // Issuer should be: 'Australian BankCard';
        if (len == 16) return 1;
    }
    
    else if (prefix == 6011) {
        // Issuer should be: 'Discover/Novus';
        if (len == 16) return 1;
    }

    return 0;
}

#define DO_CHECKS(prefix, ccn, len) \
if ((len >= MIN_CCN) && (len <= MAX_CCN)) \
    if (luhn_check(ccn) == 1) \
        if (prefix_length_check(prefix, len) == 1) \
            return 1;

int ccn_detect (unsigned char *data, unsigned int length) {
    unsigned int i, len = 0;
    unsigned long long ccn = 0;
    unsigned long prefix = 0;

    if (NULL == data) {
        return 0;
    }

    /* request must be bigger than MIN_CCN */
    if (length <= MIN_CCN) {
        return 0;
    }

    for (i = 0; i < length; i++) {
        if (('0' <= data[i]) && (data[i] <= '9')) {
            /* reset on too long strings */
            if (len == MAX_CCN) {
                ccn = 0;
                len = 0;
            }
            /* add the number to our ccn */
            ccn *= 10;
            ccn += data[i] - '0';
            len++;
            /* save the prefix */
            if (len == 4)
                prefix = ccn;
        } else {
            /* non-numeric char, check the ccn */
            DO_CHECKS(prefix, ccn, len)
            ccn = 0;
            len = 0;
        }
    }
    
    /* run check if the packet ended */
    DO_CHECKS(prefix, ccn, len)

    return 0;
}

/* detection functions */
int ruleCCNeval(void *p) {
    SFSnortPacket *sp = (SFSnortPacket *) p;
 
    // return RULE_NOMATCH;
    if (NULL == sp)
        return RULE_NOMATCH;

    if (ccn_detect(sp->payload, sp->payload_size) == 1) {
        return RULE_MATCH;
    }

    return RULE_NOMATCH;
}
