'User SSH key auth', 'page callback' => 'drupalorg_git_varnish_ssh_user_key', 'access callback' => 'drupalorg_git_varnish_access_callback', // we're restricting in apache conf, this is always TRUE 'type' => MENU_CALLBACK, ); $items['drupalorg/drupalorg-sshkey-check'] = array( 'title' => 'SSH key verification', 'page callback' => 'drupalorg_git_varnish_sshkey_check', 'access callback' => 'drupalorg_git_varnish_access_callback', // we're restricting in apache conf, this is always TRUE 'type' => MENU_CALLBACK, ); $items['drupalorg/drupalorg-vcs-auth-check-user-pass'] = array( 'title' => 'User pass auth', 'page callback' => 'drupalorg_git_varnish_vcs_auth_check_user_pass', 'access callback' => 'drupalorg_git_varnish_access_callback', // we're restricting in apache conf, this is always TRUE 'type' => MENU_CALLBACK, ); $items['drupalorg/pushctl-state'] = array( 'title' => 'Fetch project vcs auth data', 'page callback' => 'drupalorg_git_varnish_pushctl_state', 'access callback' => 'drupalorg_git_varnish_access_callback', // we're restricting in apache conf, this is always TRUE 'type' => MENU_CALLBACK, ); $items['drupalorg/vcs-auth-data'] = array( 'title' => 'Fetch project vcs auth data', 'page callback' => 'drupalorg_git_varnish_vcs_auth_data', 'access callback' => 'drupalorg_git_varnish_access_callback', // we're restricting in apache conf, this is always TRUE 'type' => MENU_CALLBACK, ); return $items; } /** * Only allow any of this data to be published if this drupal instance has the * internal flag set. * @return bool */ function drupalorg_git_varnish_access_callback() { if (variable_get('drupalorg_is_internal_varnish_instance', FALSE)) { return TRUE; } return FALSE; } function drupalorg_git_varnish_pushctl_state() { echo (int) variable_get('drupalorg_git_gateway_pushctl', 0); exit; } function drupalorg_git_varnish_sshkey_check() { $fingerprint = $_GET['fingerprint']; if ($key = sshkey_load_by_fingerprint($fingerprint)) { if ($key->entity_type == 'user') { $user = user_load($key->entity_id); if (!empty($user->roles[DRUPALORG_GIT_GATEWAY_RID])) { echo 'true'; exit; } } } echo 'false'; exit; } function drupalorg_git_varnish_ssh_user_key() { $username = $_GET['username']; $fingerprint = $_GET['fingerprint']; if ($key = sshkey_load_by_fingerprint($fingerprint)) { if ($username == user_load($key->entity_id)->git_username) { echo 'true'; exit; } } echo 'false'; exit; } function drupalorg_git_varnish_vcs_auth_check_user_pass() { $username = $_GET['username']; $password = $_GET['password']; $account = user_load(array('git_username' => $username)); if (!empty($account) && $password == $account->pass) { echo 'true'; exit; } echo 'false'; exit; } function drupalorg_git_varnish_vcs_auth_data() { $project_uri = $_GET['project_uri']; echo json_encode(versioncontrol_project_get_auth_data($project_uri)); exit; } function drupalorg_git_varnish_user($op, &$edit, &$account, $category = NULL) { if ($op == 'after_update' && !empty($account->git_username)) { // Clear the cache for both ssh keys and passwords, just to be safe $host = _drupalorg_git_varnish_get_host(); $commands = drupalorg_git_varnish_clear_user_cmds($account->uid); $commands[] = "purge req.http.host == $host && req.url ~ ^/drupalorg/drupalorg-[a-z-]+.username={$account->git_username}&(password|fingerprint)="; drupalorg_git_varnish_exec($commands); } } function drupalorg_git_varnish_clear_user_cmds($uid) { $commands = array(); // FIXME this needs improving when sandboxes get properly namespaced. $select = db_select('project_projects', 'p')->fields('p', array('uri')); $vpp_alias = $select->addJoin('INNER', 'versioncontrol_project_projects', 'vpp', 'p.nid = vpp.nid'); $vaa_alias = $select->addJoin('INNER', 'versioncontrol_auth_account', 'vaa', $vpp_alias . '.repo_id = vaa.repo_id'); $uris = $select->condition($vaa_alias . '.uid', $uid) ->execute()->fetchAll(PDO::FETCH_COLUMN); $host = _drupalorg_git_varnish_get_host(); $purge_base = "purge req.http.host == $host && "; foreach ($uris as $uri) { $commands[] = $purge_base . "req.url ~ ^/drupalorg/vcs-auth-data.project_uri=$uri$"; } return $commands; } function drupalorg_git_varnish_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) { if (in_array($op, array('update', 'insert', 'delete')) && in_array($node->type, array('project_project', 'project_release'))) { if ($node->type == 'project_project') { $uri = $node->project['uri']; } else if ($node->type == 'project_release') { $uri = db_result(db_query('SELECT uri FROM {project_projects} WHERE nid = %d', $node->project_release['pid'])); } $host = _drupalorg_git_varnish_get_host(); $commands = array( 'purge req.http.host == ' . $host . ' && req.url ~ ^/drupalorg/vcs-auth-data.project_uri=' . $uri . '$', ); drupalorg_git_varnish_exec($commands); } } function drupalorg_git_varnish_drupalorg_pushctl_changed($pushctl) { $host = _drupalorg_git_varnish_get_host(); $command = "purge req.http.host == $host && req.url == /drupalorg/pushctl-state"; drupalorg_git_varnish_exec(array($command)); } function drupalorg_git_varnish_sshkey_insert($key) { $commands = drupalorg_git_varnish_clear_sshkey_cmds($key); drupalorg_git_varnish_exec($commands); } function drupalorg_git_varnish_sshkey_update($key) { $commands = drupalorg_git_varnish_clear_sshkey_cmds($key); drupalorg_git_varnish_exec($commands); } function drupalorg_git_varnish_sshkey_delete($key) { $commands = drupalorg_git_varnish_clear_sshkey_cmds($key); drupalorg_git_varnish_exec($commands); } function drupalorg_git_varnish_clear_sshkey_cmds($key) { if ($key->entity_type == 'user') { $commands = drupalorg_git_varnish_clear_user_cmds($key->entity_id); } $host = _drupalorg_git_varnish_get_host(); $commands[] = "purge req.http.host == $host && req.url ~ ^/drupalorg/drupalorg-ssh.+fingerprint={$key->fingerprint}"; return $commands; } function drupalorg_git_varnish_project_promote_sandbox($project) { // This will probably cause some redundant cache clearing, but oh well. $host = _drupalorg_git_varnish_get_host(); $commands = array( "purge req.http.host == $host && req.url ~ ^/drupalorg/vcs-auth-data.project_uri={$project->project['uri']}$", // And also clear the old sandbox uri. "purge req.http.host == $host && req.url ~ ^/drupalorg/vcs-auth-data.project_uri={$project->nid}$", ); drupalorg_git_varnish_exec($commands); } /** * Implements hook_project_maintainer_save(). * * Clear varnish cache for a project when its maintainers list is updated. */ function drupalorg_git_varnish_project_maintainer_save($nid, $uid, $permissions) { _drupalorg_git_varnish_project_maintainer_update($nid); } /** * Implements hook_project_maintainer_remove(). * * Clear varnish cache for a project when its maintainers list is updated. */ function drupalorg_git_varnish_project_maintainer_remove($nid, $uid) { _drupalorg_git_varnish_project_maintainer_update($nid); } function _drupalorg_git_varnish_project_maintainer_update($nid) { static $nids = array(); // This function is likely to get hit multiple times on a given operation, so // we ensure it only runs - only one purge is necessary. if (in_array($nid, $nids)) { return; } $nids[] = $nid; $uri = db_result(db_query('SELECT uri FROM {project_projects} WHERE nid = %d', $nid)); $host = _drupalorg_git_varnish_get_host(); $commands = array( 'purge req.http.host == ' . $host . ' && req.url ~ ^/drupalorg/vcs-auth-data.project_uri=' . $uri . '$', ); drupalorg_git_varnish_exec($commands); } /** * Help[er function to parse the host from the global $base_url */ function _drupalorg_git_varnish_get_host() { return variable_get('drupalorg_git_varnish_http.host', 'git-dev.drupal.org:8080'); } function _drupalorg_git_varnish_get_connection_info() { $defaults = array( 'host' => '127.0.0.1', 'port' => 6082, 'version' => '2.1', ); return variable_get('drupalorg_git_varnish_connection_info', $defaults); } /** * Connect to our varnish cache and send an arbitrary number of commands. * * Unceremoniously pilfered from varnish.module, with minor adaptations. */ function drupalorg_git_varnish_exec($commands) { if (!extension_loaded('sockets')) { // Prevent fatal errors if people don't have requirements. return FALSE; } $ret = array(); $client = socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp')); $conn_info = _drupalorg_git_varnish_get_connection_info(); if (!socket_connect($client, $conn_info['host'], $conn_info['port'])) { watchdog('varnish', 'Unable to connect to server socket !server:!port', array('!server' => $server, '!port' => $port), WATCHDOG_ERROR); $ret[] = FALSE; return $ret; } socket_set_option($client, SOL_SOCKET, SO_RCVTIMEO, array('sec'=>0, 'usec'=>500)); if (version_compare($conn_info['version'], '2.1', 'ge')) { // If there is a CLI banner message (varnish >= 2.1.x), try to read it and move on. $status = _drupalorg_git_varnish_read_socket($client); } foreach ($commands as $command) { // Send command and get response. $result = socket_write($client, "$command\n"); $status = _drupalorg_git_varnish_read_socket($client); if ($status['code'] != 200) { watchdog('varnish', 'Recieved status code !code running %command. Full response text: !error', array('!code' => $status['code'], '%command' => $command, '!error' => $status['msg']), WATCHDOG_ERROR); $ret[] = FALSE; } else { // successful connection $ret[] = $status; } } socket_close($client); return $ret; } function _drupalorg_git_varnish_read_socket($client) { // status and length info is always 13 characters. $header = socket_read($client, 13, PHP_BINARY_READ); if ($header == FALSE) { $error = socket_last_error(); watchdog('varnish', 'Socket error: !error', array('!error' => socket_strerror($error)), WATCHDOG_ERROR); } $msg_len = (int)substr($header,4,6) + 1; $status = array( 'code' => substr($header,0,3), 'msg' => socket_read($client, $msg_len, PHP_BINARY_READ) ); return $status; }