The option parsing code of fortune(6) contains a hand-rolled strtonum.
There are several problems with it: fortune won't accept any files
whose name starts with a number larger than 100 and the string
conversion is susceptible to overflow, which leads to bizarre errors:

$ fortune 101dalmatians
percentages must be <= 100
$ fortune 4294967280% all
fortune: no place to put residual probability (-16%)

The patch below fixes both these issues and makes some further minor
simplifications. Now the only impossible names for a fortune file are
the ones matching the regex [0-9][0-9.]*%

Index: fortune.c
===================================================================
RCS file: /var/cvs/src/games/fortune/fortune/fortune.c,v
retrieving revision 1.51
diff -u -p -r1.51 fortune.c
--- fortune.c   10 Jan 2016 13:35:09 -0000      1.51
+++ fortune.c   1 Mar 2016 14:19:22 -0000
@@ -38,6 +38,7 @@
 #include <assert.h>
 #include <ctype.h>
 #include <dirent.h>
+#include <err.h>
 #include <fcntl.h>
 #include <limits.h>
 #include <stdio.h>
@@ -330,37 +331,34 @@ form_file_list(char **files, int file_cn
        }
        for (i = 0; i < file_cnt; i++) {
                percent = NO_PROB;
-               if (!isdigit((unsigned char)files[i][0]))
-                       sp = files[i];
-               else {
-                       percent = 0;
-                       for (sp = files[i]; isdigit((unsigned char)*sp); sp++)
-                               percent = percent * 10 + *sp - '0';
-                       if (percent > 100) {
-                               fprintf(stderr, "percentages must be <= 100\n");
-                               return 0;
-                       }
-                       if (*sp == '.') {
-                               fprintf(stderr, "percentages must be 
integers\n");
-                               return 0;
-                       }
+
+               if (isdigit((unsigned char)files[i][0])) {
+                       int pos = strspn(files[i], "0123456789.");
+
                        /*
-                        * If the number isn't followed by a '%', then
-                        * it was not a percentage, just the first part
-                        * of a file name which starts with digits.
+                        * Only try to interpret files[i] as a percentage if
+                        * it ends in '%'. Otherwise assume it's a file name.
                         */
-                       if (*sp != '%') {
-                               percent = NO_PROB;
-                               sp = files[i];
-                       }
-                       else if (*++sp == '\0') {
-                               if (++i >= file_cnt) {
-                                       fprintf(stderr, "percentages must 
precede files\n");
-                                       return 0;
-                               }
-                               sp = files[i];
+                       if (files[i][pos] == '%' && files[i][pos+1] == '\0') {
+                               const char *errstr;
+                               char *prefix;
+
+                               if ((prefix = strndup(files[i], pos)) == NULL)
+                                       err(1, NULL);
+                               if (strchr(prefix, '.') != NULL)
+                                       errx(1, "percentages must be integers");
+                               percent = strtonum(prefix, 0, 100, &errstr);
+                               if (errstr != NULL)
+                                       errx(1, "percentage is %s: %s", errstr,
+                                           prefix);
+                               free(prefix);
+
+                               if (++i >= file_cnt)
+                                       errx(1,
+                                           "percentages must precede files");
                        }
                }
+               sp = files[i];
                if (strcmp(sp, "all") == 0)
                        sp = FORTDIR;
                if (!add_file(percent, sp, NULL, &File_list, &File_tail, NULL))

Reply via email to