update from MirBSD CVS
[alioth/cvs.git] / src / root.c
index 6994160..c2d32f4 100644 (file)
@@ -1,4 +1,7 @@
 /*
+ * Copyright © 2017
+ *     mirabilos <m@mirbsd.org>
+ *
  * Copyright (C) 1986-2005 The Free Software Foundation, Inc.
  *
  * Portions Copyright (C) 1998-2005 Derek Price, Ximbiot <http://ximbiot.com>,
@@ -18,7 +21,7 @@
 #include <assert.h>
 #include "getline.h"
 
-__RCSID("$MirOS: src/gnu/usr.bin/cvs/src/root.c,v 1.11 2017/01/08 19:13:05 tg Exp $");
+__RCSID("$MirOS: src/gnu/usr.bin/cvs/src/root.c,v 1.19 2017/08/11 23:49:29 tg Exp $");
 
 /* Printable names for things in the current_parsed_root->method enum variable.
    Watch out if the enum is changed in cvs.h! */
@@ -495,6 +498,9 @@ free_cvsroot_t (cvsroot_t *root)
 }
 
 
+#if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
+static char *validate_hostname(const char *) __attribute__((__malloc__));
+#endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */
 
 /*
  * Parse a CVSROOT string to allocate and return a new cvsroot_t structure.
@@ -648,26 +654,27 @@ parse_cvsroot (const char *root_in)
            TRACE (TRACE_DATA, "CVSROOT option=`%s' value=`%s'", p, q);
            if (!strcasecmp (p, "proxy"))
            {
-               newroot->proxy_hostname = xstrdup (q);
+               if (!(newroot->proxy_hostname = validate_hostname(q))) {
+                       error(0, 0, "Invalid proxy hostname: %s", q);
+                       goto error_exit;
+               }
            }
            else if (!strcasecmp (p, "proxyport"))
            {
                char *r = q;
-               if (*r == '-') r++;
-               while (*r)
-               {
-                   if (!isdigit(*r++))
-                   {
+
+               do {
+                   if (!isdigit(*r)) {
+ proxy_port_error:
                        error (0, 0,
 "CVSROOT may only specify a positive, non-zero, integer proxy port (not `%s').",
                               q);
                        goto error_exit;
                    }
-               }
-               if ((newroot->proxy_port = atoi (q)) <= 0)
-                   error (0, 0,
-"CVSROOT may only specify a positive, non-zero, integer proxy port (not `%s').",
-                          q);
+               } while (*++r);
+               if ((newroot->proxy_port = atoi(q)) <= 0 ||
+                   newroot->proxy_port > 65535)
+                       goto proxy_port_error;
            }
            else if (!strcasecmp (p, "CVS_RSH"))
            {
@@ -760,7 +767,22 @@ parse_cvsroot (const char *root_in)
                /* a blank username is impossible, so leave it NULL in that
                 * case so we know to use the default username
                 */
+           {
+               /* for want of strcspn */
+               if (/* no at, obviously */ strchr(cvsroot_copy, '@') ||
+                   /* no colon, interference with CVSROOT/passwd file */
+                   strchr(cvsroot_copy, ':') ||
+                   /* no linefeeds, interference with pserver protocol */
+                   strchr(cvsroot_copy, '\012')) {
+                       error(0, 0, "Bad username \"%s\"", cvsroot_copy);
+                       goto error_exit;
+               }
+               /* other limitations include not beginning with a
+                * hyphen-minus but that’s not even a requirement
+                * in POSIX, let alone other operating environments…
+                */
                newroot->username = xstrdup (cvsroot_copy);
+           }
 
            cvsroot_copy = ++p;
        }
@@ -771,24 +793,18 @@ parse_cvsroot (const char *root_in)
        if ((p = strchr (cvsroot_copy, ':')) != NULL)
        {
            *p++ = '\0';
-           if (strlen(p))
+           if (*p)
            {
+               char qch;
+
                q = p;
-               if (*q == '-') q++;
-               while (*q)
+               while ((qch = *q++))
                {
-                   if (!isdigit(*q++))
-                   {
-                       error (0, 0,
-"CVSROOT may only specify a positive, non-zero, integer port (not `%s').",
-                               p);
-                       error (0, 0,
-                               "Perhaps you entered a relative pathname?");
-                       goto error_exit;
-                   }
+                   if (!isdigit(qch))
+                       goto parse_port_error;
                }
-               if ((newroot->port = atoi (p)) <= 0)
-               {
+               if ((newroot->port = atoi(p)) <= 0 || newroot->port > 65535) {
+ parse_port_error:
                    error (0, 0,
 "CVSROOT may only specify a positive, non-zero, integer port (not `%s').",
                            p);
@@ -798,12 +814,8 @@ parse_cvsroot (const char *root_in)
            }
        }
 
-       /* copy host */
-       if (*cvsroot_copy != '\0')
-           /* blank hostnames are invalid, but for now leave the field NULL
-            * and catch the error during the sanity checks later
-            */
-           newroot->hostname = xstrdup (cvsroot_copy);
+       /* check and copy host */
+       newroot->hostname = validate_hostname(cvsroot_copy);
 
        /* restore the '/' */
        cvsroot_copy = firstslash;
@@ -828,7 +840,9 @@ parse_cvsroot (const char *root_in)
 #if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
     if (newroot->username && ! newroot->hostname)
     {
-       error (0, 0, "Missing hostname in CVSROOT.");
+       /* this defangs sanity.sh tests for remote reject, though */
+ bad_hostname:
+       error (0, 0, "Missing or bad hostname in CVSROOT.");
        goto error_exit;
     }
 
@@ -942,10 +956,7 @@ parse_cvsroot (const char *root_in)
     }
 
     if (check_hostname && !newroot->hostname)
-    {
-       error (0, 0, "Didn't specify hostname in CVSROOT.");
-       goto error_exit;
-    }
+       goto bad_hostname;
 
     if (no_port && newroot->port)
     {
@@ -1131,3 +1142,78 @@ main (int argc, char *argv[])
    /* NOTREACHED */
 }
 #endif
+
+#if defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT)
+#define CLS_INVALID    0
+#define CLS_INSIDE     1
+#define CLS_OUTSIDE    2
+#define CLS_SEPARATOR  4
+/* EBCDIC safe */
+#define CLASSIFY(x)    classify[(unsigned char)(x)]
+static char *
+validate_hostname(const char *s)
+{
+       char *buf, *cp;
+       size_t sz;
+       static char classify_initialised = 0, *classify;
+
+       /* initialise classification table */
+       if (!classify_initialised) {
+               const char *ccp;
+
+               classify = xmalloc(256);
+               for (sz = 0; sz < 256; ++sz)
+                       CLASSIFY(sz) = CLS_INVALID;
+               for (ccp = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+                   *ccp; ++ccp)
+                       CLASSIFY(*ccp) = CLS_INSIDE | CLS_OUTSIDE;
+               CLASSIFY('-') = CLS_INSIDE;
+               CLASSIFY('.') = CLS_SEPARATOR;
+               classify_initialised = 1;
+       }
+
+       /* total size limit tolerating a trailing dot */
+       if ((sz = strlen(s)) > 256)
+               return (NULL);
+       buf = xstrdup(s);
+
+       /* drop trailing dot */
+       if ((unsigned char)buf[sz - 1] == (unsigned char)'.')
+               buf[--sz] = '\0';
+       /* recheck */
+       if (sz > 255) {
+ err:
+               free(buf);
+               return (NULL);
+       }
+
+       /* check each label */
+       cp = buf;
+ loop:
+       /* must begin with [0-9A-Za-z] */
+       if (!(CLASSIFY(*cp++) & CLS_OUTSIDE))
+               goto err;
+       sz = 1;
+       /* arbitrary many [0-9A-Za-z-] */
+       while (CLASSIFY(*cp) & CLS_INSIDE) {
+               ++cp;
+               ++sz;
+       }
+       /* except the last must have been [0-9A-Za-z] again */
+       if (!(CLASSIFY(cp[-1]) & CLS_OUTSIDE))
+               goto err;
+       /* maximum label size */
+       if (sz > 63)
+               goto err;
+       /* next label? */
+       if (CLASSIFY(*cp) & CLS_SEPARATOR) {
+               ++cp;
+               goto loop;
+       }
+       /* must be end of string now */
+       if (*cp)
+               goto err;
+       /* it is, everything okay */
+       return (buf);
+}
+#endif /* defined(CLIENT_SUPPORT) || defined (SERVER_SUPPORT) */