]> eyrie.org Git - kerberos/krb5-strength.git/blob - cracklib/rules.c
9046ac86b4d2c762b6ae707b5bd9247c1dab8766
[kerberos/krb5-strength.git] / cracklib / rules.c
1 /*
2  * This program is copyright Alec Muffett 1993. The author disclaims all 
3  * responsibility or liability with respect to it's usage or its effect 
4  * upon hardware or computer systems, and maintains copyright as set out 
5  * in the "LICENCE" document which accompanies distributions of Crack v4.0 
6  * and upwards.
7  */
8
9 /*
10  * Modified as part of the krb5-strength project as follows:
11  *
12  * 2016-08-17  Howard Guo <hguo@suse.com>
13  *   - Double the length of buffers in Mangle to provide enough space to
14  *     handle duplicating rules.
15  * 2007-03-22  Russ Allbery <eagle@eyrie.org>
16  *   - Cap deletion of leading or trailing characters at one more than half
17  *     the length of the password string and no more than five characters.
18  *     This goes with a change to fascist.c that adds rules to delete more
19  *     leading and trailing characters for longer passwords.
20  *   - Additional system includes for other functions.
21  * 2009-10-14  Russ Allbery <eagle@eyrie.org>
22  *   - Simplify Debug() function for how it's actually called.
23  *   - Add ANSI C protototypes for all functions.
24  *   - Tweaks for const cleanliness.
25  *   - Make internal functions static.
26  *   - Remove unused variables.
27  *   - Changed a variable to unsigned to avoid gcc warnings.
28  * 2016-11-06  Russ Allbery <eagle@eyrie.org>
29  *   - Remove unused vers_id to silence GCC warnings.
30  *   - Added GCC __attribute__ marker on Debug() function.
31  * 2020-05-16  Russ Allbery <eagle@eyrie.org>
32  *   - Change variables from int to size_t to silence warnings.
33  *   - Add missing break to RULE_MFIRST and RULE_MLAST handling.
34  */
35
36 #include <stdarg.h>
37
38 #ifndef IN_CRACKLIB
39
40 #include "crack.h"
41
42 #else
43
44 #include "packer.h"
45
46 static void __attribute__((__format__(printf, 2, 3)))
47 Debug(int val, const char *fmt, ...)
48 {
49     if (val < 2) {
50         va_list args;
51
52         va_start(args, fmt);
53         vfprintf(stderr, fmt, args);
54         va_end(args);
55     }
56 }
57
58 #endif
59
60 #include <string.h>
61
62 #define RULE_NOOP       ':'
63 #define RULE_PREPEND    '^'
64 #define RULE_APPEND     '$'
65 #define RULE_REVERSE    'r'
66 #define RULE_UPPERCASE  'u'
67 #define RULE_LOWERCASE  'l'
68 #define RULE_PLURALISE  'p'
69 #define RULE_CAPITALISE 'c'
70 #define RULE_DUPLICATE  'd'
71 #define RULE_REFLECT    'f'
72 #define RULE_SUBSTITUTE 's'
73 #define RULE_MATCH      '/'
74 #define RULE_NOT        '!'
75 #define RULE_LT         '<'
76 #define RULE_GT         '>'
77 #define RULE_EXTRACT    'x'
78 #define RULE_OVERSTRIKE 'o'
79 #define RULE_INSERT     'i'
80 #define RULE_EQUALS     '='
81 #define RULE_PURGE      '@'
82 #define RULE_CLASS      '?'     /* class rule? socialist ethic in cracker? */
83
84 #define RULE_DFIRST     '['
85 #define RULE_DLAST      ']'
86 #define RULE_MFIRST     '('
87 #define RULE_MLAST      ')'
88
89 static int
90 Suffix(const char *myword, const char *suffix)
91 {
92     register size_t i;
93     register size_t j;
94     i = strlen(myword);
95     j = strlen(suffix);
96
97     if (i > j)
98     {
99         return (STRCMP((myword + i - j), suffix));
100     } else
101     {
102         return (-1);
103     }
104 }
105
106 /* return a pointer to a reversal */
107 char *
108 Reverse(const char *str)
109 {
110     register size_t i;
111     register size_t j;
112     static char area[STRINGSIZE];
113     j = i = strlen(str);
114     while (*str)
115     {
116         area[--i] = *str++;
117     }
118     area[j] = '\0';
119     return (area);
120 }
121
122 /* return a pointer to an uppercase */
123 static char *
124 Uppercase(const char *str)
125 {
126     register char *ptr;
127     static char area[STRINGSIZE];
128     ptr = area;
129     while (*str)
130     {
131         *(ptr++) = CRACK_TOUPPER(*str);
132         str++;
133     }
134     *ptr = '\0';
135
136     return (area);
137 }
138
139 /* return a pointer to an lowercase */
140 char *
141 Lowercase(const char *str)
142 {
143     register char *ptr;
144     static char area[STRINGSIZE];
145     ptr = area;
146     while (*str)
147     {
148         *(ptr++) = CRACK_TOLOWER(*str);
149         str++;
150     }
151     *ptr = '\0';
152
153     return (area);
154 }
155
156 /* return a pointer to an capitalised */
157 static char *
158 Capitalise(const char *str)
159 {
160     register char *ptr;
161     static char area[STRINGSIZE];
162     ptr = area;
163
164     while (*str)
165     {
166         *(ptr++) = CRACK_TOLOWER(*str);
167         str++;
168     }
169
170     *ptr = '\0';
171     area[0] = CRACK_TOUPPER(area[0]);
172     return (area);
173 }
174
175 /* returns a pointer to a plural */
176 static char *
177 Pluralise(const char *string)
178 {
179     register size_t length;
180     static char area[STRINGSIZE];
181     length = strlen(string);
182     strcpy(area, string);
183
184     if (!Suffix(string, "ch") ||
185         !Suffix(string, "ex") ||
186         !Suffix(string, "ix") ||
187         !Suffix(string, "sh") ||
188         !Suffix(string, "ss"))
189     {
190         /* bench -> benches */
191         strcat(area, "es");
192     } else if (length > 2 && string[length - 1] == 'y')
193     {
194         if (strchr("aeiou", string[length - 2]))
195         {
196             /* alloy -> alloys */
197             strcat(area, "s");
198         } else
199         {
200             /* gully -> gullies */
201             strcpy(area + length - 1, "ies");
202         }
203     } else if (string[length - 1] == 's')
204     {
205         /* bias -> biases */
206         strcat(area, "es");
207     } else
208     {
209         /* catchall */
210         strcat(area, "s");
211     }
212
213     return (area);
214 }
215
216 /* returns pointer to a swapped about copy */
217 static char *
218 Substitute(const char *string, char old, char new)
219 {
220     register char *ptr;
221     static char area[STRINGSIZE];
222     ptr = area;
223     while (*string)
224     {
225         *(ptr++) = (*string == old ? new : *string);
226         string++;
227     }
228     *ptr = '\0';
229     return (area);
230 }
231
232 /* returns pointer to a purged copy */
233 static char *
234 Purge(const char *string, char target)
235 {
236     register char *ptr;
237     static char area[STRINGSIZE];
238     ptr = area;
239     while (*string)
240     {
241         if (*string != target)
242         {
243             *(ptr++) = *string;
244         }
245         string++;
246     }
247     *ptr = '\0';
248     return (area);
249 }
250 /* -------- CHARACTER CLASSES START HERE -------- */
251
252 /*
253  * this function takes two inputs, a class identifier and a character, and
254  * returns non-null if the given character is a member of the class, based
255  * upon restrictions set out below
256  */
257
258 static int
259 MatchClass(char class, char input)
260 {
261     register char c;
262     register int retval;
263     retval = 0;
264
265     switch (class)
266     {
267         /* ESCAPE */
268
269     case '?':                   /* ?? -> ? */
270         if (input == '?')
271         {
272             retval = 1;
273         }
274         break;
275
276         /* ILLOGICAL GROUPINGS (ie: not in ctype.h) */
277
278     case 'V':
279     case 'v':                   /* vowels */
280         c = CRACK_TOLOWER(input);
281         if (strchr("aeiou", c))
282         {
283             retval = 1;
284         }
285         break;
286
287     case 'C':
288     case 'c':                   /* consonants */
289         c = CRACK_TOLOWER(input);
290         if (strchr("bcdfghjklmnpqrstvwxyz", c))
291         {
292             retval = 1;
293         }
294         break;
295
296     case 'W':
297     case 'w':                   /* whitespace */
298         if (strchr("\t ", input))
299         {
300             retval = 1;
301         }
302         break;
303
304     case 'P':
305     case 'p':                   /* punctuation */
306         if (strchr(".`,:;'!?\"", input))
307         {
308             retval = 1;
309         }
310         break;
311
312     case 'S':
313     case 's':                   /* symbols */
314         if (strchr("$%%^&*()-_+=|\\[]{}#@/~", input))
315         {
316             retval = 1;
317         }
318         break;
319
320         /* LOGICAL GROUPINGS */
321
322     case 'L':
323     case 'l':                   /* lowercase */
324         if (islower(input))
325         {
326             retval = 1;
327         }
328         break;
329
330     case 'U':
331     case 'u':                   /* uppercase */
332         if (isupper(input))
333         {
334             retval = 1;
335         }
336         break;
337
338     case 'A':
339     case 'a':                   /* alphabetic */
340         if (isalpha(input))
341         {
342             retval = 1;
343         }
344         break;
345
346     case 'X':
347     case 'x':                   /* alphanumeric */
348         if (isalnum(input))
349         {
350             retval = 1;
351         }
352         break;
353
354     case 'D':
355     case 'd':                   /* digits */
356         if (isdigit(input))
357         {
358             retval = 1;
359         }
360         break;
361
362     default:
363         Debug(1, "MatchClass: unknown class %c\n", class);
364         return (0);
365         break;
366     }
367
368     if (isupper(class))
369     {
370         return (!retval);
371     }
372     return (retval);
373 }
374
375 static const char *
376 PolyStrchr(const char *string, char class)
377 {
378     while (*string)
379     {
380         if (MatchClass(class, *string))
381         {
382             return (string);
383         }
384         string++;
385     }
386     return ((char *) 0);
387 }
388
389 /* returns pointer to a swapped about copy */
390 static char *
391 PolySubst(const char *string, char class, char new)
392 {
393     register char *ptr;
394     static char area[STRINGSIZE];
395     ptr = area;
396     while (*string)
397     {
398         *(ptr++) = (MatchClass(class, *string) ? new : *string);
399         string++;
400     }
401     *ptr = '\0';
402     return (area);
403 }
404
405 /* returns pointer to a purged copy */
406 static char *
407 PolyPurge(const char *string, const char class)
408 {
409     register char *ptr;
410     static char area[STRINGSIZE];
411     ptr = area;
412     while (*string)
413     {
414         if (!MatchClass(class, *string))
415         {
416             *(ptr++) = *string;
417         }
418         string++;
419     }
420     *ptr = '\0';
421     return (area);
422 }
423 /* -------- BACK TO NORMALITY -------- */
424
425 static int
426 Char2Int(char character)
427 {
428     if (isdigit(character))
429     {
430         return (character - '0');
431     } else if (islower(character))
432     {
433         return (character - 'a' + 10);
434     } else if (isupper(character))
435     {
436         return (character - 'A' + 10);
437     }
438     return (-1);
439 }
440
441 /* returns a pointer to a controlled Mangle */
442 char *
443 Mangle(const char *input, const char *control)
444 {
445     int limit;
446     size_t min_to_shift;
447     register size_t j;
448     const char *ptr;
449     static char area[STRINGSIZE * 2] = "";
450     char area2[STRINGSIZE * 2] = "";
451     strcpy(area, input);
452
453     j = strlen(input);
454     if (j % 2 == 0)
455     { 
456         min_to_shift = (j + 1) / 2;
457     } else
458     {
459         min_to_shift = j / 2;
460     }
461     min_to_shift++;
462     if (min_to_shift > 5)
463     {
464         min_to_shift = 5;
465     }
466     
467     for (ptr = control; *ptr; ptr++)
468     {
469         switch (*ptr)
470         {
471         case RULE_NOOP:
472             break;
473         case RULE_REVERSE:
474             strcpy(area, Reverse(area));
475             break;
476         case RULE_UPPERCASE:
477             strcpy(area, Uppercase(area));
478             break;
479         case RULE_LOWERCASE:
480             strcpy(area, Lowercase(area));
481             break;
482         case RULE_CAPITALISE:
483             strcpy(area, Capitalise(area));
484             break;
485         case RULE_PLURALISE:
486             strcpy(area, Pluralise(area));
487             break;
488         case RULE_REFLECT:
489             strcat(area, Reverse(area));
490             break;
491         case RULE_DUPLICATE:
492             strcpy(area2, area);
493             strcat(area, area2);
494             break;
495         case RULE_GT:
496             if (!ptr[1])
497             {
498                 Debug(1, "Mangle: '>' missing argument in '%s'\n", control);
499                 return ((char *) 0);
500             } else
501             {
502                 limit = Char2Int(*(++ptr));
503                 if (limit < 0)
504                 {
505                     Debug(1, "Mangle: '>' weird argument in '%s'\n", control);
506                     return ((char *) 0);
507                 }
508                 if (strlen(area) <= (size_t) limit)
509                 {
510                     return ((char *) 0);
511                 }
512             }
513             break;
514         case RULE_LT:
515             if (!ptr[1])
516             {
517                 Debug(1, "Mangle: '<' missing argument in '%s'\n", control);
518                 return ((char *) 0);
519             } else
520             {
521                 limit = Char2Int(*(++ptr));
522                 if (limit < 0)
523                 {
524                     Debug(1, "Mangle: '<' weird argument in '%s'\n", control);
525                     return ((char *) 0);
526                 }
527                 if (strlen(area) >= (size_t) limit)
528                 {
529                     return ((char *) 0);
530                 }
531             }
532             break;
533         case RULE_PREPEND:
534             if (!ptr[1])
535             {
536                 Debug(1, "Mangle: prepend missing argument in '%s'\n", control);
537                 return ((char *) 0);
538             } else
539             {
540                 area2[0] = *(++ptr);
541                 strcpy(area2 + 1, area);
542                 strcpy(area, area2);
543             }
544             break;
545         case RULE_APPEND:
546             if (!ptr[1])
547             {
548                 Debug(1, "Mangle: append missing argument in '%s'\n", control);
549                 return ((char *) 0);
550             } else
551             {
552                 register char *string;
553                 string = area;
554                 while (*(string++));
555                 string[-1] = *(++ptr);
556                 *string = '\0';
557             }
558             break;
559         case RULE_EXTRACT:
560             if (!ptr[1] || !ptr[2])
561             {
562                 Debug(1, "Mangle: extract missing argument in '%s'\n", control);
563                 return ((char *) 0);
564             } else
565             {
566                 register int i;
567                 int start;
568                 int length;
569                 start = Char2Int(*(++ptr));
570                 length = Char2Int(*(++ptr));
571                 if (start < 0 || length < 0)
572                 {
573                     Debug(1, "Mangle: extract: weird argument in '%s'\n", control);
574                     return ((char *) 0);
575                 }
576                 strcpy(area2, area);
577                 for (i = 0; length-- && area2[start + i]; i++)
578                 {
579                     area[i] = area2[start + i];
580                 }
581                 /* cant use strncpy() - no trailing NUL */
582                 area[i] = '\0';
583             }
584             break;
585         case RULE_OVERSTRIKE:
586             if (!ptr[1] || !ptr[2])
587             {
588                 Debug(1, "Mangle: overstrike missing argument in '%s'\n", control);
589                 return ((char *) 0);
590             } else
591             {
592                 register int i;
593                 i = Char2Int(*(++ptr));
594                 if (i < 0)
595                 {
596                     Debug(1, "Mangle: overstrike weird argument in '%s'\n",
597                           control);
598                     return ((char *) 0);
599                 } else
600                 {
601                     ++ptr;
602                     if (area[i])
603                     {
604                         area[i] = *ptr;
605                     }
606                 }
607             }
608             break;
609         case RULE_INSERT:
610             if (!ptr[1] || !ptr[2])
611             {
612                 Debug(1, "Mangle: insert missing argument in '%s'\n", control);
613                 return ((char *) 0);
614             } else
615             {
616                 register int i;
617                 register char *p1;
618                 register char *p2;
619                 i = Char2Int(*(++ptr));
620                 if (i < 0)
621                 {
622                     Debug(1, "Mangle: insert weird argument in '%s'\n",
623                           control);
624                     return ((char *) 0);
625                 }
626                 p1 = area;
627                 p2 = area2;
628                 while (i && *p1)
629                 {
630                     i--;
631                     *(p2++) = *(p1++);
632                 }
633                 *(p2++) = *(++ptr);
634                 strcpy(p2, p1);
635                 strcpy(area, area2);
636             }
637             break;
638             /* THE FOLLOWING RULES REQUIRE CLASS MATCHING */
639
640         case RULE_PURGE:        /* @x or @?c */
641             if (!ptr[1] || (ptr[1] == RULE_CLASS && !ptr[2]))
642             {
643                 Debug(1, "Mangle: delete missing arguments in '%s'\n", control);
644                 return ((char *) 0);
645             } else if (ptr[1] != RULE_CLASS)
646             {
647                 strcpy(area, Purge(area, *(++ptr)));
648             } else
649             {
650                 strcpy(area, PolyPurge(area, ptr[2]));
651                 ptr += 2;
652             }
653             break;
654         case RULE_SUBSTITUTE:   /* sxy || s?cy */
655             if (!ptr[1] || !ptr[2] || (ptr[1] == RULE_CLASS && !ptr[3]))
656             {
657                 Debug(1, "Mangle: subst missing argument in '%s'\n", control);
658                 return ((char *) 0);
659             } else if (ptr[1] != RULE_CLASS)
660             {
661                 strcpy(area, Substitute(area, ptr[1], ptr[2]));
662                 ptr += 2;
663             } else
664             {
665                 strcpy(area, PolySubst(area, ptr[2], ptr[3]));
666                 ptr += 3;
667             }
668             break;
669         case RULE_MATCH:        /* /x || /?c */
670             if (!ptr[1] || (ptr[1] == RULE_CLASS && !ptr[2]))
671             {
672                 Debug(1, "Mangle: '/' missing argument in '%s'\n", control);
673                 return ((char *) 0);
674             } else if (ptr[1] != RULE_CLASS)
675             {
676                 if (!strchr(area, *(++ptr)))
677                 {
678                     return ((char *) 0);
679                 }
680             } else
681             {
682                 if (!PolyStrchr(area, ptr[2]))
683                 {
684                     return ((char *) 0);
685                 }
686                 ptr += 2;
687             }
688             break;
689         case RULE_NOT:          /* !x || !?c */
690             if (!ptr[1] || (ptr[1] == RULE_CLASS && !ptr[2]))
691             {
692                 Debug(1, "Mangle: '!' missing argument in '%s'\n", control);
693                 return ((char *) 0);
694             } else if (ptr[1] != RULE_CLASS)
695             {
696                 if (strchr(area, *(++ptr)))
697                 {
698                     return ((char *) 0);
699                 }
700             } else
701             {
702                 if (PolyStrchr(area, ptr[2]))
703                 {
704                     return ((char *) 0);
705                 }
706                 ptr += 2;
707             }
708             break;
709             /*
710              * alternative use for a boomerang, number 1: a standard throwing
711              * boomerang is an ideal thing to use to tuck the sheets under
712              * the mattress when making your bed.  The streamlined shape of
713              * the boomerang allows it to slip easily 'twixt mattress and
714              * bedframe, and it's curve makes it very easy to hook sheets
715              * into the gap.
716              */
717
718         case RULE_EQUALS:       /* =nx || =n?c */
719             if (!ptr[1] || !ptr[2] || (ptr[2] == RULE_CLASS && !ptr[3]))
720             {
721                 Debug(1, "Mangle: '=' missing argument in '%s'\n", control);
722                 return ((char *) 0);
723             } else
724             {
725                 register int i;
726                 if ((i = Char2Int(ptr[1])) < 0)
727                 {
728                     Debug(1, "Mangle: '=' weird argument in '%s'\n", control);
729                     return ((char *) 0);
730                 }
731                 if (ptr[2] != RULE_CLASS)
732                 {
733                     ptr += 2;
734                     if (area[i] != *ptr)
735                     {
736                         return ((char *) 0);
737                     }
738                 } else
739                 {
740                     ptr += 3;
741                     if (!MatchClass(*ptr, area[i]))
742                     {
743                         return ((char *) 0);
744                     }
745                 }
746             }
747             break;
748
749         case RULE_DFIRST:
750             if (area[0] && strlen(area) > (size_t) min_to_shift)
751             {
752                 register int i;
753                 for (i = 1; area[i]; i++)
754                 {
755                     area[i - 1] = area[i];
756                 }
757                 area[i - 1] = '\0';
758             }
759             break;
760
761         case RULE_DLAST:
762             if (area[0] && strlen(area) > (size_t) min_to_shift)
763             {
764                 register int i;
765                 for (i = 1; area[i]; i++);
766                 area[i - 1] = '\0';
767             }
768             break;
769
770         case RULE_MFIRST:
771             if (!ptr[1] || (ptr[1] == RULE_CLASS && !ptr[2]))
772             {
773                 Debug(1, "Mangle: '(' missing argument in '%s'\n", control);
774                 return ((char *) 0);
775             } else
776             {
777                 if (ptr[1] != RULE_CLASS)
778                 {
779                     ptr++;
780                     if (area[0] != *ptr)
781                     {
782                         return ((char *) 0);
783                     }
784                 } else
785                 {
786                     ptr += 2;
787                     if (!MatchClass(*ptr, area[0]))
788                     {
789                         return ((char *) 0);
790                     }
791                 }
792             }
793             break;
794
795         case RULE_MLAST:
796             if (!ptr[1] || (ptr[1] == RULE_CLASS && !ptr[2]))
797             {
798                 Debug(1, "Mangle: ')' missing argument in '%s'\n", control);
799                 return ((char *) 0);
800             } else
801             {
802                 register int i;
803
804                 for (i = 0; area[i]; i++);
805
806                 if (i > 0)
807                 {
808                     i--;
809                 } else
810                 {
811                     return ((char *) 0);
812                 }
813
814                 if (ptr[1] != RULE_CLASS)
815                 {
816                     ptr++;
817                     if (area[i] != *ptr)
818                     {
819                         return ((char *) 0);
820                     }
821                 } else
822                 {
823                     ptr += 2;
824                     if (!MatchClass(*ptr, area[i]))
825                     {
826                         return ((char *) 0);
827                     }
828                 }
829             }
830             break;
831
832         default:
833             Debug(1, "Mangle: unknown command %c in %s\n", *ptr, control);
834             return ((char *) 0);
835             break;
836         }
837     }
838     if (!area[0])               /* have we deweted de poor widdle fing away? */
839     {
840         return ((char *) 0);
841     }
842     return (area);
843 }
844
845 int
846 PMatch(const char *control, const char *string)
847 {
848     while (*string && *control)
849     {
850         if (!MatchClass(*control, *string))
851         {
852             return(0);
853         }
854
855         string++;
856         control++;
857     }
858
859     if (*string || *control)
860     {
861         return(0);
862     }
863
864     return(1);
865 }