/** * \brief Close the DVD and clean up the library. * * \param dvdcss a \e libdvdcss instance. * \return zero in case of success, a negative value otherwise. * * This function closes the DVD device and frees all the memory allocated * by \e libdvdcss. On return, the #dvdcss_t is invalidated and may not be * used again. */ LIBDVDCSS_EXPORT int dvdcss_close ( dvdcss_t dvdcss ) { dvd_title_t *p_title; int i_ret; /* Free our list of keys */ p_title = dvdcss->p_titles; while( p_title ) { dvd_title_t *p_tmptitle = p_title->p_next; free( p_title ); p_title = p_tmptitle; } i_ret = _dvdcss_close( dvdcss ); if( i_ret < 0 ) { return i_ret; } free( dvdcss->psz_device ); free( dvdcss ); return 0; }
/** * \brief Open a DVD device or directory and return a dvdcss instance. * * \param psz_target a string containing the target name, for instance * "/dev/hdc" or "E:". * \return a handle to a dvdcss instance or NULL on error. * * Initialize the \e libdvdcss library and open the requested DVD device or * directory. \e libdvdcss checks whether ioctls can be performed on the disc, * and when possible, the disc key is retrieved. * * dvdcss_open() returns a handle to be used for all subsequent \e libdvdcss * calls. If an error occurred, NULL is returned. */ LIBDVDCSS_EXPORT dvdcss_t dvdcss_open ( char *psz_target, struct svfs_ops *svfs_ops ) { char psz_buffer[PATH_MAX]; int i_ret; char *psz_method = getenv( "DVDCSS_METHOD" ); char *psz_verbose = getenv( "DVDCSS_VERBOSE" ); char *psz_cache = getenv( "DVDCSS_CACHE" ); #ifndef WIN32 char *psz_raw_device = getenv( "DVDCSS_RAW_DEVICE" ); #endif dvdcss_t dvdcss; /* * Allocate the library structure */ dvdcss = malloc( sizeof( struct dvdcss_s ) ); if( dvdcss == NULL ) { return NULL; } /* * Initialize structure with default values */ #ifndef WIN32 dvdcss->i_raw_fd = -1; #endif dvdcss->p_titles = NULL; dvdcss->psz_device = (char *)strdup( psz_target ); dvdcss->psz_error = "no error"; dvdcss->i_method = DVDCSS_METHOD_KEY; dvdcss->psz_cachefile[0] = '\0'; dvdcss->b_debug = 0; dvdcss->b_errors = 0; /* * Find verbosity from DVDCSS_VERBOSE environment variable */ if( psz_verbose != NULL ) { int i = atoi( psz_verbose ); if( i >= 2 ) dvdcss->b_debug = i; if( i >= 1 ) dvdcss->b_errors = 1; } /* * Find method from DVDCSS_METHOD environment variable */ if( psz_method != NULL ) { if( !strncmp( psz_method, "key", 4 ) ) { dvdcss->i_method = DVDCSS_METHOD_KEY; } else if( !strncmp( psz_method, "disc", 5 ) ) { dvdcss->i_method = DVDCSS_METHOD_DISC; } else if( !strncmp( psz_method, "title", 5 ) ) { dvdcss->i_method = DVDCSS_METHOD_TITLE; } else { print_error( dvdcss, "unknown decrypt method, please choose " "from 'title', 'key' or 'disc'" ); free( dvdcss->psz_device ); free( dvdcss ); return NULL; } } /* * If DVDCSS_CACHE was not set, try to guess a default value */ if( psz_cache == NULL || psz_cache[0] == '\0' ) { #ifdef HAVE_DIRECT_H typedef HRESULT( WINAPI *SHGETFOLDERPATH ) ( HWND, int, HANDLE, DWORD, LPTSTR ); # define CSIDL_FLAG_CREATE 0x8000 # define CSIDL_APPDATA 0x1A # define SHGFP_TYPE_CURRENT 0 char psz_home[MAX_PATH]; HINSTANCE p_dll; SHGETFOLDERPATH p_getpath; *psz_home = '\0'; /* Load the shfolder dll to retrieve SHGetFolderPath */ p_dll = LoadLibrary( "shfolder.dll" ); if( p_dll ) { p_getpath = (void*)GetProcAddress( p_dll, "SHGetFolderPathA" ); if( p_getpath ) { /* Get the "Application Data" folder for the current user */ if( p_getpath( NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, psz_home ) == S_OK ) { FreeLibrary( p_dll ); } else { *psz_home = '\0'; } } FreeLibrary( p_dll ); } /* Cache our keys in * C:\Documents and Settings\$USER\Application Data\dvdcss\ */ if( *psz_home ) { snprintf( psz_buffer, PATH_MAX, "%s/dvdcss", psz_home ); psz_buffer[PATH_MAX-1] = '\0'; psz_cache = psz_buffer; } #else char *psz_home = NULL; # ifdef HAVE_PWD_H struct passwd *p_pwd; /* Try looking in password file for home dir. */ p_pwd = getpwuid(getuid()); if( p_pwd ) { psz_home = p_pwd->pw_dir; } # endif if( psz_home == NULL ) { psz_home = getenv( "HOME" ); } if( psz_home == NULL ) { psz_home = getenv( "USERPROFILE" ); } /* Cache our keys in ${HOME}/.dvdcss/ */ if( psz_home ) { snprintf( psz_buffer, PATH_MAX, "%s/.dvdcss", psz_home ); psz_buffer[PATH_MAX-1] = '\0'; psz_cache = psz_buffer; } #endif } /* * Find cache dir from the DVDCSS_CACHE environment variable */ if( psz_cache != NULL ) { if( psz_cache[0] == '\0' || !strcmp( psz_cache, "off" ) ) { psz_cache = NULL; } /* Check that we can add the ID directory and the block filename */ else if( strlen( psz_cache ) + 1 + 32 + 1 + (KEY_SIZE * 2) + 10 + 1 > PATH_MAX ) { print_error( dvdcss, "cache directory name is too long" ); psz_cache = NULL; } } /* * Open device */ _dvdcss_check( dvdcss ); i_ret = _dvdcss_open( dvdcss, svfs_ops ); if( i_ret < 0 ) { free( dvdcss->psz_device ); free( dvdcss ); return NULL; } if(svfs_ops == NULL) { dvdcss->b_scrambled = 1; /* Assume the worst */ dvdcss->b_ioctls = _dvdcss_use_ioctls( dvdcss ); if( dvdcss->b_ioctls ) { i_ret = _dvdcss_test( dvdcss ); if( i_ret < 0 ) { /* Disable the CSS ioctls and hope that it works? */ print_debug( dvdcss, "could not check whether the disc was scrambled" ); dvdcss->b_ioctls = 0; } else { print_debug( dvdcss, i_ret ? "disc is scrambled" : "disc is unscrambled" ); dvdcss->b_scrambled = i_ret; } } } else { dvdcss->b_scrambled = 0; dvdcss->b_ioctls = 0; } /* If disc is CSS protected and the ioctls work, authenticate the drive */ if( dvdcss->b_scrambled && dvdcss->b_ioctls ) { i_ret = _dvdcss_disckey( dvdcss ); if( i_ret < 0 ) { _dvdcss_close( dvdcss ); free( dvdcss->psz_device ); free( dvdcss ); return NULL; } } /* If the cache is enabled, write the cache directory tag */ if( psz_cache ) { char *psz_tag = "Signature: 8a477f597d28d172789f06886806bc55\r\n" "# This file is a cache directory tag created by libdvdcss.\r\n" "# For information about cache directory tags, see:\r\n" "# http://www.brynosaurus.com/cachedir/\r\n"; char psz_tagfile[PATH_MAX + 1 + 12 + 1]; int i_fd; sprintf( psz_tagfile, "%s/CACHEDIR.TAG", psz_cache ); i_fd = open( psz_tagfile, O_RDWR|O_CREAT, 0644 ); if( i_fd >= 0 ) { write( i_fd, psz_tag, strlen(psz_tag) ); close( i_fd ); } } /* If the cache is enabled, extract a unique disc ID */ if( psz_cache ) { uint8_t p_sector[DVDCSS_BLOCK_SIZE]; char psz_debug[PATH_MAX + 30]; char psz_key[1 + KEY_SIZE * 2 + 1]; char *psz_title; uint8_t *psz_serial; int i; /* We read sector 0. If it starts with 0x000001ba (BE), we are * reading a VOB file, and we should not cache anything. */ i_ret = dvdcss->pf_seek( dvdcss, 0 ); if( i_ret != 0 ) { goto nocache; } i_ret = dvdcss->pf_read( dvdcss, p_sector, 1 ); if( i_ret != 1 ) { goto nocache; } if( p_sector[0] == 0x00 && p_sector[1] == 0x00 && p_sector[2] == 0x01 && p_sector[3] == 0xba ) { goto nocache; } /* The data we are looking for is at sector 16 (32768 bytes): * - offset 40: disc title (32 uppercase chars) * - offset 813: manufacturing date + serial no (16 digits) */ i_ret = dvdcss->pf_seek( dvdcss, 16 ); if( i_ret != 16 ) { goto nocache; } i_ret = dvdcss->pf_read( dvdcss, p_sector, 1 ); if( i_ret != 1 ) { goto nocache; } /* Get the disc title */ psz_title = (char *)p_sector + 40; psz_title[32] = '\0'; for( i = 0 ; i < 32 ; i++ ) { if( psz_title[i] <= ' ' ) { psz_title[i] = '\0'; break; } else if( psz_title[i] == '/' || psz_title[i] == '\\' ) { psz_title[i] = '-'; } } /* Get the date + serial */ psz_serial = p_sector + 813; psz_serial[16] = '\0'; /* Check that all characters are digits, otherwise convert. */ for( i = 0 ; i < 16 ; i++ ) { if( psz_serial[i] < '0' || psz_serial[i] > '9' ) { char psz_tmp[16 + 1]; sprintf( psz_tmp, "%.2"PRIx8"%.2"PRIx8"%.2"PRIx8"%.2"PRIx8"%.2"PRIx8"%.2"PRIx8"%.2"PRIx8"%.2"PRIx8"", psz_serial[0], psz_serial[1], psz_serial[2], psz_serial[3], psz_serial[4], psz_serial[5], psz_serial[6], psz_serial[7] ); memcpy( psz_serial, psz_tmp, 16 ); break; } } /* Get disk key, since some discs have got same title, manufacturing * date and serial number, but different keys */ if( dvdcss->b_scrambled ) { psz_key[0] = '-'; for( i = 0; i < KEY_SIZE; i++ ) { sprintf( &psz_key[1+i*2], "%.2"PRIx8, dvdcss->css.p_disc_key[i] ); } psz_key[1 + KEY_SIZE * 2] = '\0'; } else { psz_key[0] = 0; } /* We have a disc name or ID, we can create the cache dir */ i = sprintf( dvdcss->psz_cachefile, "%s", psz_cache ); #if !defined( WIN32 ) || defined( SYS_CYGWIN ) i_ret = mkdir( dvdcss->psz_cachefile, 0755 ); #else i_ret = mkdir( dvdcss->psz_cachefile ); #endif if( i_ret < 0 && errno != EEXIST ) { print_error( dvdcss, "failed creating cache directory" ); dvdcss->psz_cachefile[0] = '\0'; goto nocache; } i += sprintf( dvdcss->psz_cachefile + i, "/%s-%s%s", psz_title, psz_serial, psz_key ); #if !defined( WIN32 ) || defined( SYS_CYGWIN ) i_ret = mkdir( dvdcss->psz_cachefile, 0755 ); #else i_ret = mkdir( dvdcss->psz_cachefile ); #endif if( i_ret < 0 && errno != EEXIST ) { print_error( dvdcss, "failed creating cache subdirectory" ); dvdcss->psz_cachefile[0] = '\0'; goto nocache; } i += sprintf( dvdcss->psz_cachefile + i, "/"); /* Pointer to the filename we will use. */ dvdcss->psz_block = dvdcss->psz_cachefile + i; sprintf( psz_debug, "using CSS key cache dir: %s", dvdcss->psz_cachefile ); print_debug( dvdcss, psz_debug ); } nocache: #ifndef WIN32 if( psz_raw_device != NULL ) { _dvdcss_raw_open( dvdcss, psz_raw_device ); } #endif /* Seek at the beginning, just for safety. */ dvdcss->pf_seek( dvdcss, 0 ); return dvdcss; }