]> eyrie.org Git - kerberos/krb5-strength.git/blob - cracklib/rules.c
Make xmalloc diagnostic suppression conditional
[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  *   - Remove break after return to silence Clang warnings.
35  */
36
37 #include <stdarg.h>
38
39 #ifndef IN_CRACKLIB
40
41 #include "crack.h"
42
43 #else
44
45 #include "packer.h"
46
47 static void __attribute__((__format__(printf, 2, 3)))
48 Debug(int val, const char *fmt, ...)
49 {
50     if (val < 2) {
51         va_list args;
52
53         va_start(args, fmt);
54         vfprintf(stderr, fmt, args);
55         va_end(args);
56     }
57 }
58
59 #endif
60
61 #include <string.h>
62
63 #define RULE_NOOP       ':'
64 #define RULE_PREPEND    '^'
65 #define RULE_APPEND     '$'
66 #define RULE_REVERSE    'r'
67 #define RULE_UPPERCASE  'u'
68 #define RULE_LOWERCASE  'l'
69 #define RULE_PLURALISE  'p'
70 #define RULE_CAPITALISE 'c'
71 #define RULE_DUPLICATE  'd'
72 #define RULE_REFLECT    'f'
73 #define RULE_SUBSTITUTE 's'
74 #define RULE_MATCH      '/'
75 #define RULE_NOT        '!'
76 #define RULE_LT         '<'
77 #define RULE_GT         '>'
78 #define RULE_EXTRACT    'x'
79 #define RULE_OVERSTRIKE 'o'
80 #define RULE_INSERT     'i'
81 #define RULE_EQUALS     '='
82 #define RULE_PURGE      '@'
83 #define RULE_CLASS      '?'     /* class rule? socialist ethic in cracker? */
84
85 #define RULE_DFIRST     '['
86 #define RULE_DLAST      ']'
87 #define RULE_MFIRST     '('
88 #define RULE_MLAST      ')'
89
90 static int
91 Suffix(const char *myword, const char *suffix)
92 {
93     register size_t i;
94     register size_t j;
95     i = strlen(myword);
96     j = strlen(suffix);
97
98     if (i > j)
99     {
100         return (STRCMP((myword + i - j), suffix));
101     } else
102     {
103         return (-1);
104     }
105 }
106
107 /* return a pointer to a reversal */
108 char *
109 Reverse(const char *str)
110 {
111     register size_t i;
112     register size_t j;
113     static char area[STRINGSIZE];
114     j = i = strlen(str);
115     while (*str)
116     {
117         area[--i] = *str++;
118     }
119     area[j] = '\0';
120     return (area);
121 }
122
123 /* return a pointer to an uppercase */
124 static char *
125 Uppercase(const char *str)
126 {
127     register char *ptr;
128     static char area[STRINGSIZE];
129     ptr = area;
130     while (*str)
131     {
132         *(ptr++) = CRACK_TOUPPER(*str);
133         str++;
134     }
135     *ptr = '\0';
136
137     return (area);
138 }
139
140 /* return a pointer to an lowercase */
141 char *
142 Lowercase(const char *str)
143 {
144     register char *ptr;
145     static char area[STRINGSIZE];
146     ptr = area;
147     while (*str)
148     {
149         *(ptr++) = CRACK_TOLOWER(*str);
150         str++;
151     }
152     *ptr = '\0';
153
154     return (area);
155 }
156
157 /* return a pointer to an capitalised */
158 static char *
159 Capitalise(const char *str)
160 {
161     register char *ptr;
162     static char area[STRINGSIZE];
163     ptr = area;
164
165     while (*str)
166     {
167         *(ptr++) = CRACK_TOLOWER(*str);
168         str++;
169     }
170
171     *ptr = '\0';
172     area[0] = CRACK_TOUPPER(area[0]);
173     return (area);
174 }
175
176 /* returns a pointer to a plural */
177 static char *
178 Pluralise(const char *string)
179 {
180     register size_t length;
181     static char area[STRINGSIZE];
182     length = strlen(string);
183     strcpy(area, string);
184
185     if (!Suffix(string, "ch") ||
186         !Suffix(string, "ex") ||
187         !Suffix(string, "ix") ||
188         !Suffix(string, "sh") ||
189         !Suffix(string, "ss"))
190     {
191         /* bench -> benches */
192         strcat(area, "es");
193     } else if (length > 2 && string[length - 1] == 'y')
194     {
195         if (strchr("aeiou", string[length - 2]))
196         {
197             /* alloy -> alloys */
198             strcat(area, "s");
199         } else
200         {
201             /* gully -> gullies */
202             strcpy(area + length - 1, "ies");
203         }
204     } else if (string[length - 1] == 's')
205     {
206         /* bias -> biases */
207         strcat(area, "es");
208     } else
209     {
210         /* catchall */
211         strcat(area, "s");
212     }
213
214     return (area);
215 }
216
217 /* returns pointer to a swapped about copy */
218 static char *
219 Substitute(const char *string, char old, char new)
220 {
221     register char *ptr;
222     static char area[STRINGSIZE];
223     ptr = area;
224     while (*string)
225     {
226         *(ptr++) = (*string == old ? new : *string);
227         string++;
228     }
229     *ptr = '\0';
230     return (area);
231 }
232
233 /* returns pointer to a purged copy */
234 static char *
235 Purge(const char *string, char target)
236 {
237     register char *ptr;
238     static char area[STRINGSIZE];
239     ptr = area;
240     while (*string)
241     {
242         if (*string != target)
243         {
244             *(ptr++) = *string;
245         }
246         string++;
247     }
248     *ptr = '\0';
249     return (area);
250 }
251 /* -------- CHARACTER CLASSES START HERE -------- */
252
253 /*
254  * this function takes two inputs, a class identifier and a character, and
255  * returns non-null if the given character is a member of the class, based
256  * upon restrictions set out below
257  */
258
259 static int
260 MatchClass(char class, char input)
261 {
262     register char c;
263     register int retval;
264     retval = 0;
265
266     switch (class)
267     {
268         /* ESCAPE */
269
270     case '?':                   /* ?? -> ? */
271         if (input == '?')
272         {
273             retval = 1;
274         }
275         break;
276
277         /* ILLOGICAL GROUPINGS (ie: not in ctype.h) */
278
279     case 'V':
280     case 'v':                   /* vowels */
281         c = CRACK_TOLOWER(input);
282         if (strchr("aeiou", c))
283         {
284             retval = 1;
285         }
286         break;
287
288     case 'C':
289     case 'c':                   /* consonants */
290         c = CRACK_TOLOWER(input);
291         if (strchr("bcdfghjklmnpqrstvwxyz", c))
292         {
293             retval = 1;
294         }
295         break;
296
297     case 'W':
298     case 'w':                   /* whitespace */
299         if (strchr("\t ", input))
300         {
301             retval = 1;
302         }
303         break;
304
305     case 'P':
306     case 'p':                   /* punctuation */
307         if (strchr(".`,:;'!?\"", input))
308         {
309             retval = 1;
310         }
311         break;
312
313     case 'S':
314     case 's':                   /* symbols */
315         if (strchr("$%%^&*()-_+=|\\[]{}#@/~", input))
316         {
317             retval = 1;
318         }
319         break;
320
321         /* LOGICAL GROUPINGS */
322
323     case 'L':
324     case 'l':                   /* lowercase */
325         if (islower(input))
326         {
327             retval = 1;
328         }
329         break;
330
331     case 'U':
332     case 'u':                   /* uppercase */
333         if (isupper(input))
334         {
335             retval = 1;
336         }
337         break;
338
339     case 'A':
340     case 'a':                   /* alphabetic */
341         if (isalpha(input))
342         {
343             retval = 1;
344         }
345         break;
346
347     case 'X':
348     case 'x':                   /* alphanumeric */
349         if (isalnum(input))
350         {
351             retval = 1;
352         }
353         break;
354
355     case 'D':
356     case 'd':                   /* digits */
357         if (isdigit(input))
358         {
359             retval = 1;
360         }
361         break;
362
363     default:
364         Debug(1, "MatchClass: unknown class %c\n", class);
365         return (0);
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         }
836     }
837     if (!area[0])               /* have we deweted de poor widdle fing away? */
838     {
839         return ((char *) 0);
840     }
841     return (area);
842 }
843
844 int
845 PMatch(const char *control, const char *string)
846 {
847     while (*string && *control)
848     {
849         if (!MatchClass(*control, *string))
850         {
851             return(0);
852         }
853
854         string++;
855         control++;
856     }
857
858     if (*string || *control)
859     {
860         return(0);
861     }
862
863     return(1);
864 }