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