1 /*
2  *  Simple library to detect and validate SSN and Credit Card numbers.
3  *
4  *  Copyright (C) 2013-2022 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
5  *  Copyright (C) 2007-2013 Sourcefire, Inc.
6  *
7  *  Authors: Martin Roesch <roesch@sourcefire.com>
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License version 2 as
11  *  published by the Free Software Foundation.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21  *  MA 02110-1301, USA.
22  */
23 
24 #if HAVE_CONFIG_H
25 #include "clamav-config.h"
26 #endif
27 
28 #include <stdio.h>
29 #include <ctype.h>
30 #include <string.h>
31 #include <stdarg.h>
32 #include <stdlib.h>
33 #include "dlp.h"
34 #include "others.h"
35 #include "str.h"
36 
37 /* detection mode macros for the contains_* functions */
38 #define DETECT_MODE_DETECT 0
39 #define DETECT_MODE_COUNT 1
40 
41 #define IIN_SIZE 6
42 #define MAX_CC_BREAKS 8
43 
44 /* group number mapping is here */
45 /* http://www.socialsecurity.gov/employer/highgroup.txt */
46 /* here's a perl script to convert the raw data from the highgroup.txt
47  * file to the data set in ssn_max_group[]:
48 --
49 local $/;
50 my $i = <>;
51 my $count = 0;
52 while ($i =~ s/(\d{3}) (\d{2})//) {
53     print int($2) .", ";
54     if ($count == 18)
55     {
56         print "\n";
57         $count = 0;
58     }
59     else
60     {
61         $count++;
62     }
63  }
64  --
65   *
66   * run 'perl convert.pl < highgroup.txt' to generate the data
67   *
68   */
69 
70 /* MAX_AREA is the maximum assigned area number.  This can be derived from
71  * the data in the highgroup.txt file by looking at the last area->group
72  * mapping from that file.
73  */
74 #define MAX_AREA 772
75 
76 /* array of max group numbers for a given area number */
77 /*
78 static int ssn_max_group[MAX_AREA+1] = { 0,
79     6, 6, 4, 8, 8, 8, 6, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,
80     90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 88, 88, 88, 88, 72, 72, 72, 72,
81     70, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 96, 96, 96, 96, 96, 96, 96, 96,
82     96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
83     96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
84     96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96,
85     96, 96, 96, 96, 96, 96, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94,
86     94, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 17, 17, 17, 17, 17, 17,
87     17, 17, 17, 17, 17, 17, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
88     84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 82, 82, 82, 82, 82, 82, 82, 82,
89     82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82, 82,
90     82, 82, 79, 79, 79, 79, 79, 79, 79, 79, 77, 6, 4, 99, 99, 99, 99, 99, 99,
91     99, 99, 99, 53, 53, 53, 53, 53, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
92     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
93     99, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
94     13, 13, 13, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 33, 33,
95     31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 6, 6, 6, 6, 6, 6,
96     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
97     6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 4, 4, 4, 4, 4,
98     35, 35, 35, 35, 35, 35, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33,
99     33, 33, 33, 33, 33, 33, 29, 29, 29, 29, 29, 29, 29, 29, 27, 27, 27, 27, 27,
100     67, 67, 67, 67, 67, 67, 67, 67, 99, 99, 99, 99, 99, 99, 99, 99, 63, 61, 61,
101     61, 61, 61, 61, 61, 61, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
102     99, 99, 23, 23, 23, 23, 23, 23, 23, 21, 21, 99, 99, 99, 99, 99, 99, 99, 99,
103     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 51, 51, 51, 51, 49, 49, 49, 49,
104     49, 49, 37, 37, 37, 37, 37, 37, 37, 37, 25, 25, 25, 25, 25, 25, 25, 25, 25,
105     25, 25, 25, 23, 23, 23, 33, 33, 41, 39, 53, 51, 51, 51, 27, 27, 27, 27, 27,
106     27, 27, 45, 43, 79, 77, 55, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 63, 63,
107     63, 61, 61, 61, 61, 61, 61, 75, 73, 73, 73, 73, 99, 99, 99, 99, 99, 99, 99,
108     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
109     99, 99, 99, 51, 99, 99, 45, 45, 43, 37, 99, 99, 99, 99, 99, 61, 99, 3, 99,
110     99, 99, 99, 99, 99, 99, 84, 84, 84, 84, 99, 99, 67, 67, 65, 65, 65, 65, 65,
111     65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 11,
112     11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 96,
113     96, 44, 44, 46, 46, 46, 44, 28, 26, 26, 26, 26, 16, 16, 16, 14, 14, 14, 14,
114     36, 34, 34, 34, 34, 34, 34, 34, 34, 14, 14, 12, 12, 90, 14, 14, 14, 14, 12,
115     12, 12, 12, 12, 12, 9, 9, 7, 7, 7, 7, 7, 7, 7, 18, 18, 18, 18, 18,
116     18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
117     28, 18, 18, 10, 14, 10, 10, 10, 10, 10, 9, 9, 3, 1, 5, 5, 5, 5, 5,
118     5, 3, 3, 82, 82, 66, 66, 64, 64, 64, 64, 64
119 };
120 */
121 
122 /*
123   Following is a table of payment card "issuer identification number" ranges
124   and additional info such as card number length.
125 */
126 
127 struct iin_map_struct {
128     uint32_t iin_start;
129     uint32_t iin_end;
130     uint8_t card_min;
131     uint8_t card_max;
132     uint8_t is_cc;
133     uint8_t luhn;
134     const char *iin_name;
135 };
136 
137 static struct iin_map_struct iin_map[] = {
138     {100000, 199999, 13, 15, 0, 1, "UATP"},
139     {222100, 272099, 16, 16, 1, 1, "Mastercard 2016"},
140     {300000, 305999, 14, 16, 1, 1, "Diner's Club - Carte Blanche"},
141     {309500, 309599, 14, 16, 1, 1, "Diner's Club International"},
142     {340000, 349999, 15, 15, 1, 1, "American Express"},
143     {352800, 358999, 16, 16, 1, 1, "JCB"},
144     {360000, 369999, 14, 16, 1, 1, "Diner's Club International"},
145     {370000, 379999, 15, 15, 1, 1, "American Express"},
146     {380000, 399999, 16, 16, 1, 1, "Diner's Club International"},
147     {400000, 499999, 16, 16, 1, 1, "Visa"},
148     {500000, 509999, 16, 16, 0, 1, "Maestro"},
149     {510000, 559999, 16, 16, 1, 1, "Master Card"},
150     {601100, 601199, 16, 16, 1, 1, "Discover"},
151     {622126, 622926, 16, 16, 1, 1, "China Union Pay"},
152     {624000, 626999, 16, 16, 1, 1, "China Union Pay"},
153     {628200, 628899, 16, 16, 1, 1, "China Union Pay"},
154     {644000, 659999, 16, 16, 1, 1, "Discover 2009"},
155     {0}};
156 
get_iin(char * digits,int cc_only)157 static const struct iin_map_struct *get_iin(char *digits, int cc_only)
158 {
159     uint32_t iin = atoi(digits);
160     int i        = 0;
161 
162     while (iin_map[i].iin_start != 0) {
163         if (iin < iin_map[i].iin_start)
164             break;
165         if (iin <= iin_map[i].iin_end && (cc_only == 0 || iin_map[i].is_cc == 1)) {
166             cli_dbgmsg("Credit card IIN %s matched range for %s\n", digits, iin_map[i].iin_name);
167             return &iin_map[i];
168         }
169         i++;
170     }
171     cli_dbgmsg("Credit card %s did not match an IIN range\n", digits);
172     return NULL;
173 }
174 
dlp_is_valid_cc(const unsigned char * buffer,size_t length,int cc_only)175 int dlp_is_valid_cc(const unsigned char *buffer, size_t length, int cc_only)
176 {
177     int mult      = 0;
178     int sum       = 0;
179     size_t i      = 0;
180     ssize_t j     = 0;
181     int val       = 0;
182     size_t digits = 0;
183     char cc_digits[20];
184     size_t pad_allowance = MAX_CC_BREAKS;
185     const struct iin_map_struct *iin;
186 
187     if (buffer == NULL || length < 13)
188         return 0;
189     /* if the first digit is greater than 6 it isn't one of the major
190      * credit cards
191      * reference => http://www.beachnet.com/~hstiles/cardtype.html
192      */
193     if (!isdigit(buffer[0]) || buffer[0] > '6' || buffer[0] == 2)
194         return 0;
195 
196     if (length > 19 + pad_allowance) /* max credit card length is 19, with allowance for punctuation */
197         length = 19 + pad_allowance;
198 
199     /* Look for possible 6 digit IIN */
200     for (i = 0; i < length && digits < IIN_SIZE; i++) {
201         if (isdigit(buffer[i]) == 0) {
202             if (buffer[i] == ' ' || buffer[i] == '-')
203                 if (pad_allowance-- > 0)
204                     continue;
205             break;
206         }
207         cc_digits[digits] = buffer[i];
208         digits++;
209     }
210 
211     if (digits == IIN_SIZE)
212         cc_digits[digits] = 0;
213     else
214         return 0;
215 
216     /* See if it is a valid IIN. */
217     iin = get_iin(cc_digits, cc_only);
218     if (iin == NULL)
219         return 0;
220 
221     /* Look for the remaining needed digits. */
222     for (/*same 'i' from previous for-loop*/; i < length && digits < iin->card_max; i++) {
223         if (isdigit(buffer[i]) == 0) {
224             if (buffer[i] == ' ' || buffer[i] == '-')
225                 if (pad_allowance-- > 0)
226                     continue;
227             break;
228         }
229         cc_digits[digits] = buffer[i];
230         digits++;
231     }
232 
233     if (digits < iin->card_min || (i < length && isdigit(buffer[i])))
234         return 0;
235 
236     j = (ssize_t)i;
237     //figure out luhn digits
238     for (j = digits - 1; j >= 0; j--) {
239         val = cc_digits[j] - '0';
240         if (mult) {
241             if ((val *= 2) > 9) val -= 9;
242         }
243         mult = !mult;
244         sum += val;
245     }
246 
247     if (sum % 10)
248         return 0;
249 
250     cli_dbgmsg("Luhn algorithm successful for %s\n", cc_digits);
251 
252     return 1;
253 }
254 
contains_cc(const unsigned char * buffer,size_t length,int detmode,int cc_only)255 static int contains_cc(const unsigned char *buffer, size_t length, int detmode, int cc_only)
256 {
257     const unsigned char *idx;
258     const unsigned char *end;
259     int count = 0;
260 
261     if (buffer == NULL || length < 13) {
262         return 0;
263     }
264 
265     end = buffer + length;
266     idx = buffer;
267 
268     while (idx < end) {
269         if (isdigit(*idx)) {
270             if ((idx == buffer || !isdigit(idx[-1])) && dlp_is_valid_cc(idx, length - (idx - buffer), cc_only) == 1) {
271                 if (detmode == DETECT_MODE_DETECT)
272                     return 1;
273                 else {
274                     count++;
275                     /* if we got a valid match we should increment the idx ptr
276                      * to gain a little performance
277                      */
278                     idx += (length > 15 ? 15 : (length - 1));
279                 }
280             }
281         }
282         idx++;
283     }
284 
285     return count;
286 }
287 
dlp_get_cc_count(const unsigned char * buffer,size_t length,int cc_only)288 int dlp_get_cc_count(const unsigned char *buffer, size_t length, int cc_only)
289 {
290     return contains_cc(buffer, length, DETECT_MODE_COUNT, cc_only);
291 }
292 
dlp_has_cc(const unsigned char * buffer,size_t length,int cc_only)293 int dlp_has_cc(const unsigned char *buffer, size_t length, int cc_only)
294 {
295     return contains_cc(buffer, length, DETECT_MODE_DETECT, cc_only);
296 }
297 
dlp_is_valid_ssn(const unsigned char * buffer,size_t length,int format)298 int dlp_is_valid_ssn(const unsigned char *buffer, size_t length, int format)
299 {
300     int area_number;
301     int group_number;
302     int serial_number;
303     size_t minlength;
304     int retval = 1;
305     char numbuf[12];
306 
307     if (buffer == NULL)
308         return 0;
309 
310     minlength = (format == SSN_FORMAT_HYPHENS ? 11 : 9);
311 
312     if (length < minlength)
313         return 0;
314 
315     if ((length > minlength) && isdigit(buffer[minlength]))
316         return 0;
317 
318     strncpy(numbuf, (const char *)buffer, minlength);
319     numbuf[minlength] = 0;
320 
321     /* sscanf parses and (basically) validates the string for us */
322     switch (format) {
323         case SSN_FORMAT_HYPHENS:
324             if (numbuf[3] != '-' || numbuf[6] != '-')
325                 return 0;
326 
327             if (sscanf((const char *)numbuf,
328                        "%3d-%2d-%4d",
329                        &area_number,
330                        &group_number,
331                        &serial_number) != 3) {
332                 return 0;
333             }
334             break;
335         case SSN_FORMAT_STRIPPED:
336             if (!cli_isnumber(numbuf))
337                 return 0;
338 
339             if (sscanf((const char *)numbuf,
340                        "%3d%2d%4d",
341                        &area_number,
342                        &group_number,
343                        &serial_number) != 3) {
344                 return 0;
345             }
346             break;
347         default:
348             cli_dbgmsg("dlp_is_valid_ssn: unknown format type %d \n", format);
349             return 0;
350     }
351 
352     /* start validating */
353     /* validation data taken from
354      * http://en.wikipedia.org/wiki/Social_Security_number_%28United_States%29
355      */
356     if (area_number > MAX_AREA ||
357         area_number == 666 ||
358         area_number <= 0 ||
359         group_number <= 0 ||
360         group_number > 99 ||
361         serial_number <= 0 ||
362         serial_number > 9999)
363         retval = 0;
364 
365     if (area_number == 987 && group_number == 65) {
366         if (serial_number >= 4320 && serial_number <= 4329)
367             retval = 0;
368     }
369 
370     /*
371     if(group_number > ssn_max_group[area_number])
372         retval = 0;
373     */
374     if (retval)
375         cli_dbgmsg("dlp_is_valid_ssn: SSN_%s: %s\n", format == SSN_FORMAT_HYPHENS ? "HYPHENS" : "STRIPPED", numbuf);
376 
377     return retval;
378 }
379 
contains_ssn(const unsigned char * buffer,size_t length,int format,int detmode)380 static int contains_ssn(const unsigned char *buffer, size_t length, int format, int detmode)
381 {
382     const unsigned char *idx;
383     const unsigned char *end;
384     int count = 0;
385 
386     if (buffer == NULL || length < 9)
387         return 0;
388 
389     end = buffer + length;
390     idx = buffer;
391     while (idx < end) {
392         if (isdigit(*idx)) {
393             /* check for area number and the first hyphen */
394             if ((idx == buffer || !isdigit(idx[-1])) && dlp_is_valid_ssn(idx, length - (idx - buffer), format) == 1) {
395                 if (detmode == DETECT_MODE_COUNT) {
396                     count++;
397                     /* hop over the matched bytes if we found an SSN */
398                     idx += ((format == SSN_FORMAT_HYPHENS) ? 11 : 9);
399                 } else {
400                     return 1;
401                 }
402             }
403         }
404         idx++;
405     }
406 
407     return count;
408 }
409 
dlp_get_stripped_ssn_count(const unsigned char * buffer,size_t length)410 int dlp_get_stripped_ssn_count(const unsigned char *buffer, size_t length)
411 {
412     return contains_ssn(buffer,
413                         length,
414                         SSN_FORMAT_STRIPPED,
415                         DETECT_MODE_COUNT);
416 }
417 
dlp_get_normal_ssn_count(const unsigned char * buffer,size_t length)418 int dlp_get_normal_ssn_count(const unsigned char *buffer, size_t length)
419 {
420     return contains_ssn(buffer,
421                         length,
422                         SSN_FORMAT_HYPHENS,
423                         DETECT_MODE_COUNT);
424 }
425 
dlp_get_ssn_count(const unsigned char * buffer,size_t length)426 int dlp_get_ssn_count(const unsigned char *buffer, size_t length)
427 {
428     /* this will suck for performance but will find SSNs in either
429      * format
430      */
431     return (dlp_get_stripped_ssn_count(buffer, length) + dlp_get_normal_ssn_count(buffer, length));
432 }
433 
dlp_has_ssn(const unsigned char * buffer,size_t length)434 int dlp_has_ssn(const unsigned char *buffer, size_t length)
435 {
436     return (contains_ssn(buffer,
437                          length,
438                          SSN_FORMAT_HYPHENS,
439                          DETECT_MODE_DETECT) |
440             contains_ssn(buffer,
441                          length,
442                          SSN_FORMAT_STRIPPED,
443                          DETECT_MODE_DETECT));
444 }
445 
dlp_has_stripped_ssn(const unsigned char * buffer,size_t length)446 int dlp_has_stripped_ssn(const unsigned char *buffer, size_t length)
447 {
448     return contains_ssn(buffer,
449                         length,
450                         SSN_FORMAT_STRIPPED,
451                         DETECT_MODE_DETECT);
452 }
453 
dlp_has_normal_ssn(const unsigned char * buffer,size_t length)454 int dlp_has_normal_ssn(const unsigned char *buffer, size_t length)
455 {
456     return contains_ssn(buffer,
457                         length,
458                         SSN_FORMAT_HYPHENS,
459                         DETECT_MODE_DETECT);
460 }
461 
462 /*  The program below checks for the instances of where a   */
463 /*  Canadian Bank Routing Number or EFT is found, or if a   */
464 /*  U.S. MICR Bank Routing Number is encountered.           */
465 
466 /*  Author: Bill Parker                                     */
467 /*  Date:   February 17, 2013                               */
468 /*  Last Modified: February 25, 2013                        */
469 
470 /*  Purpose: To provide Snort and ClamAV the ability to     */
471 /*  detect canadian and U.S. bank routing transaction       */
472 /*  numbers via the DLP module in ClamAV or the SDF pre-    */
473 /*  processor in the Snort IDS.                             */
474 
475 /*  Are first three or last three digits a valid bank code  */
is_bank_code_valid(int bank_code)476 int is_bank_code_valid(int bank_code)
477 {
478     switch (bank_code) {
479         case 1:
480             return 1; /*  Bank of Montreal    */
481         case 2:
482             return 1; /*  Bank of Nova Scotia */
483         case 3:
484             return 1; /*  Royal Bank of Canada    */
485         case 4:
486             return 1; /*  Toronto-Dominion Bank   */
487         case 6:
488             return 1; /*  National Bank of Canada */
489         case 10:
490             return 1; /*  Canadian Imperial Bank of Commerce  */
491         case 16:
492             return 1; /*  HSBC Canada */
493         case 30:
494             return 1; /*  Canadian Western Bank   */
495         case 39:
496             return 1; /*  Laurentian Bank of Canada   */
497         case 117:
498             return 1; /*  Government of Canada    */
499         case 127:
500             return 1; /*  Canada Post (Money Orders)  */
501         case 177:
502             return 1; /*  Bank of Canada  */
503         case 219:
504             return 1; /*  ATB Financial   */
505         case 260:
506             return 1; /*  Citibank Canada */
507         case 290:
508             return 1; /*  UBS Bank (Canada)   */
509         case 308:
510             return 1; /*  Bank of China (Canada)  */
511         case 309:
512             return 1; /*  Citizens Bank of Canada */
513         case 326:
514             return 1; /*  President’s Choice Financial    */
515         case 338:
516             return 1; /*  Canadian Tire Bank  */
517         case 340:
518             return 1; /*  ICICI Bank Canada   */
519         case 509:
520             return 1; /*  Canada Trust    */
521         case 540:
522             return 1; /*  Manulife Bank   */
523         case 614:
524             return 1; /*  ING Direct Canada   */
525         case 809:
526             return 1; /*  Central 1 [Credit Union] – BC Region    */
527         case 815:
528             return 1; /*  Caisses Desjardins du Québec    */
529         case 819:
530             return 1; /*  Caisses populaires Desjardins du Manitoba   */
531         case 828:
532             return 1; /*  Central 1 [Credit Union] – ON Region    */
533         case 829:
534             return 1; /*  Caisses populaires Desjardins de l’Ontario  */
535         case 837:
536             return 1; /*  Meridian Credit Union   */
537         case 839:
538             return 1; /*  Credit Union Heritage (Nova Scotia) */
539         case 865:
540             return 1; /*  Caisses populaires Desjardins acadiennes */
541         case 879:
542             return 1; /*  Credit Union Central of Manitoba    */
543         case 889:
544             return 1; /*  Credit Union Central of Saskatchewan    */
545         case 899:
546             return 1; /*  Credit Union Central Alberta    */
547         case 900:
548             return 1; /*  Unknown???  */
549         default:
550             return 0; /*  NO MATCH...FAIL */
551     }                 /*  end if switch(bank_code)    */
552 
553     return 0;
554 } /*  end function is_bank_code_valid()   */
555 
556 /*  This function checks if the supplied string is a valid  */
557 /*  canadian transit number, the format is as follows:      */
558 
559 /*  XXXXX-YYY where XXXXX is a branch number, and YYY is    */
560 /*  the institutional number.                               */
561 
562 /*  note: it does NOT appear that the canadian RTN or EFT   */
563 /*  number formats contain any type of checksum algorithm   */
564 /*  or a check digit.                                       */
cdn_ctn_is_valid(const char * buffer,size_t length)565 int cdn_ctn_is_valid(const char *buffer, size_t length)
566 {
567     int i;
568     int bank_code = 0; /*  last three digits of Canada RTN/MICR is Bank I.D.   */
569 
570     if (buffer == NULL || length < 9) /* if the buffer is empty or  */
571         return 0;                     /* the length is less than 9, it's not valid    */
572 
573     if (buffer[5] != '-') return 0; /* if the 6th char isn't a '-', not a valid RTN */
574 
575     for (i = 0; i < 5; i++)
576         if (isdigit(buffer[i]) == 0)
577             return 0;
578 
579     /*  Check the various branch codes which are listed, but there  */
580     /*  may be more valid codes which could be added as well...     */
581 
582     /*  convert last three elements in buffer to a numeric value    */
583 
584     for (i = 6; i < 9; i++) {
585         if (isdigit(buffer[i]) == 0)
586             return 0;
587         bank_code = (bank_code * 10) + (buffer[i] - '0');
588     }
589 
590     /* now have a switch sandwich for bank codes    */
591     return (is_bank_code_valid(bank_code)); /*  return 1 if valid, 0 if not */
592 }
593 
594 /*  If the string is a canadian EFT (Electronic Fund        */
595 /*  Transaction), the format is as follows:                 */
596 
597 /*  0YYYXXXX, where a leading zero is required, XXXXX is a  */
598 /*  branch number, and YYY is the institution number.       */
599 
600 /*  note: it does NOT appear that the canadian RTN or EFT   */
601 /*  number formats contain any type of checksum algorithm   */
602 /*  or a check digit.                                       */
603 
cdn_eft_is_valid(const char * buffer,size_t length)604 int cdn_eft_is_valid(const char *buffer, size_t length)
605 {
606     int bank_code = 0;
607     int i;
608 
609     if (buffer == NULL || length < 9) /* if the buffer is empty or  */
610         return 0;                     /* the length is less than 9, it's not valid    */
611 
612     if (buffer[0] != '0') return 0; /* if the 1st char isn't a '0', not a valid EFT */
613 
614     for (i = 1; i < 4; i++) {
615         if (isdigit(buffer[i]) == 0)
616             return 0;
617         bank_code = (bank_code * 10) + (buffer[i] - '0');
618     }
619 
620     /*  Check the various branch codes which are listed, but there  */
621     /*  may be more valid codes which could be added as well...     */
622     if (!is_bank_code_valid(bank_code))
623         return 0;
624 
625     for (i = 4; i < 9; i++)
626         if (isdigit(buffer[i]) == 0)
627             return 0;
628 
629     return 1;
630 }
631 
us_micr_is_valid(const char * buffer,size_t length)632 int us_micr_is_valid(const char *buffer, size_t length)
633 {
634     int result, sum, sum1, sum2, sum3;
635     int i;
636     unsigned char micr_digits[9];
637 
638     if (buffer == NULL || length < 9) /* if the buffer is empty or    */
639         return 0;                     /* the length is < 9, it's not valid    */
640 
641     /* loop and make sure all the characters are actually digits    */
642 
643     for (i = 0; i < 9; i++) {
644         if (isdigit(buffer[i]) == 0)
645             return 0;
646         micr_digits[i] = buffer[i];
647     }
648 
649     /*  see if we have a valid MICR number via the following formula */
650 
651     /*  7 * (micr_digits[0] + micr_digits[3] + micr_digits[6]) +    */
652     /*  3 * (micr_digits[1] + micr_digits[4] + micr_digits[7]) +    */
653     /*  9 * (micr_digits[2] + micr_digits[5]) (the check digit is   */
654     /*  computed by the sum above modulus 10                        */
655 
656     sum1   = 7 * ((micr_digits[0] - '0') + (micr_digits[3] - '0') + (micr_digits[6] - '0'));
657     sum2   = 3 * ((micr_digits[1] - '0') + (micr_digits[4] - '0') + (micr_digits[7] - '0'));
658     sum3   = 9 * ((micr_digits[2] - '0') + (micr_digits[5] - '0'));
659     sum    = sum1 + sum2 + sum3;
660     result = sum % 10;
661 
662     if (result == (micr_digits[8] - '0'))
663         return 1; /* last digit of MICR matches result    */
664     return 0;     /* MICR number isn't valid  */
665 }
666