1 /*
   2  * Copyright 2017 Gary Mills
   3  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
   4  * Use is subject to license terms.
   5  */
   6 
   7 /* 
   8  * The contents of this file are subject to the Netscape Public
   9  * License Version 1.1 (the "License"); you may not use this file
  10  * except in compliance with the License. You may obtain a copy of
  11  * the License at http://www.mozilla.org/NPL/
  12  *  
  13  * Software distributed under the License is distributed on an "AS
  14  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  15  * implied. See the License for the specific language governing
  16  * rights and limitations under the License.
  17  *  
  18  * The Original Code is Mozilla Communicator client code, released
  19  * March 31, 1998.
  20  * 
  21  * The Initial Developer of the Original Code is Netscape
  22  * Communications Corporation. Portions created by Netscape are
  23  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  24  * Rights Reserved.
  25  * 
  26  * Contributor(s): 
  27  */
  28 
  29 /*
  30  *  LDAP tools fileurl.c -- functions for handling file URLs.
  31  *  Used by ldapmodify.
  32  */
  33 
  34 #include "ldaptool.h"
  35 #include "fileurl.h"
  36 #include <ctype.h>        /* for isalpha() */
  37 #ifdef SOLARIS_LDAP_CMD
  38 #include <locale.h>
  39 #endif  /* SOLARIS_LDAP_CMD */
  40 
  41 #ifndef SOLARIS_LDAP_CMD
  42 #define gettext(s) s
  43 #endif
  44 
  45 static int str_starts_with( const char *s, char *prefix );
  46 static void hex_unescape( char *s );
  47 static int unhex( char c );
  48 static void strcpy_escaped_and_convert( char *s1, char *s2 );
  49 static int berval_from_file( const char *path, struct berval *bvp,
  50         int reporterrs );
  51 
  52 /*
  53  * Convert a file URL to a local path.
  54  *
  55  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *localpathp is
  56  * set point to an allocated string.  If not, an different LDAPTOOL_FILEURL_
  57  * error code is returned.
  58  *
  59  * See RFCs 1738 and 2396 for a specification for file URLs... but
  60  * Netscape Navigator seems to be a bit more lenient in what it will
  61  * accept, especially on Windows).
  62  *
  63  * This function parses file URLs of these three forms:
  64  *
  65  *    file:///path
  66  *    file:/path
  67  *    file://localhost/path
  68  *    file://host/path          (rejected with a ...NONLOCAL error)
  69  *
  70  * On Windows, we convert leading drive letters of the form C| to C:
  71  * and if a drive letter is present we strip off the slash that precedes
  72  * path.  Otherwise, the leading slash is returned.
  73  *
  74  */
  75 int
  76 ldaptool_fileurl2path( const char *fileurl, char **localpathp )
  77 {
  78     const char  *path;
  79     char        *pathcopy;
  80 
  81     /*
  82      * Make sure this is a file URL we can handle.
  83      */
  84     if ( !str_starts_with( fileurl, "file:" )) {
  85         return( LDAPTOOL_FILEURL_NOTAFILEURL );
  86     }
  87 
  88     path = fileurl + 5;         /* skip past "file:" scheme prefix */
  89 
  90     if ( *path != '/' ) {
  91         return( LDAPTOOL_FILEURL_MISSINGPATH );
  92     }
  93 
  94     ++path;                     /* skip past '/' at end of "file:/" */
  95 
  96     if ( *path == '/' ) {
  97         ++path;                 /* remainder is now host/path or /path */
  98         if ( *path != '/' ) {
  99             /*
 100              * Make sure it is for the local host.
 101              */
 102             if ( str_starts_with( path, "localhost/" )) {
 103                 path += 9;
 104             } else {
 105                 return( LDAPTOOL_FILEURL_NONLOCAL );
 106             }
 107         }
 108     } else {            /* URL is of the form file:/path */
 109         --path;
 110     }
 111 
 112     /*
 113      * The remainder is now of the form /path.  On Windows, skip past the
 114      * leading slash if a drive letter is present.
 115      */
 116 #ifdef _WINDOWS
 117     if ( isalpha( path[1] ) && ( path[2] == '|' || path[2] == ':' )) {
 118         ++path;
 119     }
 120 #endif /* _WINDOWS */
 121 
 122     /*
 123      * Duplicate the path so we can safely alter it.
 124      * Unescape any %HH sequences.
 125      */
 126     if (( pathcopy = strdup( path )) == NULL ) {
 127         return( LDAPTOOL_FILEURL_NOMEMORY );
 128     }
 129     hex_unescape( pathcopy );
 130 
 131 #ifdef _WINDOWS
 132     /*
 133      * Convert forward slashes to backslashes for Windows.  Also,
 134      * if we see a drive letter / vertical bar combination (e.g., c|)
 135      * at the beginning of the path, replace the '|' with a ':'.
 136      */
 137     {
 138         char    *p;
 139 
 140         for ( p = pathcopy; *p != '\0'; ++p ) {
 141             if ( *p == '/' ) {
 142                 *p = '\\';
 143             }
 144         }
 145     }
 146 
 147     if ( isalpha( pathcopy[0] ) && pathcopy[1] == '|' ) {
 148         pathcopy[1] = ':';
 149     }
 150 #endif /* _WINDOWS */
 151 
 152     *localpathp = pathcopy;
 153     return( LDAPTOOL_FILEURL_SUCCESS );
 154 }
 155 
 156 
 157 /*
 158  * Convert a local path to a file URL.
 159  *
 160  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *urlp is
 161  * set point to an allocated string.  If not, an different LDAPTOOL_FILEURL_
 162  * error code is returned.  At present, the only possible error is
 163  * LDAPTOOL_FILEURL_NOMEMORY.
 164  *
 165  * This function produces file URLs of the form file:path.
 166  *
 167  * On Windows, we convert leading drive letters to C|.
 168  *
 169  */
 170 int
 171 ldaptool_path2fileurl( char *path, char **urlp )
 172 {
 173     char        *p, *url, *prefix ="file:";
 174 
 175     if ( NULL == path ) {
 176         path = "/";
 177     }
 178 
 179     /*
 180      * Allocate space for the URL, taking into account that path may
 181      * expand during the hex escaping process.
 182      */
 183     if (( url = malloc( strlen( prefix ) + 3 * strlen( path ) + 1 )) == NULL ) {
 184         return( LDAPTOOL_FILEURL_NOMEMORY );
 185     }
 186 
 187     strcpy( url, prefix );
 188     p = url + strlen( prefix );
 189 
 190 #ifdef _WINDOWS
 191     /*
 192      * On Windows, convert leading drive letters (e.g., C:) to the correct URL
 193      * syntax (e.g., C|).
 194      */
 195     if ( isalpha( path[0] ) && path[1] == ':' ) {
 196         *p++ = path[0];
 197         *p++ = '|';
 198         path += 2;
 199         *p = '\0';
 200     }
 201 #endif /* _WINDOWS */
 202 
 203     /*
 204      * Append the path, encoding any URL-special characters using the %HH
 205      * convention.
 206      * On Windows, convert backwards slashes in the path to forward ones.
 207      */
 208     strcpy_escaped_and_convert( p, path );
 209 
 210     *urlp = url;
 211     return( LDAPTOOL_FILEURL_SUCCESS );
 212 }
 213 
 214 
 215 /*
 216  * Populate *bvp from "value" of length "vlen."
 217  *
 218  * If recognize_url_syntax is non-zero, :<fileurl is recognized.
 219  * If always_try_file is recognized and no file URL was found, an
 220  * attempt is made to stat and read the value as if it were the name
 221  * of a file.
 222  *
 223  * If reporterrs is non-zero, specific error messages are printed to
 224  * stderr.
 225  *
 226  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len
 227  * and bvp->bv_val are set (the latter is set to malloc'd memory).
 228  * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned.
 229  */
 230 int
 231 ldaptool_berval_from_ldif_value( const char *value, int vlen,
 232         struct berval *bvp, int recognize_url_syntax, int always_try_file,
 233         int reporterrs )
 234 {
 235     int rc = LDAPTOOL_FILEURL_SUCCESS;  /* optimistic */
 236     const char  *url = NULL;
 237     struct stat fstats;
 238         
 239     /* recognize "attr :< url" syntax if LDIF version is >= 1 */
 240 
 241 #ifdef notdef
 242     if ( ldaptool_verbose ) {
 243         fprintf( stderr, gettext("%s: ldaptool_berval_from_ldif_value: value: %s\n"),
 244             ldaptool_progname, value);
 245     }
 246 #endif
 247 
 248     if ( recognize_url_syntax && *value == '<' ) {
 249         for ( url = value + 1; isspace( *url ); ++url ) {
 250             ;   /* NULL */
 251         }
 252 
 253         if (strlen(url) > 7 && strncasecmp(url, "file://", 7) != 0) {
 254             /*
 255              * We only support file:// URLs for now.
 256              */
 257             url = NULL;
 258         }
 259     }
 260 
 261     if ( NULL != url ) {
 262         char            *path;
 263 
 264         rc = ldaptool_fileurl2path( url, &path );
 265         switch( rc ) {
 266         case LDAPTOOL_FILEURL_NOTAFILEURL:
 267             if ( reporterrs ) fprintf( stderr, gettext("%s: unsupported URL \"%s\";"
 268                                        " use a file:// URL instead.\n"), ldaptool_progname, url );
 269             break;
 270                 
 271         case LDAPTOOL_FILEURL_MISSINGPATH:
 272             if ( reporterrs ) fprintf( stderr,
 273                                        gettext("%s: unable to process URL \"%s\" --"
 274                                        " missing path.\n"), ldaptool_progname, url );
 275             break;
 276                 
 277         case LDAPTOOL_FILEURL_NONLOCAL:
 278             if ( reporterrs ) fprintf( stderr,
 279                                        gettext("%s: unable to process URL \"%s\" -- only"
 280                                        " local file:// URLs are supported.\n"),
 281                                        ldaptool_progname, url );
 282             break;
 283                 
 284         case LDAPTOOL_FILEURL_NOMEMORY:
 285             if ( reporterrs ) perror( "ldaptool_fileurl2path" );
 286             break;
 287                 
 288         case LDAPTOOL_FILEURL_SUCCESS:
 289             if ( stat( path, &fstats ) != 0 ) {
 290                 if ( reporterrs ) perror( path );
 291             } else if (S_ISDIR(fstats.st_mode)) {       
 292                 if ( reporterrs ) fprintf( stderr,
 293                                            gettext("%s: %s is a directory, not a file\n"),
 294                                            ldaptool_progname, path );
 295                 rc = LDAPTOOL_FILEURL_FILEIOERROR;
 296             } else {
 297                 rc = berval_from_file( path, bvp, reporterrs );
 298             }
 299             free( path );
 300             break;
 301                 
 302         default:
 303             if ( reporterrs ) fprintf( stderr,
 304                                        gettext("%s: unable to process URL \"%s\""
 305                                        " -- unknown error\n"), ldaptool_progname, url );
 306         }
 307     } else if ( always_try_file && (stat( value, &fstats ) == 0) &&
 308                 !S_ISDIR(fstats.st_mode)) {     /* get value from file */
 309         rc = berval_from_file( value, bvp, reporterrs );
 310     } else {
 311         bvp->bv_len = vlen;
 312         if (( bvp->bv_val = (char *)malloc( vlen + 1 )) == NULL ) {
 313             if ( reporterrs ) perror( "malloc" );
 314             rc = LDAPTOOL_FILEURL_NOMEMORY;
 315         } else {
 316             SAFEMEMCPY( bvp->bv_val, value, vlen );
 317             bvp->bv_val[ vlen ] = '\0';
 318         }
 319     }
 320     
 321     return( rc );
 322 }
 323 
 324 
 325 /*
 326  * Map an LDAPTOOL_FILEURL_ error code to an LDAP error code (crude).
 327  */
 328 int
 329 ldaptool_fileurlerr2ldaperr( int lderr )
 330 {
 331     int         rc;
 332 
 333     switch( lderr ) {
 334     case LDAPTOOL_FILEURL_SUCCESS:
 335         rc = LDAP_SUCCESS;
 336         break;
 337     case LDAPTOOL_FILEURL_NOMEMORY:
 338         rc = LDAP_NO_MEMORY;
 339         break;
 340     default:
 341         rc = LDAP_PARAM_ERROR;
 342     }
 343 
 344     return( rc );
 345 } 
 346 
 347 
 348 /*
 349  * Populate *bvp with the contents of the file named by "path".
 350  *
 351  * If reporterrs is non-zero, specific error messages are printed to
 352  * stderr.
 353  *
 354  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len
 355  * and bvp->bv_val are set (the latter is set to malloc'd memory).
 356  * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned.
 357  */
 358 
 359 static int
 360 berval_from_file( const char *path, struct berval *bvp, int reporterrs )
 361 {
 362     FILE        *fp;
 363     long        rlen;
 364 #if defined( XP_WIN32 )
 365     char        mode[20] = "r+b";
 366 #else
 367     char        mode[20] = "r";
 368 #endif
 369 
 370 #ifdef SOLARIS_LDAP_CMD
 371     if (( fp = fopen( path, mode )) == NULL ) {
 372 #else
 373     if (( fp = ldaptool_open_file( path, mode )) == NULL ) {
 374 #endif  /* SOLARIS_LDAP_CMD */
 375         if ( reporterrs ) perror( path );
 376         return( LDAPTOOL_FILEURL_FILEIOERROR );
 377     }
 378 
 379     if ( fseek( fp, 0L, SEEK_END ) != 0 ) {
 380         if ( reporterrs ) perror( path );
 381         fclose( fp );
 382         return( LDAPTOOL_FILEURL_FILEIOERROR );
 383     }
 384 
 385     bvp->bv_len = ftell( fp );
 386 
 387     if (( bvp->bv_val = (char *)malloc( bvp->bv_len + 1 )) == NULL ) {
 388         if ( reporterrs ) perror( "malloc" );
 389         fclose( fp );
 390         return( LDAPTOOL_FILEURL_NOMEMORY );
 391     }
 392 
 393     if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {
 394         if ( reporterrs ) perror( path );
 395         fclose( fp );
 396         return( LDAPTOOL_FILEURL_FILEIOERROR );
 397     }
 398 
 399     rlen = fread( bvp->bv_val, 1, bvp->bv_len, fp );
 400     fclose( fp );
 401 
 402     if ( rlen != (long)bvp->bv_len ) {
 403         if ( reporterrs ) perror( path );
 404         free( bvp->bv_val );
 405         return( LDAPTOOL_FILEURL_FILEIOERROR );
 406     }
 407 
 408     bvp->bv_val[ bvp->bv_len ] = '\0';
 409     return( LDAPTOOL_FILEURL_SUCCESS );
 410 }
 411 
 412 
 413 /*
 414  * Return a non-zero value if the string s begins with prefix and zero if not.
 415  */
 416 static int
 417 str_starts_with( const char *s, char *prefix )
 418 {
 419     size_t      prefix_len;
 420 
 421     if ( s == NULL || prefix == NULL ) {
 422         return( 0 );
 423     }
 424 
 425     prefix_len = strlen( prefix );
 426     if ( strlen( s ) < prefix_len ) {
 427         return( 0 );
 428     }
 429 
 430     return( strncmp( s, prefix, prefix_len ) == 0 );
 431 }
 432 
 433 
 434 /*
 435  * Remove URL hex escapes from s... done in place.  The basic concept for
 436  * this routine is borrowed from the WWW library HTUnEscape() routine.
 437  *
 438  * A similar function called nsldapi_hex_unescape can be found in
 439  * ../../libraries/libldap/unescape.c
 440  */
 441 static void
 442 hex_unescape( char *s )
 443 {
 444         char    *p;
 445 
 446         for ( p = s; *s != '\0'; ++s ) {
 447                 if ( *s == '%' ) {
 448                         if ( *++s != '\0' ) {
 449                                 *p = unhex( *s ) << 4;
 450                         }
 451                         if ( *++s != '\0' ) {
 452                                 *p++ += unhex( *s );
 453                         }
 454                 } else {
 455                         *p++ = *s;
 456                 }
 457         }
 458 
 459         *p = '\0';
 460 }
 461 
 462 
 463 /*
 464  * Return the integer equivalent of one hex digit (in c).
 465  *
 466  * A similar function can be found in ../../libraries/libldap/unescape.c
 467  */
 468 static int
 469 unhex( char c )
 470 {
 471         return( c >= '0' && c <= '9' ? c - '0'
 472             : c >= 'A' && c <= 'F' ? c - 'A' + 10
 473             : c - 'a' + 10 );
 474 }
 475 
 476 
 477 #define HREF_CHAR_ACCEPTABLE( c )       (( c >= '-' && c <= '9' ) ||      \
 478                                          ( c >= '@' && c <= 'Z' ) ||      \
 479                                          ( c == '_' ) ||                \
 480                                          ( c >= 'a' && c <= 'z' ))
 481 
 482 /*
 483  * Like strcat(), except if any URL-special characters are found in s2
 484  * they are escaped using the %HH convention and backslash characters are
 485  * converted to forward slashes on Windows.
 486  *
 487  * Maximum space needed in s1 is 3 * strlen( s2 ) + 1.
 488  *
 489  * A similar function that does not convert the slashes called
 490  * strcat_escaped() can be found in ../../libraries/libldap/tmplout.c
 491  */
 492 static void
 493 strcpy_escaped_and_convert( char *s1, char *s2 )
 494 {
 495     char        *p, *q;
 496     char        *hexdig = "0123456789ABCDEF";
 497 
 498     p = s1 + strlen( s1 );
 499     for ( q = s2; *q != '\0'; ++q ) {
 500 #ifdef _WINDOWS
 501         if ( *q == '\\' ) {
 502                 *p++ = '/';
 503         } else
 504 #endif /* _WINDOWS */
 505 
 506         if ( HREF_CHAR_ACCEPTABLE( *q )) {
 507             *p++ = *q;
 508         } else {
 509             *p++ = '%';
 510             *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ];
 511             *p++ = hexdig[ 0x0F & *q ];
 512         }
 513     }
 514 
 515     *p = '\0';
 516 }