public static function auth_user() { $isLocal = self::is_local(); $headers = apache_request_headers(); $myplex_token = $headers['X-Plex-Token']; if (empty($myplex_token)) { $myplex_token = $_REQUEST['X-Plex-Token']; } if (!$isLocal) { $match_users = AmpConfig::get('plex_match_email'); $myplex_username = $headers['X-Plex-Username']; if (empty($myplex_token)) { // Never fail OPTIONS requests if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { self::setPlexHeader($headers); exit; } else { debug_event('Access Control', 'Authentication token is missing.', '3'); self::createError(401); } } $createSession = false; Session::gc(); $username = ""; $email = trim(Session::read((string) $myplex_token)); if (empty($email)) { $createSession = true; $xml = self::get_server_authtokens(); $validToken = false; foreach ($xml->access_token as $tk) { if ((string) $tk['token'] == $myplex_token) { $username = (string) $tk['username']; // We should apply filter and access restriction to shared sections only, but that's not easily possible with current Ampache architecture $validToken = true; break; } } if (!$validToken) { debug_event('Access Control', 'Auth-Token ' . $myplex_token . ' invalid for this server.', '3'); self::createError(401); } } // Need to get a match between Plex and Ampache users if ($match_users) { if (!AmpConfig::get('access_control')) { debug_event('Access Control', 'Error Attempted to use Plex with Access Control turned off and plex/ampache link enabled.', '3'); self::createError(401); } if (empty($email)) { $xml = self::get_users_account(); if ((string) $xml->username == $username) { $email = (string) $xml->email; } else { $xml = self::get_server_friends(); foreach ($xml->User as $xuser) { if ((string) $xuser['username'] == $username) { $email = (string) $xuser['email']; } } } } if (!empty($email)) { $user = User::get_from_email($email); } if (!isset($user) || !$user->id) { debug_event('Access Denied', 'Unable to get an Ampache user match for email ' . $email, '3'); self::createError(401); } else { $username = $user->username; if (!Access::check_network('init-api', $username, 5)) { debug_event('Access Denied', 'Unauthorized access attempt to Plex [' . $_SERVER['REMOTE_ADDR'] . ']', '3'); self::createError(401); } else { $GLOBALS['user'] = $user; $GLOBALS['user']->load_playlist(); } } } else { $email = $username; $username = null; $GLOBALS['user'] = new User(); $GLOBALS['user']->load_playlist(); } if ($createSession) { // Create an Ampache session from Plex authtoken Session::create(array('type' => 'api', 'sid' => $myplex_token, 'username' => $username, 'value' => $email)); } } else { AmpConfig::set('cookie_path', '/', true); $sid = $_COOKIE[AmpConfig::get('session_name')]; if (!$sid) { $sid = $myplex_token; if ($sid) { session_id($sid); Session::create_cookie(); } } if (!empty($sid) && Session::exists('api', $sid)) { Session::check(); $GLOBALS['user'] = User::get_from_username($_SESSION['userdata']['username']); } else { $GLOBALS['user'] = new User(); $data = array('type' => 'api', 'sid' => $sid); Session::create($data); Session::check(); } $GLOBALS['user']->load_playlist(); } }
echo XML_Data::error('501', T_('Access Control not Enabled')); exit; } /** * Verify the existance of the Session they passed in we do allow them to * login via this interface so we do have an exception for action=login */ if (!Session::exists('api', $_REQUEST['auth']) and $_REQUEST['action'] != 'handshake' and $_REQUEST['action'] != 'ping') { debug_event('Access Denied', 'Invalid Session attempt to API [' . $_REQUEST['action'] . ']', '3'); ob_end_clean(); echo XML_Data::error('401', T_('Session Expired')); exit; } // If the session exists then let's try to pull some data from it to see if we're still allowed to do this $username = $_REQUEST['action'] == 'handshake' || $_REQUEST['action'] == 'ping' ? $_REQUEST['user'] : Session::username($_REQUEST['auth']); if (!Access::check_network('init-api', $username, 5)) { debug_event('Access Denied', 'Unauthorized access attempt to API [' . $_SERVER['REMOTE_ADDR'] . ']', '3'); ob_end_clean(); echo XML_Data::error('403', T_('Unauthorized access attempt to API - ACL Error')); exit; } if ($_REQUEST['action'] != 'handshake' and $_REQUEST['action'] != 'ping') { Session::extend($_REQUEST['auth']); $GLOBALS['user'] = User::get_from_username($username); } // Get the list of possible methods for the Ampache API $methods = get_class_methods('api'); // Define list of internal functions that should be skipped $internal_functions = array('set_filter'); // Recurse through them and see if we're calling one of them foreach ($methods as $method) {
/** * handshake * * This is the function that handles verifying a new handshake * Takes a timestamp, auth key, and username. */ public static function handshake($input) { $timestamp = preg_replace('/[^0-9]/', '', $input['timestamp']); $passphrase = $input['auth']; if (empty($passphrase)) { $passphrase = $_POST['auth']; } $username = trim($input['user']); $ip = $_SERVER['REMOTE_ADDR']; $version = $input['version']; // Log the attempt debug_event('API', "Handshake Attempt, IP:{$ip} User:{$username} Version:{$version}", 5); // Version check shouldn't be soo restrictive... only check with initial version to not break clients compatibility if (intval($version) < self::$auth_version) { debug_event('API', 'Login Failed: version too old', 1); Error::add('api', T_('Login Failed: version too old')); return false; } $user_id = -1; // Grab the correct userid if (!$username) { $client = User::get_from_apikey($passphrase); if ($client) { $user_id = $client->id; } } else { $client = User::get_from_username($username); $user_id = $client->id; } // Log this attempt debug_event('API', "Login Attempt, IP:{$ip} Time: {$timestamp} User:{$username} ({$user_id}) Auth:{$passphrase}", 1); if ($user_id > 0 && Access::check_network('api', $user_id, 5, $ip)) { // Authentication with user/password, we still need to check the password if ($username) { // If the timestamp isn't within 30 minutes sucks to be them if ($timestamp < time() - 1800 || $timestamp > time() + 1800) { debug_event('API', 'Login Failed: timestamp out of range ' . $timestamp . '/' . time(), 1); Error::add('api', T_('Login Failed: timestamp out of range')); return false; } // Now we're sure that there is an ACL line that matches // this user or ALL USERS, pull the user's password and // then see what we come out with $realpwd = $client->get_password(); if (!$realpwd) { debug_event('API', 'Unable to find user with userid of ' . $user_id, 1); Error::add('api', T_('Invalid Username/Password')); return false; } $sha1pass = hash('sha256', $timestamp . $realpwd); if ($sha1pass !== $passphrase) { $client = null; } } else { $timestamp = time(); } if ($client) { // Create the session $data = array(); $data['username'] = $client->username; $data['type'] = 'api'; $data['value'] = $timestamp; $token = Session::create($data); debug_event('API', 'Login Success, passphrase matched', 1); // We need to also get the 'last update' of the // catalog information in an RFC 2822 Format $sql = 'SELECT MAX(`last_update`) AS `update`, MAX(`last_add`) AS `add`, MAX(`last_clean`) AS `clean` FROM `catalog`'; $db_results = Dba::read($sql); $row = Dba::fetch_assoc($db_results); // Now we need to quickly get the song totals $sql = 'SELECT COUNT(`id`) AS `song`, ' . 'COUNT(DISTINCT(`album`)) AS `album`, ' . 'COUNT(DISTINCT(`artist`)) AS `artist` ' . 'FROM `song`'; $db_results = Dba::read($sql); $counts = Dba::fetch_assoc($db_results); // Next the video counts $sql = "SELECT COUNT(`id`) AS `video` FROM `video`"; $db_results = Dba::read($sql); $vcounts = Dba::fetch_assoc($db_results); $sql = "SELECT COUNT(`id`) AS `playlist` FROM `playlist`"; $db_results = Dba::read($sql); $playlist = Dba::fetch_assoc($db_results); $sql = "SELECT COUNT(`id`) AS `catalog` FROM `catalog` WHERE `catalog_type`='local'"; $db_results = Dba::read($sql); $catalog = Dba::fetch_assoc($db_results); echo XML_Data::keyed_array(array('auth' => $token, 'api' => self::$version, 'session_expire' => date("c", time() + AmpConfig::get('session_length') - 60), 'update' => date("c", $row['update']), 'add' => date("c", $row['add']), 'clean' => date("c", $row['clean']), 'songs' => $counts['song'], 'albums' => $counts['album'], 'artists' => $counts['artist'], 'playlists' => $playlist['playlist'], 'videos' => $vcounts['video'], 'catalogs' => $catalog['catalog'])); return true; } // match } // end while debug_event('API', 'Login Failed, unable to match passphrase', '1'); XML_Data::error('401', T_('Error Invalid Handshake - ') . T_('Invalid Username/Password')); }
} else { fpassthru($fp); } fclose($fp); exit; } // if they are trying to download and they can // Prevent the script from timing out set_time_limit(0); // We're about to start. Record this user's IP. if (AmpConfig::get('track_user_ip')) { $GLOBALS['user']->insert_ip_history(); } $force_downsample = false; if (AmpConfig::get('downsample_remote')) { if (!Access::check_network('network', $GLOBALS['user']->id, '0')) { debug_event('play', 'Downsampling enabled for non-local address ' . $_SERVER['REMOTE_ADDR'], 5); $force_downsample = true; } } debug_event('play', 'Playing file (' . $media->file . '}...', 5); debug_event('play', 'Media type {' . $media->type . '}', 5); $cpaction = $_REQUEST['custom_play_action']; // Determine whether to transcode $transcode = false; // transcode_to should only have an effect if the media is the wrong format $transcode_to = $transcode_to == $media->type ? null : $transcode_to; debug_event('play', 'Custom play action {' . $cpaction . '}', 5); debug_event('play', 'Transcode to {' . $transcode_to . '}', 5); // If custom play action, do not try to transcode if (!$cpaction) {
* along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ /* We have to create a cookie here because IIS * can't handle Cookie + Redirect */ Session::create_cookie(); Preference::init(); /** * If Access Control is turned on then we don't * even want them to be able to get to the login * page if they aren't in the ACL */ if (AmpConfig::get('access_control')) { if (!Access::check_network('interface', '', '5')) { debug_event('UI::access_denied', 'Access Denied:' . $_SERVER['REMOTE_ADDR'] . ' is not in the Interface Access list', '3'); UI::access_denied(); exit; } } // access_control is enabled /* Clean Auth values */ unset($auth); if (empty($_REQUEST['step'])) { /* Check for posted username and password, or appropriate environment variable if using HTTP auth */ if ($_POST['username'] || in_array('http', AmpConfig::get('auth_methods')) && ($_SERVER['REMOTE_USER'] || $_SERVER['HTTP_REMOTE_USER'])) { /* If we are in demo mode let's force auth success */ if (AmpConfig::get('demo_mode')) { $auth['success'] = true; $auth['info']['username'] = '******';
exit; } // Authenticate the user here if ($channel->is_private) { $is_auth = false; if (isset($_SERVER['PHP_AUTH_USER'])) { $htusername = $_SERVER['PHP_AUTH_USER']; $htpassword = $_SERVER['PHP_AUTH_PW']; $auth = Auth::login($htusername, $htpassword); if ($auth['success']) { $username = $auth['username']; $GLOBALS['user'] = new User($username); $is_auth = true; Preference::init(); if (AmpConfig::get('access_control')) { if (!Access::check_network('stream', $GLOBALS['user']->id, '25') and !Access::check_network('network', $GLOBALS['user']->id, '25')) { debug_event('UI::access_denied', "Streaming Access Denied: " . $_SERVER['REMOTE_ADDR'] . " does not have stream level access", '3'); UI::access_denied(); exit; } } } } if (!$is_auth) { header('WWW-Authenticate: Basic realm="Ampache Channel Authentication"'); header('HTTP/1.0 401 Unauthorized'); echo T_('Unauthorized.'); exit; } } $url = 'http://' . $channel->interface . ':' . $channel->port . '/' . $_REQUEST['target'];