Created
April 27, 2015 16:52
-
-
Save kohsuke/70cd59a216a26a379898 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2007,2008,2009,2010 Peter Poeml / Novell Inc. | |
* All rights reserved. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
* | |
* mod_mirrorbrain is the heart of MirrorBrain, which | |
* - redirects clients to mirror servers, based on an SQL database | |
* - generates metalinks in real-time | |
* - generates per-file HTML mirror lists | |
* - acts as a server for verification hashes | |
* See http://mirrorbrain.org/ for more information. | |
* | |
* Credits: | |
* | |
* This module was inspired by mod_offload, written by | |
* Ryan C. Gordon <icculus@icculus.org>. | |
* | |
* It uses code from mod_authn_dbd, mod_authnz_ldap, mod_status, | |
* apr_memcache, ssl_scache_memcache.c */ | |
/* Copyright notice for the hex_decode() function | |
* | |
* Copyright (c) 2001-2009, PostgreSQL Global Development Group | |
* | |
* PostgreSQL Database Management System | |
* (formerly known as Postgres, then as Postgres95) | |
* | |
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group | |
* | |
* Portions Copyright (c) 1994, The Regents of the University of California | |
* | |
* Permission to use, copy, modify, and distribute this software and its | |
* documentation for any purpose, without fee, and without a written agreement | |
* is hereby granted, provided that the above copyright notice and this | |
* paragraph and the following two paragraphs appear in all copies. | |
* | |
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR | |
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING | |
* LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS | |
* DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE | |
* POSSIBILITY OF SUCH DAMAGE. | |
* | |
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, | |
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY | |
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS | |
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO | |
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ | |
#include "ap_config.h" | |
#include "httpd.h" | |
#include "http_request.h" | |
#include "http_config.h" | |
#include "http_core.h" | |
#include "http_log.h" | |
#include "http_main.h" | |
#include "http_protocol.h" | |
#include "util_md5.h" | |
#include "apr_version.h" | |
#include "apu_version.h" | |
#include "apr_strings.h" | |
#include "apr_lib.h" | |
#include "apr_fnmatch.h" | |
#include "apr_hash.h" | |
#include "apr_dbd.h" | |
#include "mod_dbd.h" | |
#include <unistd.h> /* for getpid */ | |
#include <math.h> /* for sqrt() and pow() */ | |
#include <arpa/inet.h> | |
#ifdef WITH_MEMCACHE | |
#include "mod_memcache.h" | |
#include "apr_memcache.h" | |
#endif | |
#include "ap_mpm.h" /* for ap_mpm_query */ | |
#include "mod_status.h" | |
#include "mod_form.h" | |
#define wild_match(p,s) (apr_fnmatch(p,s,APR_FNM_CASE_BLIND) == APR_SUCCESS) | |
/* from ssl/ssl_engine_config.c */ | |
#define cfgMerge(el,unset) mrg->el = (add->el == (unset)) ? base->el : add->el | |
#define cfgMergeArray(el) mrg->el = apr_array_append(p, add->el, base->el) | |
#define cfgMergeString(el) cfgMerge(el, NULL) | |
#define cfgMergeBool(el) cfgMerge(el, UNSET) | |
#define cfgMergeInt(el) cfgMerge(el, UNSET) | |
#ifndef UNSET | |
#define UNSET (-1) | |
#endif | |
/* available since APR 1.3 */ | |
#ifndef APR_ARRAY_IDX | |
#define APR_ARRAY_IDX(ary,i,type) (((type *)(ary)->elts)[i]) | |
#endif | |
#ifndef APR_ARRAY_PUSH | |
#define APR_ARRAY_PUSH(ary,type) (*((type *)apr_array_push(ary))) | |
#endif | |
#define MOD_MIRRORBRAIN_VER "2.15.0" | |
#define VERSION_COMPONENT "mod_mirrorbrain/"MOD_MIRRORBRAIN_VER | |
/* no space for time zones is provided here */ | |
#define RFC3339_DATE_LEN (21) | |
#define MD5_DIGESTSIZE 16 | |
#define SHA1_DIGESTSIZE 20 | |
#define SHA256_DIGESTSIZE 32 | |
#ifdef WITH_MEMCACHE | |
#define DEFAULT_MEMCACHED_LIFETIME 600 | |
#endif | |
#define DEFAULT_MIN_MIRROR_SIZE 4096 | |
#if (APR_MAJOR_VERSION == 1 && APR_MINOR_VERSION == 2) | |
#define DBD_LLD_FMT "d" | |
#else | |
#define DBD_LLD_FMT "lld" | |
#endif | |
#define DEFAULT_QUERY "SELECT id, identifier, region, country, " \ | |
"lat, lng, " \ | |
"asn, prefix, score, baseurl, " \ | |
"region_only, country_only, " \ | |
"as_only, prefix_only, " \ | |
"other_countries, file_maxsize " \ | |
"FROM server " \ | |
"WHERE id::smallint = any(" \ | |
"(SELECT mirrors " \ | |
"FROM filearr " \ | |
"WHERE path = %s)::smallint[]) " \ | |
"AND enabled AND status_baseurl AND score > 0" | |
#define DEFAULT_QUERY_HASH "SELECT file_id, md5hex, sha1hex, sha256hex, " \ | |
"sha1piecesize, sha1pieceshex, btihhex, pgp, " \ | |
"zblocksize, zhashlens, zsumshex " \ | |
"FROM hexhash " \ | |
"WHERE file_id = (SELECT id " \ | |
"FROM filearr " \ | |
"WHERE path = %s) " \ | |
"AND size = %" DBD_LLD_FMT " " \ | |
"AND mtime = %" DBD_LLD_FMT " " \ | |
"LIMIT 1" | |
/* the smaller, the smaller the effect of a raised prio in comparison to distance */ | |
/* 5000000 -> mirror in 200km distance is preferred already when it has prio 100 | |
* 1000000 -> mirror in 200km distance is preferred not before it has prio 300 | |
* (compared to 100 as normal priority for other mirrors, and tested in | |
* Germany, which is a small country with many mirrors) */ | |
#define DISTANCE_PRIO 2000000 | |
module AP_MODULE_DECLARE_DATA mirrorbrain_module; | |
/* (meta) representations of a requested file */ | |
enum { REDIRECT, META4, METALINK, MIRRORLIST, TORRENT, | |
ZSYNC, MAGNET, MD5, SHA1, SHA256, BTIH, YUMLIST, UNKNOWN }; | |
static struct { | |
int id; | |
char *ext; | |
} reps [] = { | |
{ REDIRECT, "" }, | |
{ META4, "meta4" }, | |
{ METALINK, "metalink" }, | |
{ MIRRORLIST, "mirrorlist" }, | |
{ TORRENT, "torrent" }, | |
{ ZSYNC, "zsync" }, | |
{ MAGNET, "magnet" }, | |
{ MD5, "md5" }, | |
{ SHA1, "sha1" }, | |
{ SHA256, "sha256" }, | |
{ BTIH, "btih" }, | |
{ YUMLIST, "yumlist" }, | |
{ UNKNOWN, NULL } | |
}; | |
/* A structure that represents a mirror */ | |
typedef struct mirror_entry mirror_entry_t; | |
/* a mirror */ | |
struct mirror_entry { | |
int id; | |
const char *identifier; | |
const char *region; /* 2-letter-string */ | |
const char *country_code; /* 2-letter-string */ | |
float lat; /* geographical latitude */ | |
float lng; /* geographical longitude */ | |
int dist; /* geographical distance to client */ | |
const char *as; /* autonomous system number as string */ | |
const char *prefix; /* network prefix xxx.xxx.xxx.xxx/yy */ | |
apr_ipsubnet_t *ipsub; /* ip-subnet representation of the network prefix */ | |
unsigned char region_only; | |
unsigned char country_only; | |
unsigned char as_only; | |
unsigned char prefix_only; | |
int score; | |
const char *baseurl; | |
apr_off_t file_maxsize; | |
char *other_countries; /* comma-separated 2-letter strings */ | |
int rank; | |
int *nsame; /* to be able to access the number of elements in | |
the array from qsort() */ | |
}; | |
/* verification hashes of a file */ | |
typedef struct hashbag hashbag_t; | |
struct hashbag { | |
apr_off_t id; | |
const char *md5hex; | |
const char *sha1hex; | |
const char *sha256hex; | |
int sha1piecesize; | |
apr_array_header_t *sha1pieceshex; | |
const char *btihhex; | |
const char *pgp; | |
int zblocksize; | |
const char *zhashlens; | |
const char *zsumshex; | |
}; | |
/* per-dir configuration */ | |
typedef struct | |
{ | |
int engine_on; | |
int debug; | |
apr_off_t min_size; | |
int handle_headrequest_locally; | |
const char *mirror_base; | |
apr_array_header_t *fallbacks; | |
apr_array_header_t *exclude_mime; | |
apr_array_header_t *exclude_agents; | |
apr_array_header_t *exclude_networks; | |
apr_array_header_t *exclude_ips; | |
ap_regex_t *exclude_filemask; | |
ap_regex_t *metalink_torrentadd_mask; | |
const char *stampkey; | |
} mb_dir_conf; | |
/* per-server configuration */ | |
typedef struct | |
{ | |
#ifdef WITH_MEMCACHE | |
const char *instance; | |
int memcached_on; | |
int memcached_lifetime; | |
#endif | |
const char *metalink_hashes_prefix; | |
const char *metalink_publisher_name; | |
const char *metalink_publisher_url; | |
apr_array_header_t *tracker_urls; | |
apr_array_header_t *dhtnodes; | |
const char *metalink_broken_test_mirrors; | |
int metalink_magnets; | |
apr_array_header_t *yumdirs; | |
const char *mirrorlist_stylesheet; | |
const char *mirrorlist_header; | |
const char *mirrorlist_footer; | |
int only_hash; | |
const char *query; | |
const char *query_label; | |
const char *query_hash; | |
const char *query_hash_label; | |
} mb_server_conf; | |
typedef struct dhtnode dhtnode_t; | |
struct dhtnode { | |
char *name; | |
int port; | |
}; | |
typedef struct yumdir yumdir_t; | |
struct yumdir { | |
char *dir; /* base dir */ | |
char *file; /* marker file within base dir */ | |
apr_array_header_t *args; /* query arguments */ | |
}; | |
typedef struct yumarg yumarg_t; | |
struct yumarg { | |
char *key; | |
ap_regex_t *regexp; | |
}; | |
static ap_dbd_t *(*mb_dbd_acquire_fn)(request_rec*) = NULL; | |
static void (*mb_dbd_prepare_fn)(server_rec*, const char*, const char*) = NULL; | |
static apr_version_t vsn; | |
static int dbd_first_row; | |
static void debugLog(const request_rec *r, const mb_dir_conf *cfg, | |
const char *fmt, ...) | |
{ | |
if (cfg->debug == 1) { | |
char buf[512]; | |
va_list ap; | |
va_start(ap, fmt); | |
apr_vsnprintf(buf, sizeof (buf), fmt, ap); | |
va_end(ap); | |
/* we use warn loglevel to be able to debug without | |
* setting the entire server into debug logging mode */ | |
ap_log_rerror(APLOG_MARK, | |
APLOG_WARNING, | |
APR_SUCCESS, | |
r, "[mod_mirrorbrain] %s", buf); | |
} | |
} | |
static apr_status_t mb_cleanup() | |
{ | |
return APR_SUCCESS; | |
} | |
static void mb_child_init(apr_pool_t *p, server_rec *s) | |
{ | |
apr_pool_cleanup_register(p, NULL, mb_cleanup, mb_cleanup); | |
srand((unsigned int)getpid()); | |
} | |
static int mb_post_config(apr_pool_t *pconf, apr_pool_t *plog, | |
apr_pool_t *ptemp, server_rec *s) | |
{ | |
apr_version(&vsn); | |
ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, | |
"[mod_mirrorbrain] compiled with APR/APR-Util %s/%s", | |
APR_VERSION_STRING, APU_VERSION_STRING); | |
if ((vsn.major == 1) && (vsn.minor == 2)) { | |
/* database access semantics were changed between 1.2 and 1.3 (strictly | |
* speaking, breaking the binary compatibility */ | |
dbd_first_row = 0; | |
} else { | |
dbd_first_row = 1; | |
} | |
/* be visible in the server signature */ | |
ap_add_version_component(pconf, VERSION_COMPONENT); | |
/* make sure that mod_form is loaded */ | |
if (ap_find_linked_module("mod_form.c") == NULL) { | |
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, | |
"[mod_mirrorbrain] Module mod_form missing. It must be " | |
"loaded in order for mod_mirrorbrain to function properly"); | |
return HTTP_INTERNAL_SERVER_ERROR; | |
} | |
/* make sure that mod_dbd is loaded */ | |
if (mb_dbd_prepare_fn == NULL) { | |
mb_dbd_prepare_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare); | |
if (mb_dbd_prepare_fn == NULL) { | |
ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, | |
"[mod_mirrorbrain] You must load mod_dbd to enable MirrorBrain functions"); | |
return HTTP_INTERNAL_SERVER_ERROR; | |
} | |
mb_dbd_acquire_fn = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire); | |
} | |
/* prepare DBD SQL statements */ | |
static unsigned int label_num = 0; | |
server_rec *sp; | |
for (sp = s; sp; sp = sp->next) { | |
mb_server_conf *cfg = ap_get_module_config(sp->module_config, | |
&mirrorbrain_module); | |
/* make a label */ | |
cfg->query_label = apr_psprintf(pconf, "mirrorbrain_dbd_%d", ++label_num); | |
cfg->query_hash_label = apr_psprintf(pconf, "mirrorbrain_dbd_hash_%d", ++label_num); | |
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, | |
"[mod_mirrorbrain] preparing stmt for server %s, label_num %d, label %s", | |
s->server_hostname, label_num, cfg->query_label); | |
mb_dbd_prepare_fn(sp, cfg->query, cfg->query_label); | |
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, | |
"[mod_mirrorbrain] preparing stmt for server %s, label_num %d, label %s", | |
s->server_hostname, label_num, cfg->query_hash_label); | |
mb_dbd_prepare_fn(sp, cfg->query_hash, cfg->query_hash_label); | |
} | |
return OK; | |
} | |
static void *create_mb_dir_config(apr_pool_t *p, char *dirspec) | |
{ | |
mb_dir_conf *new = | |
(mb_dir_conf *) apr_pcalloc(p, sizeof(mb_dir_conf)); | |
new->engine_on = UNSET; | |
new->debug = UNSET; | |
new->min_size = DEFAULT_MIN_MIRROR_SIZE; | |
new->handle_headrequest_locally = 0; | |
new->mirror_base = NULL; | |
new->fallbacks = apr_array_make(p, 10, sizeof (mirror_entry_t)); | |
new->exclude_mime = apr_array_make(p, 0, sizeof (char *)); | |
new->exclude_agents = apr_array_make(p, 0, sizeof (char *)); | |
new->exclude_networks = apr_array_make(p, 4, sizeof (char *)); | |
new->exclude_ips = apr_array_make(p, 4, sizeof (char *)); | |
new->exclude_filemask = NULL; | |
new->metalink_torrentadd_mask = NULL; | |
new->stampkey = NULL; | |
return (void *) new; | |
} | |
static void *merge_mb_dir_config(apr_pool_t *p, void *basev, void *addv) | |
{ | |
mb_dir_conf *mrg = (mb_dir_conf *) apr_pcalloc(p, sizeof(mb_dir_conf)); | |
mb_dir_conf *base = (mb_dir_conf *) basev; | |
mb_dir_conf *add = (mb_dir_conf *) addv; | |
/* debugLog("merge_mb_dir_config: new=%08lx base=%08lx overrides=%08lx", | |
* (long)mrg, (long)base, (long)add); */ | |
cfgMergeInt(engine_on); | |
cfgMergeInt(debug); | |
mrg->min_size = (add->min_size != DEFAULT_MIN_MIRROR_SIZE) ? add->min_size : base->min_size; | |
cfgMergeInt(handle_headrequest_locally); | |
cfgMergeString(mirror_base); | |
/* inheriting makes sense. But does inheriting also make sense if an | |
* inheriting directory has its own fallback mirror directives? */ | |
/* mrg->fallbacks = apr_is_empty_array(add->fallbacks) ? base->fallbacks : add->fallbacks; */ | |
/* it's a merge for now */ | |
mrg->fallbacks = apr_array_append(p, base->fallbacks, add->fallbacks); | |
mrg->exclude_mime = apr_array_append(p, base->exclude_mime, add->exclude_mime); | |
mrg->exclude_agents = apr_array_append(p, base->exclude_agents, add->exclude_agents); | |
mrg->exclude_networks = apr_array_append(p, base->exclude_networks, add->exclude_networks); | |
mrg->exclude_ips = apr_array_append(p, base->exclude_ips, add->exclude_ips); | |
mrg->exclude_filemask = (add->exclude_filemask == NULL) ? base->exclude_filemask : add->exclude_filemask; | |
mrg->metalink_torrentadd_mask = (add->metalink_torrentadd_mask == NULL) ? base->metalink_torrentadd_mask : add->metalink_torrentadd_mask; | |
cfgMergeString(stampkey); | |
return (void *) mrg; | |
} | |
static void *create_mb_server_config(apr_pool_t *p, server_rec *s) | |
{ | |
mb_server_conf *new = | |
(mb_server_conf *) apr_pcalloc(p, sizeof(mb_server_conf)); | |
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, | |
"[mod_mirrorbrain] creating server config"); | |
#ifdef WITH_MEMCACHE | |
new->instance = "default"; | |
new->memcached_on = UNSET; | |
new->memcached_lifetime = UNSET; | |
#endif | |
new->metalink_hashes_prefix = NULL; | |
new->metalink_publisher_name = NULL; | |
new->metalink_publisher_url = NULL; | |
new->tracker_urls = apr_array_make(p, 5, sizeof (char *)); | |
new->dhtnodes = apr_array_make(p, 5, sizeof (dhtnode_t)); | |
new->metalink_broken_test_mirrors = NULL; | |
new->metalink_magnets = UNSET; | |
new->yumdirs = apr_array_make(p, 10, sizeof (yumdir_t)); | |
new->mirrorlist_stylesheet = NULL; | |
new->mirrorlist_header = NULL; | |
new->mirrorlist_footer = NULL; | |
new->only_hash = UNSET; | |
new->query = DEFAULT_QUERY; | |
new->query_label = NULL; | |
new->query_hash = DEFAULT_QUERY_HASH; | |
new->query_hash_label = NULL; | |
return (void *) new; | |
} | |
static void *merge_mb_server_config(apr_pool_t *p, void *basev, void *addv) | |
{ | |
mb_server_conf *base = (mb_server_conf *) basev; | |
mb_server_conf *add = (mb_server_conf *) addv; | |
mb_server_conf *mrg = apr_pcalloc(p, sizeof(mb_server_conf)); | |
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, | |
"[mod_mirrorbrain] merging server config"); | |
#ifdef WITH_MEMCACHE | |
cfgMergeString(instance); | |
cfgMergeBool(memcached_on); | |
cfgMergeInt(memcached_lifetime); | |
#endif | |
cfgMergeString(metalink_hashes_prefix); | |
cfgMergeString(metalink_publisher_name); | |
cfgMergeString(metalink_publisher_url); | |
mrg->tracker_urls = apr_array_append(p, base->tracker_urls, add->tracker_urls); | |
mrg->dhtnodes = apr_array_append(p, base->dhtnodes, add->dhtnodes); | |
cfgMergeString(metalink_broken_test_mirrors); | |
cfgMergeBool(metalink_magnets); | |
mrg->yumdirs = apr_array_append(p, base->yumdirs, add->yumdirs); | |
cfgMergeString(mirrorlist_stylesheet); | |
cfgMergeString(mirrorlist_header); | |
cfgMergeString(mirrorlist_footer); | |
cfgMergeBool(only_hash); | |
mrg->query = (add->query != (char *) DEFAULT_QUERY) ? add->query : base->query; | |
cfgMergeString(query_label); | |
mrg->query_hash = (add->query_hash != (char *) DEFAULT_QUERY_HASH) | |
? add->query_hash : base->query_hash; | |
cfgMergeString(query_hash_label); | |
return (void *) mrg; | |
} | |
static const char *mb_cmd_engine(cmd_parms *cmd, void *config, int flag) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->engine_on = flag; | |
cfg->mirror_base = apr_pstrdup(cmd->pool, cmd->path); | |
return NULL; | |
} | |
static const char *mb_cmd_debug(cmd_parms *cmd, void *config, int flag) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->debug = flag; | |
return NULL; | |
} | |
static const char *mb_cmd_minsize(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->min_size = apr_atoi64(arg1); | |
if (cfg->min_size < 0) | |
return "MirrorBrainMinSize requires a non-negative integer."; | |
return NULL; | |
} | |
static const char *mb_cmd_fallback(cmd_parms *cmd, void *config, | |
const char *arg1, const char *arg2, | |
const char *arg3) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
mirror_entry_t *new; | |
apr_uri_t uri; | |
if (APR_SUCCESS != apr_uri_parse(cmd->pool, arg3, &uri)) { | |
return "MirrorBrainFallback URI cannot be parsed"; | |
} | |
new = apr_array_push(cfg->fallbacks); | |
new->nsame = &cfg->fallbacks->nelts; | |
new->id = 0; | |
new->identifier = uri.hostname; | |
new->region = apr_pstrdup(cmd->pool, arg1); | |
new->country_code = apr_pstrdup(cmd->pool, arg2); | |
new->lat = 0; | |
new->lng = 0; | |
new->dist = 0; | |
new->other_countries = NULL; | |
new->as = NULL; | |
new->prefix = NULL; | |
new->ipsub = NULL; | |
new->region_only = 0; | |
new->country_only = 0; | |
new->as_only = 0; | |
new->prefix_only = 0; | |
new->score = 1; /* give it a minimal score (but with 0, it wouldn't be considered) */ | |
new->file_maxsize = 0; | |
if (arg3[strlen(arg3) - 1] == '/') { | |
new->baseurl = apr_pstrdup(cmd->pool, arg3); | |
} else { | |
new->baseurl = apr_pstrcat(cmd->pool, arg3, "/", NULL); | |
} | |
ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL, | |
"[mod_mirrorbrain] configured fallback mirror (%s:%s): %s", | |
new->region, new->country_code, new->baseurl); | |
return NULL; | |
} | |
static const char *mb_cmd_excludemime(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
char **mimepattern = (char **) apr_array_push(cfg->exclude_mime); | |
*mimepattern = apr_pstrdup(cmd->pool, arg1); | |
return NULL; | |
} | |
static const char *mb_cmd_excludeagent(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
char **agentpattern = (char **) apr_array_push(cfg->exclude_agents); | |
*agentpattern = apr_pstrdup(cmd->pool, arg1); | |
return NULL; | |
} | |
static const char *mb_cmd_excludenetwork(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
char **network = (char **) apr_array_push(cfg->exclude_networks); | |
*network = apr_pstrdup(cmd->pool, arg1); | |
return NULL; | |
} | |
static const char *mb_cmd_excludeip(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
char **ip = (char **) apr_array_push(cfg->exclude_ips); | |
*ip = apr_pstrdup(cmd->pool, arg1); | |
return NULL; | |
} | |
static const char *mb_cmd_exclude_filemask(cmd_parms *cmd, void *config, | |
const char *arg) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->exclude_filemask = ap_pregcomp(cmd->pool, arg, AP_REG_EXTENDED); | |
if (cfg->exclude_filemask == NULL) { | |
return "MirrorBrainExcludeFileMask regex could not be compiled"; | |
} | |
return NULL; | |
} | |
static const char *mb_cmd_handle_dirindex_locally(cmd_parms *cmd, | |
void *config, int flag) | |
{ | |
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, | |
"[mod_mirrorbrain] The MirrorBrainHandleDirectoryIndexLocally " | |
"directive is obsolete. You can remove it from your config."); | |
return NULL; | |
} | |
static const char *mb_cmd_handle_headrequest_locally(cmd_parms *cmd, | |
void *config, int flag) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->handle_headrequest_locally = flag; | |
return NULL; | |
} | |
#ifdef WITH_MEMCACHE | |
static const char *mb_cmd_instance(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->instance = arg1; | |
return NULL; | |
} | |
#endif | |
static const char *mb_cmd_dbd_query(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->query = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_dbd_query_hash(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->query_hash = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_geoip_filename(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
return "mod_mirrorbrain: the GeoIPFilename directive is obsolete. Use mod_geoip."; | |
} | |
static const char *mb_cmd_metalink_hashes_prefix(cmd_parms *cmd, | |
void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->metalink_hashes_prefix = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_metalink_publisher(cmd_parms *cmd, void *config, | |
const char *arg1, | |
const char *arg2) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->metalink_publisher_name = arg1; | |
cfg->metalink_publisher_url = arg2; | |
return NULL; | |
} | |
static const char *mb_cmd_mirrorlist_header(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->mirrorlist_header = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_mirrorlist_footer(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->mirrorlist_footer = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_tracker_url(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
char **url = (char **) apr_array_push(cfg->tracker_urls); | |
*url = apr_pstrdup(cmd->pool, arg1); | |
return NULL; | |
} | |
static const char *mb_cmd_dht_node(cmd_parms *cmd, void *config, | |
const char *arg1, const char *arg2) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
dhtnode_t *new = apr_array_push(cfg->dhtnodes); | |
new->name = apr_pstrdup(cmd->pool, arg1); | |
new->port = atoi(apr_pstrdup(cmd->pool, arg2)); | |
if (new->port <= 0) | |
return "MirrorBrainDHTNode requires a positive integer " | |
"as second argument (server port)."; | |
return NULL; | |
} | |
static const char *mb_cmd_hashes_suppress_filenames(cmd_parms *cmd, void *config, | |
int flag) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->only_hash = flag; | |
return NULL; | |
} | |
static const char *mb_cmd_metalink_broken_test_mirrors(cmd_parms *cmd, | |
void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->metalink_broken_test_mirrors = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_metalink_magnet_links(cmd_parms *cmd, | |
void *config, | |
int flag) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->metalink_magnets = flag; | |
return NULL; | |
} | |
static const char *mb_cmd_mirrorlist_stylesheet(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->mirrorlist_stylesheet = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_metalink_torrentadd_mask(cmd_parms *cmd, void *config, | |
const char *arg) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->metalink_torrentadd_mask = ap_pregcomp(cmd->pool, arg, AP_REG_EXTENDED); | |
if (cfg->metalink_torrentadd_mask == NULL) { | |
return "MirrorBrainMetalinkTorrentAddMask regex could not be compiled"; | |
} | |
return NULL; | |
} | |
static const char *mb_cmd_redirect_stamp_key(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
mb_dir_conf *cfg = (mb_dir_conf *) config; | |
cfg->stampkey = arg1; | |
return NULL; | |
} | |
static const char *mb_cmd_add_yumdir(cmd_parms *cmd, void *dummy, | |
const char *arg) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
char *d = NULL; /* base dir */ | |
char *f = NULL; /* marker file within base dir */ | |
char *word; | |
apr_array_header_t *yumargs = apr_array_make(cmd->pool, 3, sizeof (yumarg_t)); | |
while (*arg) { | |
word = ap_getword_conf(cmd->pool, &arg); | |
char *val = ap_strchr(word, '='); | |
if (!val) { | |
if (!d) { | |
d = word; | |
continue; | |
} else if (!f) { | |
f = word; | |
continue; | |
} else { | |
return "Invalid MirrorBrainYumDir parameter. Parameter must be " | |
"in the form 'key=value'."; | |
} | |
} | |
*val++ = '\0'; | |
yumarg_t *a = apr_array_push(yumargs); | |
a->key = apr_pstrdup(cmd->pool, word); | |
/* we better anchor the regexp to the start and end, because when user | |
* data matches the regexp, we will substitute parts of the URL with it */ | |
a->regexp = ap_pregcomp(cmd->pool, | |
apr_pstrcat(cmd->pool, "^", val, "$", NULL), | |
AP_REG_EXTENDED); | |
if (!a->regexp) | |
return "Regular expression for ProxyRemoteMatch could not be compiled."; | |
}; | |
if (d == NULL) | |
return "MirrorBrainYumDir needs a (relative) base path"; | |
if (f == NULL) | |
return "MirrorBrainYumDir needs a file name relative to the base path"; | |
if (apr_is_empty_array(yumargs)) | |
return "MirrorBrainYumDir needs at least one query argument"; | |
yumdir_t *new = apr_array_push(cfg->yumdirs); | |
new->dir = apr_pstrdup(cmd->pool, d); | |
new->file = apr_pstrdup(cmd->pool, f); | |
new->args = yumargs; | |
return NULL; | |
} | |
#ifdef WITH_MEMCACHE | |
static const char *mb_cmd_memcached_on(cmd_parms *cmd, void *config, | |
int flag) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->memcached_on = flag; | |
return NULL; | |
} | |
static const char *mb_cmd_memcached_lifetime(cmd_parms *cmd, void *config, | |
const char *arg1) | |
{ | |
server_rec *s = cmd->server; | |
mb_server_conf *cfg = | |
ap_get_module_config(s->module_config, &mirrorbrain_module); | |
cfg->memcached_lifetime = atoi(arg1); | |
if (cfg->memcached_lifetime <= 0) | |
return "MirrorBrainMemcachedLifeTime requires an integer > 0."; | |
return NULL; | |
} | |
#endif | |
static int find_lowest_rank(apr_array_header_t *arr) | |
{ | |
if (arr->nelts == 1) { | |
return 0; /* the first and only element */ | |
} | |
int i; | |
int lowest_id = 0; | |
int lowest = INT_MAX; | |
mirror_entry_t *mirror; | |
mirror_entry_t **mirrorp; | |
mirrorp = (mirror_entry_t **)arr->elts; | |
for (i = 0; i < arr->nelts; i++) { | |
mirror = mirrorp[i]; | |
if (mirror->rank < lowest) { | |
lowest = mirror->rank; | |
lowest_id = i; | |
} | |
} | |
return lowest_id; | |
} | |
static int find_closest_dist(apr_array_header_t *arr) | |
{ | |
if (arr->nelts == 1) { | |
return 0; /* the first and only element */ | |
} | |
int i, d; | |
int closest_id = 0; | |
int closest = INT_MAX; | |
int closest_rank = INT_MAX; | |
mirror_entry_t *mirror; | |
mirror_entry_t **mirrorp; | |
int distprio = DISTANCE_PRIO / arr->nelts; | |
mirrorp = (mirror_entry_t **)arr->elts; | |
for (i = 0; i < arr->nelts; i++) { | |
mirror = mirrorp[i]; | |
d = mirror->dist + distprio / mirror->score; | |
/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, "[find_closest_dist] " | |
"i: %d, %-30s - dist: %d, score: %d, %d/score: %d, d: %d", | |
i, mirror->identifier, mirror->dist, mirror->score, distprio, | |
distprio/mirror->score, d); */ | |
if ( d < closest) { | |
/* this mirror is closer */ | |
closest = d; | |
closest_id = i; | |
closest_rank = mirror->rank; | |
} else if (d == closest) { | |
/* this mirror is as close as the closest known mirror. So we pick | |
* one of them randomly. */ | |
if (mirror->rank < closest_rank) { | |
closest = d; | |
closest_id = i; | |
closest_rank = mirror->rank; | |
} | |
} | |
} | |
return closest_id; | |
} | |
static int cmp_mirror_rank(const void *v1, const void *v2) | |
{ | |
mirror_entry_t *m1 = *(mirror_entry_t **)v1; | |
mirror_entry_t *m2 = *(mirror_entry_t **)v2; | |
return m1->rank - m2->rank; | |
} | |
static int cmp_mirror_dist(const void *v1, const void *v2) | |
{ | |
mirror_entry_t *m1 = *(mirror_entry_t **)v1; | |
mirror_entry_t *m2 = *(mirror_entry_t **)v2; | |
int distprio = DISTANCE_PRIO / *m1->nsame; | |
/* ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, | |
* "[cmp_mirror_dist] nsame: %d, distprio: %d", *m1->nsame, distprio); */ | |
return (m1->dist + distprio / m1->score) - (m2->dist + distprio / m2->score); | |
} | |
static apr_array_header_t *get_n_best_mirrors(request_rec *r, int n, | |
apr_array_header_t *a1, apr_array_header_t *a2, | |
apr_array_header_t *a3, apr_array_header_t *a4, | |
apr_array_header_t *a5) | |
{ | |
int i; | |
int found = 0; | |
apr_array_header_t *mirrors_n_best; | |
mirror_entry_t **mirrorp; | |
mirrors_n_best = apr_array_make(r->pool, n, sizeof (mirror_entry_t *)); | |
mirrorp = (mirror_entry_t **)a1->elts; | |
for (i = 0; found < n && i < a1->nelts; i++, found++) { | |
*(void **)apr_array_push(mirrors_n_best) = mirrorp[i]; | |
} | |
mirrorp = (mirror_entry_t **)a2->elts; | |
for (i = 0; found < n && i < a2->nelts; i++, found++) { | |
*(void **)apr_array_push(mirrors_n_best) = mirrorp[i]; | |
} | |
mirrorp = (mirror_entry_t **)a3->elts; | |
for (i = 0; found < n && i < a3->nelts; i++, found++) { | |
*(void **)apr_array_push(mirrors_n_best) = mirrorp[i]; | |
} | |
mirrorp = (mirror_entry_t **)a4->elts; | |
for (i = 0; found < n && i < a4->nelts; i++, found++) { | |
*(void **)apr_array_push(mirrors_n_best) = mirrorp[i]; | |
} | |
mirrorp = (mirror_entry_t **)a5->elts; | |
for (i = 0; found < n && i < a5->nelts; i++, found++) { | |
*(void **)apr_array_push(mirrors_n_best) = mirrorp[i]; | |
} | |
return mirrors_n_best; | |
} | |
/* return the scheme of an URL, e.g. ftp for ftp://foo.example.com/ */ | |
static const char *url_scheme(apr_pool_t *p, const char *url) | |
{ | |
const char *s; | |
s = apr_pstrndup(p, url, strcspn(url, ":")); | |
if (s && s[0]) { | |
return s; | |
} | |
return "INVALID URL SCHEME"; | |
} | |
/* | |
* This routine returns an URL in the format needed for old (v3) or newer (IETF) | |
* Metalinks. | |
*/ | |
static void emit_metalink_url(request_rec *r, int rep, | |
const char *baseurl, | |
const char *country_code, | |
const char *filename, int v3prio, int prio) | |
{ | |
switch (rep) { | |
case META4: | |
ap_rprintf(r, " <url location=\"%s\" priority=\"%d\">%s%s</url>\n", | |
country_code, prio, baseurl, filename); | |
break; | |
case METALINK: | |
ap_rprintf(r, " <url type=\"%s\" location=\"%s\" preference=\"%d\">%s%s</url>\n", | |
url_scheme(r->pool, baseurl), country_code, v3prio, baseurl, filename); | |
break; | |
} | |
} | |
/* set variables in the subprocess environment, to make it available for | |
* logging via the CustomLog directive */ | |
static void setenv_give(request_rec *r, const char *rep) | |
{ | |
apr_table_setn(r->subprocess_env, "GIVE", rep); | |
} | |
static void setenv_want(request_rec *r, const char *rep) | |
{ | |
apr_table_setn(r->subprocess_env, "WANT", rep); | |
} | |
/* Fast hex decoding function from PostgreSQL, src/backend/utils/adt/encode.c | |
* | |
* Note on binary data (bytea columns) in PostgreSQL: | |
* | |
* PostgreSQL escapes binary (BYTEA) data on output. But hex encoding is more | |
* efficient than the traditionally (<8.5) used escaping method. Hex encoding | |
* results in shorter strings, and thus less data to transfer over the wire, | |
* and encoding is also done faster. Hex encoding might actually become the | |
* default later. The escape format doesn't make sense for a new application | |
* anymore (like us). | |
* Storage on the other hand (in BYTEA data type) is as compact as could be. | |
* Compact storage means that the datawill more likely fit into memory, which | |
* is crucial. And the hex encoding function in PostgreSQL seems to be fast. | |
* | |
* This means that, on our side, we have to convert back the data from hex to | |
* binary for output formats like e.g. torrents. Therefore we copied the hex | |
* decoder from PostgreSQL here. */ | |
static const int8_t hexlookup[128] = { | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, | |
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | |
}; | |
static char get_hex(request_rec *r, char c) | |
{ | |
int res = -1; | |
if (c > 0 && c < 127) | |
res = hexlookup[(unsigned char) c]; | |
if (res < 0) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] invalid hexadecimal digit: \"%c\"", c); | |
return (char) res; | |
} | |
static char *hex_decode(request_rec *r, const char *src, unsigned dstlen) | |
{ | |
const char *s, *srcend; | |
char *dst; | |
char v1, v2, *p; | |
if (!dstlen) { | |
dstlen = (strlen(src) >> 1); | |
} | |
dst = apr_palloc(r->pool, (dstlen)); | |
srcend = src + (dstlen << 1); | |
s = src; | |
p = dst; | |
while (s < srcend) { | |
v1 = get_hex(r, *s++) << 4; | |
if (s >= srcend) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] invalid hexadecimal data: " | |
"odd number of digits"); | |
} | |
v2 = get_hex(r, *s++); | |
*p++ = v1 | v2; | |
} | |
return dst; | |
} | |
static hashbag_t *hashbag_fill(request_rec *r, ap_dbd_t *dbd, char *filename) | |
{ | |
mb_server_conf *scfg = NULL; | |
scfg = (mb_server_conf *) ap_get_module_config(r->server->module_config, | |
&mirrorbrain_module); | |
if (scfg->query_hash == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] No MirrorBrainDBDQueryHash configured!"); | |
return NULL; | |
} | |
if (scfg->query_hash_label == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] No database hash query prepared!"); | |
return NULL; | |
} | |
if (dbd == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Don't have a database connection"); | |
return NULL; | |
} | |
apr_dbd_prepared_t *stmt; | |
stmt = apr_hash_get(dbd->prepared, scfg->query_hash_label, APR_HASH_KEY_STRING); | |
if (stmt == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Could not get prepared statement labelled '%s'", | |
scfg->query_hash_label); | |
return NULL; | |
} | |
hashbag_t *h = apr_pcalloc(r->pool, sizeof(hashbag_t)); | |
h->id = 0; | |
h->md5hex = NULL; | |
h->sha1hex = NULL; | |
h->sha256hex = NULL; | |
h->sha1piecesize = 0; | |
h->sha1pieceshex = NULL; | |
h->btihhex = NULL; | |
h->pgp = NULL; | |
h->zblocksize = 0; | |
h->zhashlens = NULL; | |
h->zsumshex = NULL; | |
apr_status_t rv; | |
apr_dbd_results_t *res = NULL; | |
apr_dbd_row_t *row = NULL; | |
if (apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res, stmt, 0, | |
filename, | |
apr_off_t_toa(r->pool, r->finfo.size), | |
apr_itoa(r->pool, apr_time_sec(r->finfo.mtime)), /* APR finfo times are in microseconds */ | |
NULL) != 0) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Error looking up %s in database", filename); | |
return NULL; | |
} | |
/* we care only about the 1st row, because our query uses 'limit 1' */ | |
rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, dbd_first_row); | |
if (rv != APR_SUCCESS) { | |
const char *errmsg = apr_dbd_error(dbd->driver, dbd->handle, rv); | |
ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, | |
"[mod_mirrorbrain] Could not retrieve row from database for %s " | |
"(size: %s, mtime %s): %s Likely cause: there are no hashes in " | |
"the database (yet).", | |
filename, | |
apr_off_t_toa(r->pool, r->finfo.size), | |
apr_itoa(r->pool, apr_time_sec(r->finfo.mtime)), | |
(errmsg ? errmsg : "[???]")); | |
return NULL; | |
} | |
const char *val = NULL; | |
short col = 0; /* column that we are reading out */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for file_id"); | |
else | |
h->id = apr_atoi64(val); | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for md5"); | |
} else { | |
if (val[0]) { | |
h->md5hex = apr_pstrdup(r->pool, val); | |
} | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for sha1"); | |
} else { | |
if (val[0]) | |
h->sha1hex = apr_pstrdup(r->pool, val); | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for sha256"); | |
} else { | |
if (val[0]) | |
h->sha256hex = apr_pstrdup(r->pool, val); | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for sha1piecesize"); | |
else | |
h->sha1piecesize = atoi(val); | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for sha1pieces"); | |
} else { | |
if (val[0] && (h->sha1piecesize > 0)) { | |
/* split the string into an array of the actual pieces */ | |
apr_off_t n = r->finfo.size / h->sha1piecesize; | |
// XXX ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: %" APR_INT64_T_FMT " sha1 pieces", n); | |
h->sha1pieceshex = apr_array_make(r->pool, n, sizeof(const char *)); | |
int max = strlen(val); | |
int i; | |
for (i = 0; (i <= n); i++) { | |
if (((i + 1) * SHA1_DIGESTSIZE*2) > max) | |
break; | |
APR_ARRAY_PUSH(h->sha1pieceshex, char *) = apr_pstrndup(r->pool, | |
val + (i * SHA1_DIGESTSIZE * 2), (SHA1_DIGESTSIZE * 2)); | |
} | |
} | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for btih"); | |
} else { | |
if (val[0]) | |
h->btihhex = apr_pstrdup(r->pool, val); | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for pgp"); | |
} else { | |
if (val[0]) | |
h->pgp = apr_pstrdup(r->pool, val); | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for zblocksize"); | |
else | |
h->zblocksize = atoi(val); | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for zhashlens"); | |
} else { | |
if (val[0]) | |
h->zhashlens = apr_pstrdup(r->pool, val); | |
} | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] dbd: got NULL for zsums"); | |
} else { | |
if (val[0]) | |
h->zsumshex = apr_pstrdup(r->pool, val); | |
} | |
/* clear the cursor by accessing invalid row */ | |
rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, dbd_first_row + 1); | |
if (rv != -1) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
"[mod_mirrorbrain] found too many rows when looking up hashes for %s", | |
filename); | |
return NULL; | |
} | |
return h; | |
} | |
static int mb_handler(request_rec *r) | |
{ | |
mb_dir_conf *cfg = NULL; | |
mb_server_conf *scfg = NULL; | |
char *ptr = NULL; | |
char *uri = NULL; | |
char *filename = NULL; | |
const char *basename = NULL; | |
const char *mirror_base = NULL; | |
char *realfile = NULL; | |
yumdir_t *yum = NULL; | |
const char *clientip = NULL; | |
apr_sockaddr_t *clientaddr; | |
const char *query_country = NULL; | |
char *query_asn = NULL; | |
char fakefile = 0, newmirror = 0, only_hash = 0; | |
int rep = UNKNOWN; /* type of a requested representation */ | |
char *rep_ext = NULL; /* extension string of a requested representation */ | |
char meta_negotiated = 0; /* a metalink representation was chosed by negotiation, i.e. | |
the server might still decide to return the file itself | |
if it's excluded from redirection by configuration */ | |
const char *continent_code; | |
const char *country_code; | |
const char *country_name; | |
const char *slat, *slng; | |
float lat = 0, lng = 0; | |
const char *state_id, *state_name; | |
const char *as; /* autonomous system */ | |
const char *prefix; /* network prefix */ | |
int i; | |
int mirror_cnt; | |
apr_size_t len, nr; | |
mirror_entry_t *new; | |
mirror_entry_t *mirror; | |
mirror_entry_t **mirrorp; | |
mirror_entry_t *chosen = NULL; | |
hashbag_t *hashbag = NULL; | |
apr_status_t rv; | |
apr_dbd_prepared_t *statement; | |
apr_dbd_results_t *res = NULL; | |
apr_dbd_row_t *row = NULL; | |
/* this holds all mirror_entrys */ | |
apr_array_header_t *mirrors; | |
/* the following arrays all hold pointers into the mirrors array */ | |
apr_array_header_t *mirrors_same_prefix; /* in the same network prefix */ | |
apr_array_header_t *mirrors_same_as; /* in the same autonomous system */ | |
apr_array_header_t *mirrors_same_country; | |
apr_array_header_t *mirrors_fallback_country; | |
apr_array_header_t *mirrors_same_region; | |
apr_array_header_t *mirrors_elsewhere; | |
#ifdef WITH_MEMCACHE | |
apr_memcache_t *memctxt; /* memcache context provided by mod_memcache */ | |
char *m_res; | |
char *m_key, *m_val; | |
int cached_id; | |
#endif | |
const char *(*form_lookup)(request_rec*, const char*); | |
int (*cmp_mirror_best)(const void *, const void *); | |
int (*find_best) (apr_array_header_t *); | |
cfg = (mb_dir_conf *) ap_get_module_config(r->per_dir_config, | |
&mirrorbrain_module); | |
scfg = (mb_server_conf *) ap_get_module_config(r->server->module_config, | |
&mirrorbrain_module); | |
/* is MirrorBrainEngine disabled for this directory? */ | |
if (cfg->engine_on != 1) { | |
return DECLINED; | |
} | |
#ifdef WITH_MEMCACHE | |
debugLog(r, cfg, "MirrorBrainEngine On, instance '%s', mirror_base '%s'", | |
scfg->instance, cfg->mirror_base); | |
#else | |
debugLog(r, cfg, "MirrorBrainEngine On, mirror_base '%s'", | |
cfg->mirror_base); | |
#endif | |
/* is it a HEAD request? */ | |
if (r->header_only && cfg->handle_headrequest_locally) { | |
debugLog(r, cfg, "HEAD request for URI '%s'", r->unparsed_uri); | |
return DECLINED; | |
} | |
if (r->method_number != M_GET) { | |
debugLog(r, cfg, "Not a GET method for URI '%s'", r->unparsed_uri); | |
return DECLINED; | |
} | |
/* is there PATH_INFO, and are we supposed to accept it? */ | |
if ((r->path_info && *r->path_info) | |
&& (r->used_path_info != AP_REQ_ACCEPT_PATH_INFO)) { | |
debugLog(r, cfg, "ignoring request with PATH_INFO='%s'", r->path_info); | |
return DECLINED; | |
} | |
debugLog(r, cfg, "URI: '%s'", r->unparsed_uri); | |
debugLog(r, cfg, "filename: '%s'", r->filename); | |
//debugLog(r, cfg, "server_hostname: '%s'", r->server->server_hostname); | |
setenv_want(r, "file"); | |
/* parse query arguments if present, */ | |
/* using mod_form's form_value() */ | |
form_lookup = APR_RETRIEVE_OPTIONAL_FN(form_value); | |
if (form_lookup && r->args) { | |
if (form_lookup(r, "fakefile")) fakefile = 1; | |
query_country = form_lookup(r, "country"); | |
query_asn = (char *) form_lookup(r, "as"); | |
if (form_lookup(r, "newmirror")) newmirror = 1; | |
if (form_lookup(r, "meta4")) { rep = META4; rep_ext = reps[META4].ext; }; | |
if (form_lookup(r, "metalink")) { rep = METALINK; rep_ext = reps[METALINK].ext; }; | |
if (form_lookup(r, "mirrorlist")) { rep = MIRRORLIST; rep_ext = reps[MIRRORLIST].ext; } | |
if (form_lookup(r, "torrent")) { rep = TORRENT; rep_ext = reps[TORRENT].ext; } | |
if (form_lookup(r, "zsync")) { rep = ZSYNC; rep_ext = reps[ZSYNC].ext; } | |
if (form_lookup(r, "magnet")) { rep = MAGNET; rep_ext = reps[MAGNET].ext; } | |
if (form_lookup(r, "md5")) { rep = MD5; rep_ext = reps[MD5].ext; }; | |
if (form_lookup(r, "sha1")) { rep = SHA1; rep_ext = reps[SHA1].ext; }; | |
if (form_lookup(r, "sha256")) { rep = SHA256; rep_ext = reps[SHA256].ext; }; | |
if (form_lookup(r, "btih")) { rep = BTIH; rep_ext = reps[BTIH].ext; }; | |
if (form_lookup(r, "only_hash")) { only_hash = 1; }; | |
/* yum query to be parsed? */ | |
if (!apr_is_empty_array(scfg->yumdirs)) { | |
for (i = 0; i < scfg->yumdirs->nelts; i++) { | |
yumdir_t y; | |
yumarg_t a; | |
char *d, *f; | |
const char *val; | |
int val_len_sum = 0; | |
int n_matches = 0; | |
y = ((yumdir_t *) scfg->yumdirs->elts)[i]; | |
d = y.dir; | |
f = y.file; | |
//debugLog(r, cfg, "checking against yum dir %s / %s", d, f); | |
for (n_matches = 0; n_matches < y.args->nelts; n_matches++) { | |
a = ((yumarg_t *) y.args->elts)[n_matches]; | |
val = form_lookup(r, a.key); | |
if (!val) | |
break; | |
if (ap_regexec(a.regexp, val, 0, NULL, 0)) | |
break; | |
val_len_sum += strlen(val); | |
//debugLog(r, cfg, "value '%s' matches regexp for '%s'", val, a.key); | |
} | |
char *src, *dst; | |
char c; | |
if (n_matches == y.args->nelts) { | |
rep = YUMLIST; | |
yum = apr_pcalloc(r->pool, sizeof(yumdir_t)); | |
debugLog(r, cfg, "match for yum dir %s / %s", d, f); | |
yum->file = apr_pstrdup(r->pool, f); | |
if ((ptr = ap_strchr(d, '$'))) { | |
//debugLog(r, cfg, "substitution to be done"); | |
src = d; | |
yum->dir = dst = apr_pcalloc(r->pool, strlen(d) + val_len_sum + 1); | |
while ((c = *src++) != '\0') { | |
if (c == '$' && apr_isdigit(*src)) { | |
nr = *src++ - '0'; | |
if (nr == 0) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] " | |
"cannot substitute $0 in '%s' -- use 1 or a greater digit", d); | |
return HTTP_INTERNAL_SERVER_ERROR; | |
} else if (nr > n_matches) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] " | |
"cannot substitute $%d in '%s' -- only %d args are defined", | |
nr, d, y.args->nelts); | |
return HTTP_INTERNAL_SERVER_ERROR; | |
} | |
a = ((yumarg_t *) y.args->elts)[nr - 1]; | |
val = form_lookup(r, a.key); | |
debugLog(r, cfg, "substituting $%d with '%s'", nr, val); | |
len = strlen(val); | |
memcpy(dst, val, len); | |
dst += len; | |
} else { | |
*dst++ = c; | |
} | |
} | |
*dst = '\0'; | |
} else { | |
yum->dir = apr_pstrdup(r->pool, d); | |
} | |
debugLog(r, cfg, "yum->dir: '%s', yum->file: '%s'", yum->dir, yum->file); | |
/* FIXME: maybe don't break in debug mode, to discover double matches */ | |
break; | |
} | |
} | |
if (yum == NULL) { | |
debugLog(r, cfg, "yum query received, but didn't match any of the rules"); | |
return HTTP_NOT_FOUND; | |
} | |
} | |
} | |
if (!query_country | |
|| strlen(query_country) != 2 | |
|| !apr_isalnum(query_country[0]) | |
|| !apr_isalnum(query_country[1])) { | |
query_country = NULL; | |
} | |
if (query_asn) { | |
for (i = 0; apr_isdigit(query_asn[i]); i++) | |
; | |
query_asn[i] = '\0'; | |
} | |
if (rep == UNKNOWN) { | |
const char *accepts; | |
accepts = apr_table_get(r->headers_in, "Accept"); | |
if (accepts != NULL) { | |
if (ap_strstr_c(accepts, "metalink4+xml")) { | |
rep = META4; | |
rep_ext = reps[META4].ext; | |
meta_negotiated = 1; | |
setenv_want(r, reps[rep].ext); | |
} else if (ap_strstr_c(accepts, "metalink+xml")) { | |
rep = METALINK; | |
rep_ext = reps[METALINK].ext; | |
meta_negotiated = 1; | |
setenv_want(r, reps[rep].ext); | |
} | |
} | |
} | |
/* We might be running as a backend which sees client IPs only through HTTP | |
* headers */ | |
clientip = apr_table_get(r->subprocess_env, "GEOIP_ADDR"); | |
rv = apr_sockaddr_info_get(&clientaddr, clientip, APR_UNSPEC, 0, 0, r->pool); | |
if(APR_STATUS_IS_EINVAL(rv) || (rv != APR_SUCCESS)) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] " | |
"Error in parsing GEOIP_ADDR: '%s'", clientip); | |
} | |
debugLog(r, cfg, "clientip: %s", clientip); | |
/* These checks apply only if the server response is not faked for testing */ | |
if (fakefile) { | |
debugLog(r, cfg, "FAKE File -- not looking at real files"); | |
} else { | |
if ((r->finfo.filetype == APR_DIR) && (rep != YUMLIST)) { | |
/* if (ap_is_directory(r->pool, r->filename)) */ | |
debugLog(r, cfg, "'%s' is a directory", r->filename); | |
return DECLINED; | |
} | |
/* if the file doesn't exist, maybe a representation of it is requested */ | |
if ((r->finfo.filetype != APR_REG) && (rep != YUMLIST)) { | |
debugLog(r, cfg, "File does not exist according to r->finfo"); | |
if (r->filename[strlen(r->filename) - 1] == '.') { | |
debugLog(r, cfg, "invalid file extension '.'"); | |
return DECLINED; | |
} | |
/* Try if we find a valid .metalink/.meta4/... extension. */ | |
char *ext; | |
if ((ext = ap_strrchr(r->filename, '.')) == NULL) { | |
return DECLINED; | |
} | |
rep = UNKNOWN; | |
for (i = 0; reps[i].ext; i++) { | |
if (strcmp(ext + 1, reps[i].ext) == 0) { | |
rep = i; | |
rep_ext = reps[i].ext; | |
/* debugLog(r, cfg, "File ending .%s found", rep_ext); */ | |
break; | |
} | |
} | |
switch (rep) { | |
case UNKNOWN: | |
return DECLINED; | |
case META4: | |
case METALINK: | |
case MIRRORLIST: | |
case TORRENT: | |
case ZSYNC: | |
case MAGNET: | |
case MD5: | |
case SHA1: | |
case SHA256: | |
case BTIH: | |
setenv_want(r, reps[rep].ext); | |
debugLog(r, cfg, "Representation chosen by .%s extension", rep_ext); | |
/* note this actually modifies r->filename. */ | |
ext[0] = '\0'; | |
/* strip the extension from r->uri as well */ | |
debugLog(r, cfg, "r->uri: '%s'", r->uri); | |
if ((ext = ap_strrchr(r->uri, '.')) != NULL) { | |
if (strcmp(ext + 1, rep_ext) == 0) { | |
ext[0] = '\0'; | |
} | |
} | |
debugLog(r, cfg, "r->uri: '%s'", r->uri); | |
/* fill in finfo */ | |
if ( apr_stat(&r->finfo, r->filename, APR_FINFO_SIZE | APR_FINFO_MTIME, r->pool) | |
!= APR_SUCCESS ) { | |
return HTTP_NOT_FOUND; | |
} | |
break; | |
} | |
} | |
} /* end if(!fakefile) */ | |
if (rep == UNKNOWN) | |
rep = REDIRECT; | |
if ((rep == REDIRECT) || meta_negotiated) { | |
/* is the requested file too small to be worth a redirect? */ | |
if (!fakefile && (r->finfo.size < cfg->min_size)) { | |
debugLog(r, cfg, "File '%s' too small (%s bytes, less than %s)", | |
r->filename, apr_off_t_toa(r->pool, r->finfo.size), | |
apr_off_t_toa(r->pool, cfg->min_size)); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
/* is this file excluded from mirroring? */ | |
if (cfg->exclude_filemask | |
&& !ap_regexec(cfg->exclude_filemask, r->uri, 0, NULL, 0) ) { | |
debugLog(r, cfg, "File '%s' is excluded by MirrorBrainExcludeFileMask", r->uri); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
/* is the request originating from an ip address excluded from redirecting? */ | |
if (!apr_is_empty_array(cfg->exclude_ips)) { | |
for (i = 0; i < cfg->exclude_ips->nelts; i++) { | |
char *ip = ((char **) cfg->exclude_ips->elts)[i]; | |
if (strcmp(ip, clientip) == 0) { | |
debugLog(r, cfg, | |
"URI request '%s' from ip '%s' is excluded from" | |
" redirecting because it matches IP '%s'", | |
r->unparsed_uri, clientip, ip); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
} | |
} | |
/* is the request originating from a network excluded from redirecting? */ | |
if (!apr_is_empty_array(cfg->exclude_networks)) { | |
for (i = 0; i < cfg->exclude_networks->nelts; i++) { | |
char *network = ((char **) cfg->exclude_networks->elts)[i]; | |
if (strncmp(network, clientip, strlen(network)) == 0) { | |
debugLog(r, cfg, | |
"URI request '%s' from ip '%s' is excluded from" | |
" redirecting because it matches network '%s'", | |
r->unparsed_uri, clientip, network); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
} | |
} | |
/* is the file in the list of mimetypes to never mirror? */ | |
if ((r->content_type) && !apr_is_empty_array(cfg->exclude_mime)) { | |
for (i = 0; i < cfg->exclude_mime->nelts; i++) { | |
char *mimetype = ((char **) cfg->exclude_mime->elts)[i]; | |
if (wild_match(mimetype, r->content_type)) { | |
debugLog(r, cfg, | |
"URI '%s' (%s) is excluded from redirecting" | |
" by mimetype pattern '%s'", r->unparsed_uri, | |
r->content_type, mimetype); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
} | |
} | |
/* is this User-Agent excluded from redirecting? */ | |
const char *user_agent = | |
(const char *) apr_table_get(r->headers_in, "User-Agent"); | |
if (user_agent && !apr_is_empty_array(cfg->exclude_agents)) { | |
for (i = 0; i < cfg->exclude_agents->nelts; i++) { | |
char *agent = ((char **) cfg->exclude_agents->elts)[i]; | |
if (wild_match(agent, user_agent)) { | |
debugLog(r, cfg, | |
"URI request '%s' from agent '%s' is excluded from" | |
" redirecting by User-Agent pattern '%s'", | |
r->unparsed_uri, user_agent, agent); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
} | |
} | |
} /* end if ((rep == REDIRECT) || meta_negotiated) */ | |
#ifdef WITH_MEMCACHE | |
memctxt = ap_memcache_client(r->server); | |
if (memctxt == NULL) scfg->memcached_on = 0; | |
/* look for associated mirror in memcache */ | |
cached_id = 0; | |
if (scfg->memcached_on) { | |
m_key = apr_pstrcat(r->pool, "mb_", scfg->instance, "_", clientip, NULL); | |
if (newmirror) { | |
debugLog(r, cfg, "client requested new mirror"); | |
} else { | |
rv = apr_memcache_getp(memctxt, r->pool, m_key, &m_res, &len, NULL); | |
if (rv == APR_SUCCESS) { | |
cached_id = atoi(m_res); | |
debugLog(r, cfg, "IP known in memcache: mirror id %d", cached_id); | |
} | |
else { | |
debugLog(r, cfg, "IP unknown in memcache"); | |
} | |
} | |
} | |
#endif | |
if (scfg->query == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] No MirrorBrainDBDQuery configured!"); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
country_code = apr_table_get(r->subprocess_env, "GEOIP_COUNTRY_CODE"); | |
country_name = apr_table_get(r->subprocess_env, "GEOIP_COUNTRY_NAME"); | |
continent_code = apr_table_get(r->subprocess_env, "GEOIP_CONTINENT_CODE"); | |
slat = apr_table_get(r->subprocess_env, "GEOIP_LATITUDE"); | |
slng = apr_table_get(r->subprocess_env, "GEOIP_LONGITUDE"); | |
if (slat && slng) { | |
lat = atof(slat); | |
lng = atof(slng); | |
}; | |
state_id = apr_table_get(r->subprocess_env, "GEOIP_REGION"); | |
state_name = apr_table_get(r->subprocess_env, "GEOIP_REGION_NAME"); | |
if (!country_code) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] could not resolve country"); | |
country_code = "--"; | |
} | |
if (!continent_code) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] could not resolve continent"); | |
continent_code = "--"; | |
} | |
if (query_country) { | |
country_code = query_country; | |
} | |
debugLog(r, cfg, "Country '%s', Continent '%s'", country_code, | |
continent_code); | |
/* save details for logging via a CustomLog */ | |
apr_table_setn(r->subprocess_env, "MB_FILESIZE", | |
apr_off_t_toa(r->pool, r->finfo.size)); | |
apr_table_set(r->subprocess_env, "MB_COUNTRY_CODE", country_code); | |
apr_table_set(r->subprocess_env, "MB_CONTINENT_CODE", continent_code); | |
/* see if we find info about autonomous system and network prefix | |
* in the subprocess environment - set for us by mod_asn */ | |
if (query_asn && (query_asn[0] != '\0')) { | |
as = query_asn; | |
} else { | |
as = apr_table_get(r->subprocess_env, "ASN"); | |
if (!as) { | |
as = "--"; | |
} | |
} | |
prefix = apr_table_get(r->subprocess_env, "PFX"); | |
if (!prefix) { | |
prefix = "--"; | |
} | |
debugLog(r, cfg, "AS '%s', Prefix '%s', lat/lng %f,%f state id %s, state '%s'", | |
as, prefix, lat, lng, state_id, state_name); | |
/* The basedir might contain symlinks. That needs to be taken into account. | |
* See discussion in http://mirrorbrain.org/issues/issue17 */ | |
ptr = realpath(cfg->mirror_base, NULL); | |
if (ptr == NULL) { | |
/* this should never happen, because the MirrorBrainEngine directive would never | |
* be applied to a non-existing directories */ | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Document root \'%s\' does not seem to " | |
"exist. Filesystem not mounted?", cfg->mirror_base); | |
return HTTP_INTERNAL_SERVER_ERROR; | |
} | |
mirror_base = apr_pstrdup(r->pool, ptr); | |
free(ptr); | |
/* prepare the filename to look up */ | |
if (rep != YUMLIST) { | |
filename = apr_pstrdup(r->pool, r->filename); | |
} else { | |
filename = apr_pstrcat(r->pool, mirror_base, "/", yum->dir, "/", yum->file, NULL); | |
debugLog(r, cfg, "yum path on disk: %s", filename); | |
} | |
ptr = realpath(filename, NULL); | |
if (ptr == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Error canonicalizing filename '%s'", filename); | |
/* return HTTP_INTERNAL_SERVER_ERROR; */ | |
return HTTP_NOT_FOUND; | |
} | |
realfile = apr_pstrdup(r->pool, ptr); | |
debugLog(r, cfg, "Canonicalized file on disk: %s", realfile); | |
free(ptr); | |
/* the leading directory needs to be stripped from the file path */ | |
/* a directory from Apache always ends in '/'; a result from realpath() doesn't */ | |
filename = realfile + strlen(mirror_base) + 1; | |
if (rep != YUMLIST) { | |
/* keep a filename version without leading path, because metalink clients | |
* will otherwise place the downloaded file into a directory hierarchy */ | |
if ((basename = ap_strrchr_c(filename, '/')) == NULL) | |
basename = filename; | |
else | |
++basename; | |
} | |
debugLog(r, cfg, "SQL file to look up: %s", filename); | |
if (scfg->query_label == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] No database query prepared!"); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
ap_dbd_t *dbd = mb_dbd_acquire_fn(r); | |
if (dbd == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Error acquiring database connection"); | |
if (apr_is_empty_array(cfg->fallbacks)) { | |
setenv_give(r, "file"); | |
return DECLINED; /* fail gracefully */ | |
} | |
} | |
debugLog(r, cfg, "Successfully acquired database connection."); | |
switch (rep) { | |
case MD5: | |
case SHA1: | |
case SHA256: | |
case BTIH: | |
case TORRENT: | |
case ZSYNC: | |
case MAGNET: | |
hashbag = hashbag_fill(r, dbd, filename); | |
if (!hashbag) { | |
debugLog(r, cfg, "no hashes found in database, but needed " | |
"for %s representation", rep_ext); | |
return HTTP_NOT_FOUND; | |
} | |
} | |
switch (rep) { | |
case MD5: | |
case SHA1: | |
case SHA256: | |
case BTIH: { | |
const char *h = NULL; | |
switch (rep) { | |
case MD5: h = hashbag->md5hex; break; | |
case SHA1: h = hashbag->sha1hex; break; | |
case SHA256: h = hashbag->sha256hex; break; | |
case BTIH: h = hashbag->btihhex; break; | |
} | |
if (h && h[0]) { | |
ap_set_content_type(r, "text/plain; charset=UTF-8"); | |
if (only_hash || (scfg->only_hash == 1) ) { | |
ap_rprintf(r, "%s\n", h); | |
} | |
else { | |
ap_rprintf(r, "%s %s\n", h, basename); | |
} | |
setenv_give(r, reps[rep].ext); | |
return OK; | |
} | |
return HTTP_NOT_FOUND; | |
} | |
} | |
statement = apr_hash_get(dbd->prepared, scfg->query_label, APR_HASH_KEY_STRING); | |
if (statement == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Could not get prepared statement labelled '%s'", | |
scfg->query_label); | |
/* log existing prepared statements. It might help with figuring out | |
* misconfigurations */ | |
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
"[mod_mirrorbrain] dbd->prepared hash contains %d key/value pairs", | |
apr_hash_count(dbd->prepared)); | |
apr_hash_index_t *hi; | |
const char *label, *query; | |
for (hi = apr_hash_first(r->pool, dbd->prepared); hi; hi = apr_hash_next(hi)) { | |
apr_hash_this(hi, (void*) &label, NULL, (void*) &query); | |
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
"[mod_mirrorbrain] dbd->prepared dump: key %s, value 0x%08lx", label, (long)query); | |
} | |
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
"[mod_mirrorbrain] Hint: connection strings defined with " | |
"DBDParams must be unique. The same string cannot be used " | |
"in two vhosts."); | |
return DECLINED; | |
} | |
/* no need to escape for the SQL query because we use a prepared | |
* statement with bound parameters */ | |
if (apr_dbd_pvselect(dbd->driver, r->pool, dbd->handle, &res, statement, | |
1, /* we don't need random access actually, but | |
without it the mysql driver doesn't return results | |
once apr_dbd_num_tuples() has been called; | |
apr_dbd_get_row() will only return -1 after that. */ | |
filename, NULL) != 0) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] Error looking up %s in database", filename); | |
if (apr_is_empty_array(cfg->fallbacks)) { | |
return DECLINED; | |
} | |
} | |
mirror_cnt = apr_dbd_num_tuples(dbd->driver, res); | |
if (mirror_cnt > 0) { | |
debugLog(r, cfg, "Found %d mirror%s", mirror_cnt, | |
(mirror_cnt == 1) ? "" : "s"); | |
} | |
/* handling of the case mirror_cnt==0 is further below. It may happen that | |
* we have mirrors, but no usable ones. */ | |
/* allocate space for the expected results */ | |
mirrors = apr_array_make(r->pool, mirror_cnt, sizeof (mirror_entry_t)); | |
/* n.b., the following arrays only hold pointers into the above array */ | |
mirrors_same_prefix = apr_array_make(r->pool, 1, sizeof (mirror_entry_t *)); | |
mirrors_same_as = apr_array_make(r->pool, 1, sizeof (mirror_entry_t *)); | |
mirrors_same_country = apr_array_make(r->pool, mirror_cnt, sizeof (mirror_entry_t *)); | |
mirrors_fallback_country = apr_array_make(r->pool, 5, sizeof (mirror_entry_t *)); | |
mirrors_same_region = apr_array_make(r->pool, mirror_cnt, sizeof (mirror_entry_t *)); | |
mirrors_elsewhere = apr_array_make(r->pool, mirror_cnt, sizeof (mirror_entry_t *)); | |
/* store the results which the database yielded, taking into account which | |
* mirrors are in the same country, same reagion, or elsewhere */ | |
/* we copy all values to pool memory, because not all database drivers | |
* behave the same (see http://marc.info/?l=apr-dev&m=122982975912314&w=2 ) */ | |
i = 1; | |
while (i <= mirror_cnt) { | |
char unusable = 0; /* if crucial data is missing... */ | |
const char *val = NULL; | |
short col = 0; /* incremented for the column we are reading out */ | |
rv = apr_dbd_get_row(dbd->driver, r->pool, res, &row, | |
#if (APR_MAJOR_VERSION == 1 && APR_MINOR_VERSION == 2) | |
/* APR 1.2 was the first version to support the DBD | |
* framework, and had a different way of counting | |
* rows, see http://mirrorbrain.org/issues/issue7 | |
* */ | |
i - 1 | |
#else | |
i | |
#endif | |
); | |
if (rv != APR_SUCCESS) { | |
const char *errmsg = apr_dbd_error(dbd->driver, dbd->handle, rv); | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
"[mod_mirrorbrain] Error looking up %s in database: %s", | |
filename, (errmsg ? errmsg : "[???]")); | |
return DECLINED; | |
} | |
new = apr_array_push(mirrors); | |
new->nsame = &mirrors->nelts; | |
new->id = 0; | |
new->identifier = NULL; | |
new->region = NULL; | |
new->country_code = NULL; | |
new->lat = 0; | |
new->lng = 0; | |
new->dist = 9999999; | |
new->other_countries = NULL; | |
new->as = NULL; | |
new->prefix = NULL; | |
new->ipsub = NULL; | |
new->region_only = 0; | |
new->country_only = 0; | |
new->as_only = 0; | |
new->prefix_only = 0; | |
new->score = 0; | |
new->file_maxsize = 0; | |
new->baseurl = NULL; | |
/* id */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for id"); | |
else | |
new->id = atoi(val); | |
/* identifier */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for identifier"); | |
else | |
new->identifier = apr_pstrdup(r->pool, val); | |
/* region */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for region"); | |
else | |
new->region = apr_pstrdup(r->pool, val); | |
/* country_code */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for country_code"); | |
else | |
new->country_code = apr_pstrndup(r->pool, val, 2); /* fixed length, two bytes */ | |
/* latitude */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for lat"); | |
} else | |
new->lat = atof(val); | |
/* longitude */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for lng"); | |
} else | |
new->lng = atof(val); | |
/* FIXME: would be sufficient to do it only for the "interesting" mirrors */ | |
if (new->lat != 0 && new->lng != 0 && lat != 0 && lng != 0) { | |
new->dist = (int) ( sqrt( pow((lat - new->lat), 2) + pow((lng - new->lng), 2) ) * 1000 ); | |
} | |
/* autonomous system number */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for AS number"); | |
else | |
new->as = apr_pstrdup(r->pool, val); | |
/* network prefix */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for network prefix"); | |
else { | |
char *s; | |
new->prefix = apr_pstrdup(r->pool, val); | |
if ((s = ap_strchr(val, '/'))) { | |
*s++ = '\0'; | |
rv = apr_ipsubnet_create(&new->ipsub, val, s, r->pool); | |
if(APR_STATUS_IS_EINVAL(rv) || (rv != APR_SUCCESS)) { | |
/* looked nothing like an IP address, or could not be converted */ | |
new->ipsub = NULL; | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] " | |
"Error in parsing network prefix of %s: %s/%s", | |
new->identifier, val, s); | |
} | |
} | |
} | |
/* score */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for score"); | |
unusable = 1; | |
} else | |
new->score = atoi(val); | |
/* baseurl */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for baseurl"); | |
unusable = 1; | |
} else if (!val[0]) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] mirror '%s' (#%d) has empty baseurl", | |
new->identifier, new->id); | |
unusable = 1; | |
} else { | |
new->baseurl = apr_pstrdup(r->pool, val); | |
if (new->baseurl[strlen(new->baseurl) - 1] != '/') { | |
new->baseurl = apr_pstrcat(r->pool, new->baseurl, "/", NULL); | |
} | |
} | |
/* region_only */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for region_only"); | |
} else | |
new->region_only = ((val[0] == 't') || (val[0] == '1')) ? 1 : 0; | |
/* country_only */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for country_only"); | |
} else | |
new->country_only = ((val[0] == 't') || (val[0] == '1')) ? 1 : 0; | |
/* as_only */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for as_only"); | |
} else | |
new->as_only = ((val[0] == 't') || (val[0] == '1')) ? 1 : 0; | |
/* prefix_only */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for prefix_only"); | |
} else | |
new->prefix_only = ((val[0] == 't') || (val[0] == '1')) ? 1 : 0; | |
/* other_countries */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for other_countries"); | |
else | |
new->other_countries = apr_pstrdup(r->pool, val); | |
/* file_maxsize */ | |
if ((val = apr_dbd_get_entry(dbd->driver, row, col++)) == NULL) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "[mod_mirrorbrain] apr_dbd_get_entry found NULL for file_maxsize"); | |
unusable = 1; | |
} else | |
new->file_maxsize = apr_atoi64(val); | |
/* now, take some decisions */ | |
if (unusable) { | |
/* discard */ | |
apr_array_pop(mirrors); | |
i++; | |
continue; | |
} | |
/* rank it (randomized, weighted by "score" value) */ | |
/* not using thread-safe rand_r() here, because it shouldn't make | |
* a real difference here */ | |
new->rank = (rand()>>16) * ((RAND_MAX>>16) / new->score); | |
#ifdef WITH_MEMCACHE | |
if (new->id && (new->id == cached_id)) { | |
debugLog(r, cfg, "Mirror '%s' associated in memcache (cached_id %d)", new->identifier, cached_id); | |
chosen = new; | |
} | |
#endif | |
/* file too large for this mirror? */ | |
if (new->file_maxsize > 0 && r->finfo.size > new->file_maxsize) { | |
debugLog(r, cfg, "Mirror '%s' is configured to not handle files larger than %d bytes", | |
new->identifier, new->file_maxsize); | |
/* but keep it as reserve - after all, it could be the only one */ | |
*(void **)apr_array_push(mirrors_elsewhere) = new; | |
} | |
/* same prefix? */ | |
else if (new->ipsub | |
&& apr_ipsubnet_test(new->ipsub, clientaddr)) { | |
*(void **)apr_array_push(mirrors_same_prefix) = new; | |
new->nsame = &mirrors_same_prefix->nelts; | |
debugLog(r, cfg, "Mirror '%s' in same prefix (%s)", new->identifier, new->prefix); | |
} | |
/* same AS? */ | |
else if ((strcmp(new->as, as) == 0) | |
&& !new->prefix_only) { | |
*(void **)apr_array_push(mirrors_same_as) = new; | |
new->nsame = &mirrors_same_as->nelts; | |
} | |
/* same country? */ | |
else if ((strcasecmp(new->country_code, country_code) == 0) | |
&& !new->as_only | |
&& !new->prefix_only) { | |
*(void **)apr_array_push(mirrors_same_country) = new; | |
new->nsame = &mirrors_same_country->nelts; | |
} | |
/* is the mirror's country_code a wildcard, indicating that the mirror should be | |
* considered for every country? */ | |
else if (strcmp(new->country_code, "**") == 0) { | |
*(void **)apr_array_push(mirrors_same_country) = new; | |
new->nsame = &mirrors_same_country->nelts; | |
/* if so, forget memcache association, so the mirror is not ruled out */ | |
chosen = NULL; | |
/* set its country and region to that of the client */ | |
new->country_code = country_code; | |
new->region = continent_code; | |
} | |
/* mirror from elsewhere, but suitable for this country? */ | |
else if (new->other_countries && ap_strcasestr(new->other_countries, country_code)) { | |
*(void **)apr_array_push(mirrors_fallback_country) = new; | |
new->nsame = &mirrors_fallback_country->nelts; | |
} | |
/* same region? */ | |
/* to be actually considered for this group, the mirror must be willing | |
* to take redirects from foreign country */ | |
else if ((strcasecmp(new->region, continent_code) == 0) | |
&& !new->country_only | |
&& !new->as_only | |
&& !new->prefix_only) { | |
*(void **)apr_array_push(mirrors_same_region) = new; | |
new->nsame = &mirrors_same_region->nelts; | |
} | |
/* to be considered as "worldwide" mirror, it must be willing | |
* to take redirects from foreign regions. | |
* (N.B. region_only implies country_only) */ | |
else if (!new->region_only | |
&& !new->country_only | |
&& !new->as_only | |
&& !new->prefix_only) { | |
*(void **)apr_array_push(mirrors_elsewhere) = new; | |
new->nsame = &mirrors_elsewhere->nelts; | |
} | |
i++; | |
} | |
#if 0 | |
/* dump the mirror array */ | |
mirror_entry_t *elts; | |
elts = (mirror_entry_t *) mirrors->elts; | |
for (i = 0; i < mirrors->nelts; i++) { | |
debugLog(r, cfg, "mirror %3d %-30s", elts[i].id, elts[i].identifier); | |
} | |
#endif | |
/* 2nd pass */ | |
/* if we didn't find a mirror in the country: are other mirrors set to | |
* handle this country? */ | |
if (apr_is_empty_array(mirrors_same_country) | |
&& !apr_is_empty_array(mirrors_fallback_country)) { | |
mirrors_same_country = mirrors_fallback_country; | |
debugLog(r, cfg, "no mirror in country, but found fallback_country mirrors"); | |
} | |
/* 3rd pass */ | |
if (apr_is_empty_array(mirrors) && ! apr_is_empty_array(cfg->fallbacks)) { | |
debugLog(r, cfg, "ok, need to add fallback mirrors (%d configured)", | |
cfg->fallbacks->nelts); | |
/* we copy the array, so we don't modify the one in the config */ | |
mirrors = apr_array_copy(r->pool, cfg->fallbacks); | |
mirror_entry_t *elts; | |
elts = (mirror_entry_t *) mirrors->elts; | |
for (i = 0; i < mirrors->nelts; i++) { | |
elts[i].rank = (rand()>>16) * ((RAND_MAX>>16) / elts[i].score); | |
/* elts[i].identifier = apr_psprintf(r->pool, "fallback_%02d(%s)", | |
i, elts[i].baseurl); */ | |
if (strcasecmp(elts[i].country_code, country_code) == 0) { | |
*(void **)apr_array_push(mirrors_same_country) = &(elts[i]); | |
debugLog(r, cfg, "adding fallback mirror in same country: %s:%s %s", | |
elts[i].region, elts[i].country_code, elts[i].baseurl); | |
} | |
else if (strcasecmp(elts[i].region, continent_code) == 0) { | |
*(void **)apr_array_push(mirrors_same_region) = &(elts[i]); | |
debugLog(r, cfg, "adding fallback mirror in same region: %s:%s %s", | |
elts[i].region, elts[i].country_code, elts[i].baseurl); | |
} | |
else { | |
*(void **)apr_array_push(mirrors_elsewhere) = &(elts[i]); | |
debugLog(r, cfg, "adding fallback mirror elsewhere: %s:%s %s", | |
elts[i].region, elts[i].country_code, elts[i].baseurl); | |
} | |
} | |
} | |
if (lat != 0 && lng != 0) { | |
debugLog(r, cfg, "[mod_mirrorbrain] taking geo distance into account"); | |
find_best = find_closest_dist; | |
cmp_mirror_best = cmp_mirror_dist; | |
} else { | |
debugLog(r, cfg, "[mod_mirrorbrain] no distance data - using rank selection"); | |
find_best = find_lowest_rank; | |
cmp_mirror_best = cmp_mirror_rank; | |
} | |
/* | |
* Sorting the mirror list(s): | |
* - is needed only when metalink (or mirrorlist) is requested | |
* - sorting the mirrorlist itself would invalidates the pointer lists | |
* mirrors_same_country et al., as they are already done. | |
* The sorting could be done _before_ picking up mirrors_same_country et al. | |
* - but those are not needed also when doing a metalink | |
* - and since the ranking is not global, we still need to iterate over the | |
* mirrors_same_country et al. when doing the metalink | |
* | |
* The sorting might invalidate the location where "chosen" points at -- if | |
* "mirrors" itself is sorted. | |
* | |
* => best to sort the mirrors_same_country et al. individually, right? | |
*/ | |
switch (rep) { | |
case META4: | |
case METALINK: | |
case MIRRORLIST: | |
case ZSYNC: | |
case YUMLIST: | |
qsort(mirrors_same_prefix->elts, mirrors_same_prefix->nelts, | |
mirrors_same_prefix->elt_size, cmp_mirror_best); | |
qsort(mirrors_same_as->elts, mirrors_same_as->nelts, | |
mirrors_same_as->elt_size, cmp_mirror_best); | |
qsort(mirrors_same_country->elts, mirrors_same_country->nelts, | |
mirrors_same_country->elt_size, cmp_mirror_best); | |
qsort(mirrors_same_region->elts, mirrors_same_region->nelts, | |
mirrors_same_region->elt_size, cmp_mirror_best); | |
qsort(mirrors_elsewhere->elts, mirrors_elsewhere->nelts, | |
mirrors_elsewhere->elt_size, cmp_mirror_best); | |
break; | |
} | |
if (cfg->debug) { | |
/* list the sorted result */ | |
/* Brad's mod_edir hdir.c helped me here.. thanks to his kind help */ | |
mirror = NULL; | |
/* list the same-prefix mirrors */ | |
mirrorp = (mirror_entry_t **)mirrors_same_prefix->elts; | |
for (i = 0; i < mirrors_same_prefix->nelts; i++) { | |
mirror = mirrorp[i]; | |
debugLog(r, cfg, "same prefix: %-30s (score %4d) (rank %10d) (dist %d)", | |
mirror->identifier, mirror->score, mirror->rank, mirror->dist); | |
} | |
/* list the same-AS mirrors */ | |
mirrorp = (mirror_entry_t **)mirrors_same_as->elts; | |
for (i = 0; i < mirrors_same_as->nelts; i++) { | |
mirror = mirrorp[i]; | |
debugLog(r, cfg, "same AS: %-30s (score %4d) (rank %10d) (dist %d)", | |
mirror->identifier, mirror->score, mirror->rank, mirror->dist); | |
} | |
/* list the same-country mirrors */ | |
mirrorp = (mirror_entry_t **)mirrors_same_country->elts; | |
for (i = 0; i < mirrors_same_country->nelts; i++) { | |
mirror = mirrorp[i]; | |
debugLog(r, cfg, "same country: %-30s (score %4d) (rank %10d) (dist %d)", | |
mirror->identifier, mirror->score, mirror->rank, mirror->dist); | |
} | |
/* list the same-region mirrors */ | |
mirrorp = (mirror_entry_t **)mirrors_same_region->elts; | |
for (i = 0; i < mirrors_same_region->nelts; i++) { | |
mirror = mirrorp[i]; | |
debugLog(r, cfg, "same region: %-30s (score %4d) (rank %10d) (dist %d)", | |
mirror->identifier, mirror->score, mirror->rank, mirror->dist); | |
} | |
/* list all other mirrors */ | |
mirrorp = (mirror_entry_t **)mirrors_elsewhere->elts; | |
for (i = 0; i < mirrors_elsewhere->nelts; i++) { | |
mirror = mirrorp[i]; | |
debugLog(r, cfg, "elsewhere: %-30s (score %4d) (rank %10d) (dist %d)", | |
mirror->identifier, mirror->score, mirror->rank, mirror->dist); | |
} | |
debugLog(r, cfg, "classifying %d mirror%s: %d prefix, %d AS, %d country, " | |
"%d region, %d elsewhere", | |
mirror_cnt, (mirror_cnt == 1) ? "" : "s", | |
mirrors_same_prefix->nelts, | |
mirrors_same_as->nelts, | |
mirrors_same_country->nelts, | |
mirrors_same_region->nelts, | |
mirrors_elsewhere->nelts); | |
} | |
#if 0 | |
if ((mirror_cnt <= 0) || (!mirrors_same_prefix->nelts && !mirrors_same_as->nelts | |
&& !mirrors_same_country->nelts && !mirrors_same_region->nelts | |
&& !mirrors_elsewhere->nelts)) { | |
#endif | |
if (!mirrors_same_prefix->nelts && !mirrors_same_as->nelts && !mirrors_same_country->nelts | |
&& !mirrors_same_region->nelts && !mirrors_elsewhere->nelts) { | |
if (apr_is_empty_array(cfg->fallbacks)) { | |
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, | |
"[mod_mirrorbrain] no mirrors found for %s", filename); | |
} else { | |
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, | |
"[mod_mirrorbrain] no mirrors found for %s, " | |
"but fallback mirrors are available", filename); | |
} | |
/* can be used with a CustomLog directive, conditionally logging these requests */ | |
apr_table_setn(r->subprocess_env, "MB_NOMIRROR", "1"); | |
if (apr_is_empty_array(cfg->fallbacks)) { | |
switch (rep) { | |
case META4: | |
case METALINK: | |
if (meta_negotiated) { | |
debugLog(r, cfg, "would have to send empty metalink... -> deliver directly"); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} else { | |
debugLog(r, cfg, "would have to send empty metalink... -> 404"); | |
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
"[mod_mirrorbrain] Can't send metalink for %s (no mirrors)", filename); | |
return HTTP_NOT_FOUND; | |
} | |
} | |
} | |
} | |
if ((rep != REDIRECT) && (rep != YUMLIST) && (!hashbag)) { | |
hashbag = hashbag_fill(r, dbd, filename); | |
if (hashbag == NULL) { | |
debugLog(r, cfg, "no hashes found in database"); | |
} | |
} | |
/* if it makes sense, build a magnet link for later inclusion */ | |
char *magnet = NULL; | |
if (hashbag != NULL) { | |
switch (rep) { | |
case META4: | |
case METALINK: | |
case MAGNET: { | |
apr_array_header_t *m; | |
m = apr_array_make(r->pool, 7, sizeof(char *)); | |
/* BitTorrent info hash */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "magnet:?xt=urn:btih:%s", hashbag->btihhex); | |
#if 0 | |
/* SHA-1 */ | |
/* As far as I can see, this hash would actually need to be Base32 | |
* encoded, not hex. But it's probably not worth adding Base32 | |
* encoder just for this. */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "&xt=urn:sha1:%s", hashbag->sha1hex); | |
#endif | |
/* MD5 */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "&xt=urn:md5:%s", hashbag->md5hex); | |
/* size */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "&xl=%s", apr_off_t_toa(r->pool, r->finfo.size)); | |
/* file basename */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "&dn=%s", ap_escape_uri(r->pool, basename)); | |
/* a HTTP link to the file */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "&as=http://%s%s", ap_escape_uri(r->pool, r->hostname), | |
ap_escape_uri(r->pool, r->uri)); | |
if (!apr_is_empty_array(scfg->tracker_urls)) { | |
for (i = 0; i < scfg->tracker_urls->nelts; i++) { | |
char *url = ((char **) scfg->tracker_urls->elts)[i]; | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "&tr=%s", ap_escape_uri(r->pool, url)); | |
} | |
} | |
magnet = apr_array_pstrcat(r->pool, m, '\0'); | |
} | |
} | |
} | |
/* So, which representation are we going to send back? */ | |
switch (rep) { | |
case META4: | |
case METALINK: | |
debugLog(r, cfg, "Sending metalink"); | |
setenv_give(r, reps[rep].ext); | |
/* tell caches that this is negotiated response and that not every client will take it */ | |
apr_table_mergen(r->headers_out, "Vary", "accept"); | |
/* add rfc2183 header for filename, with .metalink appended | |
* because some clients trigger on that extension */ | |
apr_table_setn(r->headers_out, | |
"Content-Disposition", | |
apr_pstrcat(r->pool, | |
"attachment; filename=\"", | |
basename, ".", rep_ext, "\"", NULL)); | |
char *time_str = NULL; | |
switch (rep) { | |
case META4: | |
ap_set_content_type(r, "application/metalink4+xml; charset=UTF-8"); | |
ap_rputs( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | |
"<metalink xmlns=\"urn:ietf:params:xml:ns:metalink\">\n", r); | |
/* current time */ | |
time_str = apr_palloc(r->pool, RFC3339_DATE_LEN); | |
apr_time_exp_t tm; | |
/* r->request_time should be filled out already, and save us the syscall to time() | |
* through apr_time_now() */ | |
apr_time_exp_gmt(&tm, r->request_time); | |
apr_strftime(time_str, &len, RFC3339_DATE_LEN, "%Y-%m-%dT%H:%M:%SZ", &tm); | |
ap_rputs( " <generator>MirrorBrain/"MOD_MIRRORBRAIN_VER"</generator>\n", r); | |
/* The origin URL is meant to specify the location for revalidation of this metalink | |
* | |
* Unfortunately, r->parsed_uri.scheme and r->parsed_uri.hostname don't | |
* seem to be filled out (why?). But we can put it together from | |
* r->hostname and r->uri. Actually we should add the port. | |
* | |
* We could use r->server->server_hostname instead, which would be the configured server name. | |
* | |
* We use r->uri, not r->unparsed_uri, so we don't need to escape query strings for xml. | |
*/ | |
ap_rprintf(r, " <origin dynamic=\"true\">http://%s%s.%s</origin>\n", r->hostname, r->uri, rep_ext); | |
ap_rprintf(r, " <published>%s</published>\n", time_str); | |
if (scfg->metalink_publisher_name && scfg->metalink_publisher_url) { | |
ap_rputs( " <publisher>\n", r); | |
ap_rprintf(r, " <name>%s</name>\n", scfg->metalink_publisher_name); | |
ap_rprintf(r, " <url>%s</url>\n", scfg->metalink_publisher_url); | |
ap_rputs( " </publisher>\n\n", r); | |
} | |
break; | |
case METALINK: | |
ap_set_content_type(r, "application/metalink+xml; charset=UTF-8"); | |
ap_rputs( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | |
"<metalink version=\"3.0\" xmlns=\"http://www.metalinker.org/\"\n", r); | |
/* current time */ | |
time_str = apr_palloc(r->pool, APR_RFC822_DATE_LEN); | |
apr_rfc822_date(time_str, apr_time_now()); | |
ap_rprintf(r, " origin=\"http://%s%s.%s\"\n", r->hostname, r->uri, rep_ext); | |
ap_rputs( " generator=\"MirrorBrain "MOD_MIRRORBRAIN_VER" (see http://mirrorbrain.org/)\"\n", r); | |
ap_rputs( " type=\"dynamic\"", r); | |
ap_rprintf(r, " pubdate=\"%s\"", time_str); | |
ap_rprintf(r, " refreshdate=\"%s\">\n\n", time_str); | |
if (scfg->metalink_publisher_name && scfg->metalink_publisher_url) { | |
ap_rputs( " <publisher>\n", r); | |
ap_rprintf(r, " <name>%s</name>\n", scfg->metalink_publisher_name); | |
ap_rprintf(r, " <url>%s</url>\n", scfg->metalink_publisher_url); | |
ap_rputs( " </publisher>\n\n", r); | |
} | |
ap_rputs( " <files>\n", r); | |
break; | |
} | |
ap_rprintf(r, " <file name=\"%s\">\n", basename); | |
ap_rprintf(r, " <size>%s</size>\n\n", apr_off_t_toa(r->pool, r->finfo.size)); | |
ap_rprintf(r, " <!-- <mtime>%" APR_INT64_T_FMT "</mtime> -->\n\n", | |
apr_time_sec(r->finfo.mtime)); /* APR finfo times are in microseconds */ | |
if (hashbag != NULL) { | |
if (hashbag->id) { | |
ap_rprintf(r, " <!-- internal id: %s -->\n", | |
apr_off_t_toa(r->pool, hashbag->id)); | |
} | |
switch (rep) { | |
case META4: | |
if (hashbag->pgp) { | |
ap_rputs(" <signature mediatype=\"application/pgp-signature\">\n", r); | |
ap_rputs(hashbag->pgp, r); | |
ap_rputs(" </signature>\n", r); | |
} | |
if (hashbag->md5hex) | |
ap_rprintf(r, " <hash type=\"md5\">%s</hash>\n", hashbag->md5hex); | |
if (hashbag->sha1hex) | |
ap_rprintf(r, " <hash type=\"sha-1\">%s</hash>\n", hashbag->sha1hex); | |
if (hashbag->sha256hex) | |
ap_rprintf(r, " <hash type=\"sha-256\">%s</hash>\n", hashbag->sha256hex); | |
if (hashbag->sha1pieceshex | |
&& (hashbag->sha1piecesize > 0) | |
&& !apr_is_empty_array(hashbag->sha1pieceshex)) { | |
ap_rprintf(r, " <pieces length=\"%d\" type=\"sha-1\">\n", | |
hashbag->sha1piecesize); | |
char **p = (char **)hashbag->sha1pieceshex->elts; | |
for (i = 0; i < hashbag->sha1pieceshex->nelts; i++) { | |
ap_rprintf(r, " <hash>%s</hash>\n", p[i]); | |
} | |
ap_rputs(" </pieces>\n", r); | |
} | |
break; | |
case METALINK: | |
/* There are a few slight differences to the newer meta4 format */ | |
ap_rputs(" <verification>\n", r); | |
if (hashbag->pgp) { | |
ap_rprintf(r, " <signature type=\"pgp\" file=\"%s.asc\">\n", basename); | |
ap_rputs(hashbag->pgp, r); | |
ap_rputs(" </signature>\n", r); | |
} | |
if (hashbag->md5hex) | |
ap_rprintf(r, " <hash type=\"md5\">%s</hash>\n", hashbag->md5hex); | |
if (hashbag->sha1hex) | |
ap_rprintf(r, " <hash type=\"sha1\">%s</hash>\n", hashbag->sha1hex); | |
if (hashbag->sha256hex) | |
ap_rprintf(r, " <hash type=\"sha256\">%s</hash>\n", hashbag->sha256hex); | |
if (hashbag->sha1pieceshex | |
&& (hashbag->sha1piecesize > 0) | |
&& !apr_is_empty_array(hashbag->sha1pieceshex)) { | |
ap_rprintf(r, " <pieces length=\"%d\" type=\"sha1\">\n", | |
hashbag->sha1piecesize); | |
char **p = (char **)hashbag->sha1pieceshex->elts; | |
for (i = 0; i < hashbag->sha1pieceshex->nelts; i++) { | |
ap_rprintf(r, " <hash piece=\"%d\">%s</hash>\n", i, p[i]); | |
} | |
ap_rputs(" </pieces>\n", r); | |
} | |
ap_rputs(" </verification>\n", r); | |
break; | |
} | |
} | |
if (!hashbag && rep == METALINK) { | |
/* if the above failed, and we are creating a v3 metalink, let's try the old on-disk format */ | |
apr_finfo_t sb; | |
const char *hashfilename; | |
hashfilename = apr_psprintf(r->pool, "%s%s.size_%s", | |
scfg->metalink_hashes_prefix ? scfg->metalink_hashes_prefix : "", | |
r->filename, | |
apr_off_t_toa(r->pool, r->finfo.size)); | |
if (apr_stat(&sb, hashfilename, APR_FINFO_MIN, r->pool) == APR_SUCCESS && (sb.filetype == APR_REG)) { | |
debugLog(r, cfg, "hashfile '%s' exists", hashfilename); | |
/* the old on-disk format is injected as-is */ | |
if (sb.mtime == r->finfo.mtime) { | |
debugLog(r, cfg, "hashfile '%s' up to date, injecting", hashfilename); | |
apr_file_t *fh; | |
rv = apr_file_open(&fh, hashfilename, APR_READ, APR_OS_DEFAULT, r->pool); | |
if (rv == APR_SUCCESS) { | |
ap_send_fd(fh, r, 0, sb.size, &len); | |
apr_file_close(fh); | |
} else { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] could not open hashfile '%s'.", hashfilename); | |
} | |
} else { | |
debugLog(r, cfg, "hashfile '%s' outdated, ignoring", hashfilename); | |
} | |
} else { | |
debugLog(r, cfg, "no hash file found (%s)", hashfilename); | |
} | |
} | |
if (rep == METALINK) { | |
ap_rputs( " <resources>\n\n", r); | |
} | |
apr_finfo_t sb; | |
if (cfg->metalink_torrentadd_mask | |
&& !ap_regexec(cfg->metalink_torrentadd_mask, r->filename, 0, NULL, 0) | |
&& apr_stat(&sb, apr_pstrcat(r->pool, r->filename, ".torrent", NULL), APR_FINFO_MIN, r->pool) == APR_SUCCESS) { | |
debugLog(r, cfg, "found torrent file"); | |
ap_rprintf(r, " <url type=\"bittorrent\" preference=\"%d\">http://%s%s.torrent</url>\n\n", | |
100, | |
r->hostname, | |
r->uri); | |
} | |
if ((scfg->metalink_magnets == 1) && (hashbag != NULL) && (magnet != NULL)) { | |
switch (rep) { | |
case META4: | |
/* inclusion of torrents and other metaurls should probably happen here | |
* | |
* restrict the use of the new metaurl element to new metalinks */ | |
/* <metaurl mediatype="torrent">http://example.com/example.ext.torrent</metaurl> */ | |
ap_rputs("\n\n <!-- Meta URLs -->\n", r); | |
ap_rprintf(r, " <metaurl mediatype=\"torrent\">%s</metaurl>\n", magnet); | |
break; | |
case METALINK: | |
ap_rprintf(r, " <url type=\"bittorrent\" preference=\"%d\">%s</url>\n\n", | |
100, magnet); | |
} | |
} | |
ap_rprintf(r, "\n\n <!-- Found %d mirror%s: %d in the same network prefix, %d in the same " | |
"autonomous system,\n %d handling this country, %d in the same " | |
"region, %d elsewhere -->\n", | |
mirror_cnt, | |
(mirror_cnt == 1) ? "" : "s", | |
mirrors_same_prefix->nelts, | |
mirrors_same_as->nelts, | |
mirrors_same_country->nelts, | |
mirrors_same_region->nelts, | |
mirrors_elsewhere->nelts); | |
/* metalink resource priority */ | |
int prio = 0; | |
/* the highest v3 metalink preference according to the spec is 100, and | |
* we'll decrement it for each mirror by one, until zero is reached */ | |
int v3prio = 101; | |
/* insert broken mirrors at the top, for failover testing? */ | |
if(scfg->metalink_broken_test_mirrors | |
&& (ptr = (char*) apr_table_get(r->headers_in, "X-Broken-Mirrors")) | |
&& (apr_stat(&sb, scfg->metalink_broken_test_mirrors, | |
APR_FINFO_MIN, r->pool) == APR_SUCCESS)) { | |
debugLog(r, cfg, "adding broken mirrors (requested via X-Broken-Mirrors header)"); | |
apr_table_mergen(r->headers_out, "Cache-Control", "no-store,max-age=0"); | |
apr_table_mergen(r->headers_out, "Vary", "X-Broken-Mirrors"); | |
apr_table_addn(r->headers_out, "X-Broken-Mirrors", "true"); | |
apr_file_t *fh; | |
if (apr_file_open(&fh, scfg->metalink_broken_test_mirrors, | |
APR_READ, APR_OS_DEFAULT, r->pool) == APR_SUCCESS) { | |
ap_send_fd(fh, r, 0, sb.size, &len); | |
apr_file_close(fh); | |
} | |
if (strcmp(ptr, "only") == 0) { | |
/* finish here */ | |
switch (rep) { | |
case META4: | |
ap_rputs( " </file>\n" | |
"</metalink>\n", r); | |
break; | |
case METALINK: | |
ap_rputs( " </resources>\n" | |
" </file>\n" | |
" </files>\n" | |
"</metalink>\n", r); | |
break; | |
} | |
return OK; | |
} | |
/* we leave a gap for insertion of 15 such non-working URLs, | |
* still keeping decrementing the preference in order */ | |
v3prio = 85; | |
} | |
ap_rprintf(r, "\n <!-- Mirrors in the same network (%s): -->\n", | |
(strcmp(prefix, "--") == 0) ? "unknown" : prefix); | |
mirrorp = (mirror_entry_t **)mirrors_same_prefix->elts; | |
for (i = 0; i < mirrors_same_prefix->nelts; i++) { | |
mirror = mirrorp[i]; | |
prio++; | |
if (v3prio) v3prio--; | |
emit_metalink_url(r, rep, mirror->baseurl, mirror->country_code, filename, v3prio, prio); | |
} | |
ap_rprintf(r, "\n <!-- Mirrors in the same AS (%s): -->\n", | |
(strcmp(as, "--") == 0) ? "unknown" : as); | |
mirrorp = (mirror_entry_t **)mirrors_same_as->elts; | |
for (i = 0; i < mirrors_same_as->nelts; i++) { | |
mirror = mirrorp[i]; | |
if (mirror->prefix_only) | |
continue; | |
prio++; | |
if (v3prio) v3prio--; | |
emit_metalink_url(r, rep, mirror->baseurl, mirror->country_code, filename, v3prio, prio); | |
} | |
/* failed geoip lookups yield country='--', which leads to invalid XML */ | |
ap_rprintf(r, "\n <!-- Mirrors which handle this country (%s): -->\n", | |
(strcmp(country_code, "--") == 0) ? "unknown" : country_code); | |
mirrorp = (mirror_entry_t **)mirrors_same_country->elts; | |
for (i = 0; i < mirrors_same_country->nelts; i++) { | |
mirror = mirrorp[i]; | |
if (mirror->prefix_only || mirror->as_only) | |
continue; | |
prio++; | |
if (v3prio) v3prio--; | |
emit_metalink_url(r, rep, mirror->baseurl, mirror->country_code, filename, v3prio, prio); | |
} | |
ap_rprintf(r, "\n <!-- Mirrors in the same continent (%s): -->\n", | |
(strcmp(continent_code, "--") == 0) ? "unknown" : continent_code); | |
mirrorp = (mirror_entry_t **)mirrors_same_region->elts; | |
for (i = 0; i < mirrors_same_region->nelts; i++) { | |
mirror = mirrorp[i]; | |
if (mirror->prefix_only || mirror->as_only || mirror->country_only) | |
continue; | |
prio++; | |
if (v3prio) v3prio--; | |
emit_metalink_url(r, rep, mirror->baseurl, mirror->country_code, filename, v3prio, prio); | |
} | |
ap_rputs("\n <!-- Mirrors in the rest of the world: -->\n", r); | |
mirrorp = (mirror_entry_t **)mirrors_elsewhere->elts; | |
for (i = 0; i < mirrors_elsewhere->nelts; i++) { | |
mirror = mirrorp[i]; | |
if (mirror->prefix_only || mirror->as_only | |
|| mirror->country_only || mirror->region_only) { | |
continue; | |
} | |
prio++; | |
if (v3prio) v3prio--; | |
emit_metalink_url(r, rep, mirror->baseurl, mirror->country_code, filename, v3prio, prio); | |
} | |
switch (rep) { | |
case META4: | |
ap_rputs( " </file>\n" | |
"</metalink>\n", r); | |
break; | |
case METALINK: | |
ap_rputs( " </resources>\n" | |
" </file>\n" | |
" </files>\n" | |
"</metalink>\n", r); | |
break; | |
} | |
return OK; | |
/* send an HTML list instead of doing a redirect? */ | |
case MIRRORLIST: | |
setenv_give(r, "mirrorlist"); | |
debugLog(r, cfg, "Sending mirrorlist"); | |
ap_set_content_type(r, "text/html; charset=UTF-8"); | |
if (scfg->mirrorlist_header) { | |
/* send the configured custom header */ | |
apr_file_t *fh; | |
rv = apr_stat(&sb, scfg->mirrorlist_header, APR_FINFO_MIN, r->pool); | |
if (rv != APR_SUCCESS) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
"[mod_mirrorbrain] could not stat mirrorlist header file '%s'.", | |
scfg->mirrorlist_header); | |
} else { | |
rv = apr_file_open(&fh, scfg->mirrorlist_header, APR_READ, APR_OS_DEFAULT, r->pool); | |
if (rv == APR_SUCCESS) { | |
ap_send_fd(fh, r, 0, sb.size, &len); | |
apr_file_close(fh); | |
} else { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] could not open mirrorlist header '%s'.", | |
scfg->mirrorlist_header); | |
} | |
} | |
} else { | |
/* standard header */ | |
ap_rputs(DOCTYPE_XHTML_1_0T | |
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" | |
"<head>\n" | |
" <title>Mirror List</title>\n", r); | |
if (scfg->mirrorlist_stylesheet) { | |
ap_rprintf(r, " <link type=\"text/css\" rel=\"stylesheet\" href=\"%s\" />\n", | |
scfg->mirrorlist_stylesheet); | |
} | |
ap_rputs("</head>\n\n" "<body>\n", r); | |
} | |
ap_rputs("<div id=\"mirrorbrain-details\">\n", r); | |
ap_rprintf(r, " <h2>Mirrors for <a href=\"http://%s%s\">http://%s%s</a></h2>\n" | |
" <br/>\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
ap_rputs(" <address>Powered by <a href=\"http://mirrorbrain.org/\">MirrorBrain</a></address>\n", r); | |
/* Metadata */ | |
ap_rputs(" <ul>\n", r); | |
char buf[5]; | |
ap_rprintf(r, " <li>Size: %s (%s bytes)</li>\n", | |
apr_strfsize(r->finfo.size, buf), | |
apr_off_t_toa(r->pool, r->finfo.size)); | |
time_str = apr_palloc(r->pool, APR_RFC822_DATE_LEN); | |
apr_rfc822_date(time_str, r->finfo.mtime); | |
ap_rprintf(r, " <li>Last modified: %s (Unix time: %" APR_INT64_T_FMT ")</li>\n", time_str, apr_time_sec(r->finfo.mtime)); | |
if (hashbag != NULL) { | |
if (hashbag->sha256hex) | |
ap_rprintf(r, " <li><a href=\"http://%s%s.sha256\">SHA-256 Hash</a>: <tt>%s</tt> " | |
"</li>\n", r->hostname, r->uri, hashbag->sha256hex); | |
if (hashbag->sha1hex) | |
ap_rprintf(r, " <li><a href=\"http://%s%s.sha1\">SHA-1 Hash</a>: <tt>%s</tt> " | |
"</li>\n", r->hostname, r->uri, hashbag->sha1hex); | |
if (hashbag->md5hex) | |
ap_rprintf(r, " <li><a href=\"http://%s%s.md5\">MD5 Hash</a>: <tt>%s</tt> " | |
"</li>\n", r->hostname, r->uri, hashbag->md5hex); | |
if (hashbag->btihhex) | |
ap_rprintf(r, " <li><a href=\"http://%s%s.btih\">BitTorrent Information Hash</a>: <tt>%s</tt> " | |
"</li>\n", r->hostname, r->uri, hashbag->btihhex); | |
if (hashbag->pgp) { | |
/* contrary to the hashes, we don't have a handler for .asc files, because | |
* the database always only gets a signature when one already exists on-disk */ | |
ap_rprintf(r, " <li>PGP signature <a href=\"http://%s%s.asc\">available</a> " | |
"</li>\n", r->hostname, r->uri); | |
} | |
} | |
ap_rputs(" </ul>\n", r); | |
/* Metalink info */ | |
ap_rputs(" <br/>\n" | |
" <blockquote>Metalinks for reliable downloads:\n" | |
" <br/>\n", r); | |
ap_rprintf(r, " <a href=\"http://%s%s.meta4\">http://%s%s.meta4</a> (IETF Metalink)" | |
" <br/>\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
ap_rprintf(r, " <a href=\"http://%s%s.metalink\">http://%s%s.metalink</a> (old (v3) Metalink)" | |
" <br/>\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
if (hashbag) { | |
ap_rprintf(r, " P2P Links:\n<br/>" | |
" <a href=\"http://%s%s.torrent\">http://%s%s.torrent</a> (BitTorrent)\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
ap_rprintf(r, " <br/><a href=\"http://%s%s.magnet\">http://%s%s.magnet</a> (Magnet)\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
if (hashbag->sha1hex && (hashbag->zblocksize > 0) | |
&& hashbag->zhashlens && hashbag->zsumshex) { | |
ap_rprintf(r, " <br/>zsync Link:\n<br/>" | |
" <a href=\"http://%s%s.zsync\">http://%s%s.zsync</a>\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
} | |
} | |
ap_rputs(" </blockquote>\n\n", r); | |
ap_rprintf(r, " <p>List of best mirrors for IP address %s, located ", clientip); | |
if (lat != 0 && lng != 0) { | |
ap_rprintf(r, "at %f,%f ", lat, lng); | |
} | |
if (strcmp(country_code, "--") != 0) { | |
ap_rprintf(r, "in %s (%s)", country_name, country_code); | |
} else { | |
ap_rputs("in an unknown country", r); | |
} | |
if (strcmp(prefix, "--") != 0) { | |
ap_rprintf(r, ", \n network %s (autonomous system %s)", prefix, as); | |
} | |
ap_rputs(": ", r); | |
if (lat != 0 && lng != 0) { | |
ap_rputs(" ", r); | |
apr_array_header_t *topten = get_n_best_mirrors(r, 9, mirrors_same_prefix, mirrors_same_as, | |
mirrors_same_country, mirrors_same_region, | |
mirrors_elsewhere); | |
mirrorp = (mirror_entry_t **)topten->elts; | |
ap_rprintf(r, "\n <a href=\"http://maps.google.com/maps/api/staticmap?size=640x640" | |
"&maptype=terrain&visible&sensor=false&markers=size:mid|color:red|%f,%f", lat, lng); | |
for (i = 0; i < topten->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "&markers=size:normal|color:yellow|label:%d|%f,%f", i+1, mirror->lat, mirror->lng); | |
} | |
ap_rputs("\">\n <img src=\"", r); | |
ap_rprintf(r, "http://maps.google.com/maps/api/staticmap?size=50x50" | |
"&maptype=terrain&visible&sensor=false&markers=size:mid|color:red|%f,%f", lat, lng); | |
for (i = 0; i < topten->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "&markers=size:normal|color:yellow|label:%d|%f,%f", i+1, mirror->lat, mirror->lng); | |
} | |
ap_rputs("\" width=\"50\" height=\"50\" alt=\"map showing the closest mirrors\"/></a> ", r); | |
} | |
ap_rputs("</p>\n", r); | |
if ((mirror_cnt <= 0) || (!mirrors_same_prefix->nelts && !mirrors_same_as->nelts | |
&& !mirrors_same_country->nelts && !mirrors_same_region->nelts | |
&& !mirrors_elsewhere->nelts)) { | |
ap_rprintf(r, " <p>I am very sorry, but no mirror was found. Feel free to download directly:<br/>\n"); | |
ap_rprintf(r, " <a href=\"http://%s%s\">http://%s%s</a> </p>\n", | |
r->hostname, r->uri, r->hostname, r->uri); | |
ap_rputs("</body></html>\n", r); | |
return OK; | |
} | |
/* prefix */ | |
if (!apr_is_empty_array(mirrors_same_prefix)) { | |
ap_rprintf(r, "\n <h3>Found %d mirror%s directly nearby (within the same network prefix: %s :-)</h3>\n", | |
mirrors_same_prefix->nelts, | |
(mirrors_same_prefix->nelts == 1) ? "" : "s", | |
prefix); | |
ap_rputs(" <ul>\n", r); | |
mirrorp = (mirror_entry_t **)mirrors_same_prefix->elts; | |
for (i = 0; i < mirrors_same_prefix->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, " <li><a href=\"%s%s\">%s%s</a> (%s, prio %d)</li>\n", | |
mirror->baseurl, filename, | |
mirror->baseurl, filename, | |
mirror->country_code, | |
mirror->score); | |
} | |
ap_rputs(" </ul>\n", r); | |
} | |
/* AS */ | |
if (!apr_is_empty_array(mirrors_same_as)) { | |
ap_rprintf(r, "\n <h3>Found %d mirror%s very close (within the same autonomous system (AS%s):</h3>\n", | |
mirrors_same_as->nelts, | |
(mirrors_same_as->nelts == 1) ? "" : "s", | |
as); | |
ap_rputs(" <ul>\n", r); | |
mirrorp = (mirror_entry_t **)mirrors_same_as->elts; | |
for (i = 0; i < mirrors_same_as->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, " <li><a href=\"%s%s\">%s%s</a> (%s, prio %d)</li>\n", | |
mirror->baseurl, filename, | |
mirror->baseurl, filename, | |
mirror->country_code, | |
mirror->score); | |
} | |
ap_rputs(" </ul>\n", r); | |
} | |
/* country */ | |
if (!apr_is_empty_array(mirrors_same_country)) { | |
ap_rprintf(r, "\n <h3>Found %d mirror%s which handle this country (%s):</h3>\n", | |
mirrors_same_country->nelts, | |
(mirrors_same_country->nelts == 1) ? "" : "s", | |
country_code); | |
ap_rputs(" <ul>\n", r); | |
mirrorp = (mirror_entry_t **)mirrors_same_country->elts; | |
for (i = 0; i < mirrors_same_country->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, " <li><a href=\"%s%s\">%s%s</a> (%s, prio %d)</li>\n", | |
mirror->baseurl, filename, | |
mirror->baseurl, filename, | |
mirror->country_code, | |
mirror->score); | |
} | |
ap_rputs(" </ul>\n", r); | |
} | |
/* region */ | |
if (!apr_is_empty_array(mirrors_same_region)) { | |
ap_rprintf(r, "\n <h3>Found %d mirror%s in other countries, but same continent (%s):</h3>\n", | |
mirrors_same_region->nelts, | |
(mirrors_same_region->nelts == 1) ? "" : "s", | |
continent_code); | |
ap_rputs(" <ul>\n", r); | |
mirrorp = (mirror_entry_t **)mirrors_same_region->elts; | |
for (i = 0; i < mirrors_same_region->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, " <li><a href=\"%s%s\">%s%s</a> (%s, prio %d)</li>\n", | |
mirror->baseurl, filename, | |
mirror->baseurl, filename, | |
mirror->country_code, | |
mirror->score); | |
} | |
ap_rputs(" </ul>\n", r); | |
} | |
/* elsewhere */ | |
if (!apr_is_empty_array(mirrors_elsewhere)) { | |
ap_rprintf(r, "\n <h3>Found %d mirror%s in other parts of the world:</h3>\n", | |
mirrors_elsewhere->nelts, | |
(mirrors_elsewhere->nelts == 1) ? "" : "s"); | |
ap_rputs(" <ul>\n", r); | |
mirrorp = (mirror_entry_t **)mirrors_elsewhere->elts; | |
for (i = 0; i < mirrors_elsewhere->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, " <li><a href=\"%s%s\">%s%s</a> (%s, prio %d)</li>\n", | |
mirror->baseurl, filename, | |
mirror->baseurl, filename, | |
mirror->country_code, | |
mirror->score); | |
} | |
ap_rputs(" </ul>\n", r); | |
} | |
ap_rputs("</div> <!-- mirrorbrain-details -->\n", r); | |
if (scfg->mirrorlist_footer) { | |
/* send the configured custom footer */ | |
apr_file_t *fh; | |
rv = apr_stat(&sb, scfg->mirrorlist_footer, APR_FINFO_MIN, r->pool); | |
if (rv != APR_SUCCESS) { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
"[mod_mirrorbrain] could not stat mirrorlist footer file '%s'.", | |
scfg->mirrorlist_footer); | |
} else { | |
rv = apr_file_open(&fh, scfg->mirrorlist_footer, APR_READ, APR_OS_DEFAULT, r->pool); | |
if (rv == APR_SUCCESS) { | |
ap_send_fd(fh, r, 0, sb.size, &len); | |
apr_file_close(fh); | |
} else { | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, | |
"[mod_mirrorbrain] could not open mirrorlist footer '%s'.", | |
scfg->mirrorlist_footer); | |
} | |
} | |
} else { | |
ap_rputs("</body>\n", r); | |
ap_rputs("</html>\n", r); | |
} | |
return OK; | |
case TORRENT: | |
{ | |
if (!hashbag || (hashbag->sha1piecesize <= 0) || apr_is_empty_array(hashbag->sha1pieceshex)) { | |
debugLog(r, cfg, "Torrent requested, but no hashes found for %s", filename); | |
return HTTP_NOT_FOUND; | |
} | |
if (apr_is_empty_array(scfg->tracker_urls)) { | |
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, | |
"[mod_mirrorbrain] Cannot create torrent: at least one MirrorBrainTorrentTrackerURL must configured"); | |
return HTTP_NOT_FOUND; | |
} | |
setenv_give(r, "torrent"); | |
debugLog(r, cfg, "Sending torrent"); | |
ap_set_content_type(r, "application/x-bittorrent"); | |
char *tracker = ((char **) scfg->tracker_urls->elts)[0]; | |
ap_rprintf(r, "d" | |
"8:announce" | |
"%d:%s", strlen(tracker), tracker); | |
ap_rputs( "13:announce-listll", r); | |
for (i = 0; i < scfg->tracker_urls->nelts; i++) { | |
tracker = ((char **) scfg->tracker_urls->elts)[i]; | |
ap_rprintf(r, "%d:%s", strlen(tracker), tracker); | |
} | |
ap_rputs( "e" | |
"e", r); | |
ap_rprintf(r, "7:comment" | |
"%d:%s", strlen(basename), basename); | |
/* This is meant to be the creation time of the torrent, | |
* but let's take the mtime of the file since we can generate the | |
* torrent any time */ | |
ap_rprintf(r, "10:created by" | |
"%d:MirrorBrain/%s", | |
strlen("MirrorBrain/") + strlen(MOD_MIRRORBRAIN_VER), | |
MOD_MIRRORBRAIN_VER); | |
ap_rprintf(r, "13:creation date" | |
"i%se", apr_itoa(r->pool, apr_time_sec(r->finfo.mtime))); | |
ap_rprintf(r, "4:info" | |
"d" | |
"6:length" | |
"i%se", | |
apr_off_t_toa(r->pool, r->finfo.size)); | |
ap_rprintf(r, "6:md5sum" | |
"%d:%s", MD5_DIGESTSIZE * 2, hashbag->md5hex); | |
ap_rprintf(r, "4:name" | |
"%d:%s" | |
"12:piece length" | |
"i%de" | |
"6:pieces" | |
"%d:", strlen(basename), | |
basename, | |
hashbag->sha1piecesize, | |
(hashbag->sha1pieceshex->nelts * SHA1_DIGESTSIZE)); | |
char **p = (char **)hashbag->sha1pieceshex->elts; | |
for (i = 0; i < hashbag->sha1pieceshex->nelts; i++) { | |
ap_rwrite(hex_decode(r, p[i], SHA1_DIGESTSIZE), SHA1_DIGESTSIZE, r); | |
} | |
ap_rprintf(r, "4:sha1" | |
"%d:", SHA1_DIGESTSIZE); | |
ap_rwrite( hex_decode(r, hashbag->sha1hex, SHA1_DIGESTSIZE), | |
SHA1_DIGESTSIZE, r); | |
ap_rprintf(r, "6:sha256" | |
"%d:", SHA256_DIGESTSIZE); | |
ap_rwrite( hex_decode(r, hashbag->sha256hex, SHA256_DIGESTSIZE), | |
SHA256_DIGESTSIZE, r); | |
/* end of info hash: */ | |
ap_rputs( "e", r); | |
if (!apr_is_empty_array(scfg->dhtnodes)) { | |
ap_rputs( "5:nodes" | |
"l", r); | |
for (i = 0; i < scfg->dhtnodes->nelts; i++) { | |
dhtnode_t node = ((dhtnode_t *) scfg->dhtnodes->elts)[i]; | |
ap_rprintf(r, "l" "%d:%s" "i%de" "e", strlen(node.name), node.name, | |
node.port); | |
} | |
ap_rputs( "e", r); | |
} | |
/* Web seeds | |
* | |
* There's a trick: send this stuff _after_ the sha1 pieces. | |
* The original BitTorrent client doesn't ignore unknown keys, but | |
* refuses to grok the torrent and says "is not a valid torrent file | |
* (not a valid bencoded string)". (Which is wrong.) However, it does | |
* _not_ seam to read past the pieces; at least it doesn't complain | |
* about stuff occurring afterwards. */ | |
{ | |
apr_array_header_t *m; | |
m = apr_array_make(r->pool, 11, sizeof(char *)); | |
int found_urls = 0; | |
mirrorp = (mirror_entry_t **)mirrors_same_prefix->elts; | |
for (i = 0; i < mirrors_same_prefix->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "%d:%s%s", (strlen(mirror->baseurl) + strlen(filename)), | |
mirror->baseurl, filename); | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_same_as->elts; | |
for (i = 0; i < mirrors_same_as->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "%d:%s%s", (strlen(mirror->baseurl) + strlen(filename)), | |
mirror->baseurl, filename); | |
} | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_same_country->elts; | |
for (i = 0; i < mirrors_same_country->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "%d:%s%s", (strlen(mirror->baseurl) + strlen(filename)), | |
mirror->baseurl, filename); | |
} | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_same_region->elts; | |
for (i = 0; i < mirrors_same_region->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "%d:%s%s", (strlen(mirror->baseurl) + strlen(filename)), | |
mirror->baseurl, filename); | |
} | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_elsewhere->elts; | |
for (i = 0; i < mirrors_elsewhere->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "%d:%s%s", (strlen(mirror->baseurl) + strlen(filename)), | |
mirror->baseurl, filename); | |
} | |
} | |
/* add the redirector, in case there wasn't any mirror */ | |
if (!found_urls) { | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "%d:http://%s%s", (7 + strlen(r->hostname) + strlen(r->uri)), | |
r->hostname, r->uri); | |
} | |
#if 0 | |
/* it would be simple to just list the URL of the redirector itself, but aria2c | |
* retrieves a Metalink then and doesn't expect it in that situation. Maybe later */ | |
APR_ARRAY_PUSH(m, char *) = | |
apr_psprintf(r->pool, "8:url-list" | |
"%d:http://%s%s", (7 + strlen(r->hostname) + strlen(r->uri)), | |
r->hostname, r->uri); | |
#endif | |
if (!apr_is_empty_array(m)) { | |
ap_rputs( "7:sourcesl", r); | |
for (i = 0; i < m->nelts; i++) { | |
char *e = ((char **) m->elts)[i]; | |
ap_rputs(e, r); | |
} | |
ap_rputs( "e8:url-listl", r); | |
for (i = 0; i < m->nelts; i++) { | |
char *e = ((char **) m->elts)[i]; | |
ap_rputs(e, r); | |
} | |
} | |
} | |
ap_rputs( "e", r); | |
ap_rputs( "e", r); | |
return OK; | |
} | |
case ZSYNC: | |
if (!hashbag || !hashbag->sha1hex || (hashbag->zblocksize == 0) | |
|| !hashbag->zhashlens || !hashbag->zsumshex) { | |
debugLog(r, cfg, "zsync requested, but required data is missing"); | |
return HTTP_NOT_FOUND; | |
} | |
setenv_give(r, "zsync"); | |
debugLog(r, cfg, "Sending zsync"); | |
ap_set_content_type(r, "application/x-zsync"); | |
ap_rputs("zsync: 0.6.1\n", r); | |
ap_rprintf(r, "Filename: %s\n", basename); | |
time_str = apr_palloc(r->pool, APR_RFC822_DATE_LEN); | |
apr_rfc822_date(time_str, r->finfo.mtime); | |
ap_rprintf(r, "MTime: %s\n", time_str); | |
ap_rprintf(r, "Blocksize: %d\n", hashbag->zblocksize); | |
ap_rprintf(r, "Length: %s\n", apr_off_t_toa(r->pool, r->finfo.size)); | |
ap_rprintf(r, "Hash-Lengths: %s\n", hashbag->zhashlens); | |
/* URLs */ | |
/* The zsync client (as of 0.6.1) tries the provided URLs in random order. | |
* Thus, we need to restrict the list of URLs to the ones that are | |
* closest; otherwise, it will download from anywhere in the world. */ | |
int found_urls = 0; | |
mirrorp = (mirror_entry_t **)mirrors_same_prefix->elts; | |
for (i = 0; i < mirrors_same_prefix->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "URL: %s%s\n", mirror->baseurl, filename); | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_same_as->elts; | |
for (i = 0; i < mirrors_same_as->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "URL: %s%s\n", mirror->baseurl, filename); | |
} | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_same_country->elts; | |
for (i = 0; i < mirrors_same_country->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "URL: %s%s\n", mirror->baseurl, filename); | |
} | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_same_region->elts; | |
for (i = 0; i < mirrors_same_region->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "URL: %s%s\n", mirror->baseurl, filename); | |
} | |
} | |
if (!found_urls) { | |
mirrorp = (mirror_entry_t **)mirrors_elsewhere->elts; | |
for (i = 0; i < mirrors_elsewhere->nelts; i++, found_urls++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "URL: %s%s\n", mirror->baseurl, filename); | |
} | |
} | |
/* add the redirector, in case there wasn't any mirror */ | |
if (!found_urls) { | |
ap_rprintf(r, "URL: http://%s%s\n", r->hostname, r->uri); | |
} | |
ap_rprintf(r, "SHA-1: %s\n\n", hashbag->sha1hex); | |
if (!hashbag->zsumshex || !hashbag->zsumshex[0]) { | |
/* A zero-length file will correctly have zero zsync checksums */ | |
return OK; | |
} | |
int l = strlen(hashbag->zsumshex); | |
ap_rwrite(hex_decode(r, hashbag->zsumshex, l/2), | |
l/2, r); | |
return OK; | |
case MAGNET: | |
if (!hashbag || !magnet) { | |
return HTTP_NOT_FOUND; | |
} | |
ap_set_content_type(r, "text/plain; charset=UTF-8"); | |
ap_rprintf(r, "%s\n", magnet); | |
setenv_give(r, "magnet"); | |
return OK; | |
case YUMLIST: | |
ap_set_content_type(r, "text/plain; charset=UTF-8"); | |
apr_array_header_t *topten = get_n_best_mirrors(r, 10, mirrors_same_prefix, mirrors_same_as, | |
mirrors_same_country, mirrors_same_region, | |
mirrors_elsewhere); | |
if (topten->nelts > 0) { | |
mirrorp = (mirror_entry_t **)topten->elts; | |
for (i = 0; i < topten->nelts; i++) { | |
mirror = mirrorp[i]; | |
ap_rprintf(r, "%s%s/\n", mirror->baseurl, yum->dir); | |
} | |
} else { | |
ap_rprintf(r, "http://%s/%s/\n", r->hostname, yum->dir); | |
} | |
setenv_give(r, "yumlist"); | |
return OK; | |
} /* end switch representation */ | |
const char *found_in; | |
/* choose from country, then from region, then from elsewhere */ | |
if (!chosen) { | |
if (!apr_is_empty_array(mirrors_same_prefix)) { | |
mirrorp = (mirror_entry_t **)mirrors_same_prefix->elts; | |
chosen = mirrorp[find_best(mirrors_same_prefix)]; | |
found_in = "prefix"; | |
} else if (!apr_is_empty_array(mirrors_same_as)) { | |
mirrorp = (mirror_entry_t **)mirrors_same_as->elts; | |
chosen = mirrorp[find_best(mirrors_same_as)]; | |
found_in = "AS"; | |
} else if (!apr_is_empty_array(mirrors_same_country)) { | |
mirrorp = (mirror_entry_t **)mirrors_same_country->elts; | |
chosen = mirrorp[find_best(mirrors_same_country)]; | |
if (strcasecmp(chosen->country_code, country_code) == 0) { | |
found_in = "country"; | |
} else { | |
found_in = "other_country"; | |
} | |
} else if (!apr_is_empty_array(mirrors_same_region)) { | |
mirrorp = (mirror_entry_t **)mirrors_same_region->elts; | |
chosen = mirrorp[find_best(mirrors_same_region)]; | |
found_in = "region"; | |
} else if (!apr_is_empty_array(mirrors_elsewhere)) { | |
mirrorp = (mirror_entry_t **)mirrors_elsewhere->elts; | |
chosen = mirrorp[find_best(mirrors_elsewhere)]; | |
found_in = "other"; | |
} | |
} | |
if (!chosen) { | |
ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, | |
"[mod_mirrorbrain] '%s': no usable mirrors after classification. Have to deliver directly.", | |
filename); | |
setenv_give(r, "file"); | |
return DECLINED; | |
} | |
debugLog(r, cfg, "Chose server %s", chosen->identifier); | |
/* Build target URI */ | |
if (cfg->stampkey) { | |
const char *epoch = apr_itoa(r->pool, apr_time_sec(r->request_time)); | |
const char *epochkey = apr_pstrcat(r->pool, epoch, " ", cfg->stampkey, NULL); | |
const char *stamp = ap_md5(r->pool, (unsigned const char *)epochkey); | |
debugLog(r, cfg, "stamp: '%s' -> %s", epochkey, stamp); | |
uri = apr_pstrcat(r->pool, chosen->baseurl, filename, | |
"?time=", epoch, | |
"&stamp=", stamp, NULL); | |
} else { | |
uri = apr_pstrcat(r->pool, chosen->baseurl, filename, NULL); | |
} | |
/* Send it away: set a "Location:" header and 302 redirect. */ | |
debugLog(r, cfg, "Redirect to '%s'", uri); | |
/* for _conditional_ logging, leave some mark */ | |
apr_table_setn(r->subprocess_env, "MB_REDIRECTED", "1"); | |
apr_table_setn(r->subprocess_env, "MB_REALM", apr_pstrdup(r->pool, found_in)); | |
apr_table_setn(r->err_headers_out, "X-MirrorBrain-Mirror", chosen->identifier); | |
apr_table_setn(r->err_headers_out, "X-MirrorBrain-Realm", found_in); | |
apr_table_setn(r->headers_out, "Location", uri); | |
#ifdef WITH_MEMCACHE | |
if (scfg->memcached_on) { | |
/* memorize IP<->mirror association in memcache */ | |
m_val = apr_itoa(r->pool, chosen->id); | |
debugLog(r, cfg, "memcache insert: '%s' -> '%s'", m_key, m_val); | |
if (scfg->memcached_lifetime == UNSET) | |
scfg->memcached_lifetime = DEFAULT_MEMCACHED_LIFETIME; | |
rv = apr_memcache_set(memctxt, m_key, m_val, strlen(m_val), scfg->memcached_lifetime, 0); | |
if (rv != APR_SUCCESS) | |
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, | |
"[mod_mirrorbrain] memcache error setting key '%s' " | |
"with %d bytes of data", | |
m_key, (int) strlen(m_val)); | |
} | |
#endif | |
setenv_give(r, "redirect"); | |
return HTTP_MOVED_TEMPORARILY; | |
} | |
#ifdef WITH_MEMCACHE | |
static int mb_status_hook(request_rec *r, int flags) | |
{ | |
apr_uint16_t i; | |
apr_status_t rv; | |
apr_memcache_t *memctxt; /* memcache context provided by mod_memcache */ | |
apr_memcache_stats_t *stats; | |
mb_server_conf *sc = ap_get_module_config(r->server->module_config, &mirrorbrain_module); | |
if (sc == NULL || flags & AP_STATUS_SHORT) | |
return OK; | |
if (!sc->memcached_on) | |
return OK; | |
memctxt = ap_memcache_client(r->server); | |
for (i = 0; i < memctxt->ntotal; i++) { | |
rv = apr_memcache_stats(memctxt->live_servers[i], r->pool, &stats); | |
ap_rputs("<hr />\n", r); | |
ap_rprintf(r, "<h1>MemCached Status for %s:%d</h1>\n\n", | |
memctxt->live_servers[i]->host, | |
memctxt->live_servers[i]->port); | |
ap_rputs("\n\n<table border=\"0\">", r); | |
ap_rprintf(r, "<tr><td>version: </td><td>%s</td>\n", stats->version); | |
ap_rprintf(r, "<tr><td>pid: </td><td>%d</td>\n", stats->pid); | |
ap_rprintf(r, "<tr><td>uptime: </td><td>\t%d</td>\n", stats->uptime); | |
ap_rprintf(r, "<tr><td>pointer_size: </td><td>\t%d</td>\n", stats->pointer_size); | |
ap_rprintf(r, "<tr><td>rusage_user: </td><td>\t%" APR_INT64_T_FMT "</td>\n", stats->rusage_user); | |
ap_rprintf(r, "<tr><td>rusage_system: </td><td>\t%" APR_INT64_T_FMT "</td>\n", stats->rusage_system); | |
ap_rprintf(r, "<tr><td>curr_items: </td><td>\t%d</td>\n", stats->curr_items); | |
ap_rprintf(r, "<tr><td>total_items: </td><td>\t%d</td>\n", stats->total_items); | |
ap_rprintf(r, "<tr><td>bytes used: </td><td>\t%" APR_UINT64_T_FMT "</td>\n", stats->bytes); | |
ap_rprintf(r, "<tr><td>curr_connections: </td><td>\t%d</td>\n", stats->curr_connections); | |
ap_rprintf(r, "<tr><td>total_connections: </td><td>\t%d</td>\n", stats->total_connections); | |
ap_rprintf(r, "<tr><td>connection_structures: </td><td>\t%d</td>\n", stats->connection_structures); | |
ap_rprintf(r, "<tr><td>cmd_get: </td><td>\t%d</td>\n", stats->cmd_get); | |
ap_rprintf(r, "<tr><td>cmd_set: </td><td>\t%d</td>\n", stats->cmd_set); | |
ap_rprintf(r, "<tr><td>get_hits: </td><td>\t%d</td>\n", stats->get_hits); | |
ap_rprintf(r, "<tr><td>get_misses: </td><td>\t%d</td>\n", stats->get_misses); | |
ap_rprintf(r, "<tr><td>evictions: </td><td>\t%" APR_UINT64_T_FMT "</td>\n", stats->evictions); | |
ap_rprintf(r, "<tr><td>bytes_read: </td><td>\t%" APR_UINT64_T_FMT "</td>\n", stats->bytes_read); | |
ap_rprintf(r, "<tr><td>bytes_written: </td><td>\t%" APR_UINT64_T_FMT "</td>\n", stats->bytes_written); | |
ap_rprintf(r, "<tr><td>limit_maxbytes: </td><td>\t%d</td>\n", stats->limit_maxbytes); | |
ap_rprintf(r, "<tr><td>threads: </td><td>\t%d</td>\n", stats->threads); | |
ap_rputs("</table>\n", r); | |
} | |
return OK; | |
} | |
void mb_status_register(apr_pool_t *p) | |
{ | |
APR_OPTIONAL_HOOK(ap, status_hook, mb_status_hook, NULL, NULL, APR_HOOK_MIDDLE); | |
} | |
static int mb_pre_config(apr_pool_t *pconf, | |
apr_pool_t *plog, | |
apr_pool_t *ptemp) | |
{ | |
/* Register to handle mod_status status page generation */ | |
mb_status_register(pconf); | |
return OK; | |
} | |
#endif | |
static const command_rec mb_cmds[] = | |
{ | |
/* to be used only in Directory et al. */ | |
AP_INIT_FLAG("MirrorBrainEngine", mb_cmd_engine, NULL, | |
ACCESS_CONF, | |
"Set to On or Off to enable or disable redirecting"), | |
AP_INIT_FLAG("MirrorBrainDebug", mb_cmd_debug, NULL, | |
ACCESS_CONF, | |
"Set to On or Off to enable or disable debug logging to error log"), | |
/* to be used everywhere */ | |
AP_INIT_TAKE1("MirrorBrainMinSize", mb_cmd_minsize, NULL, | |
OR_OPTIONS, | |
"Minimum size, in bytes, that a file must be, in order to redirect " | |
"requests to a mirror. Smaller files will be delivered directly. " | |
"Default: 4096 bytes."), | |
AP_INIT_TAKE1("MirrorBrainExcludeMimeType", mb_cmd_excludemime, 0, | |
OR_OPTIONS, | |
"Mimetype to always exclude from redirecting (wildcards allowed)"), | |
AP_INIT_TAKE1("MirrorBrainExcludeUserAgent", mb_cmd_excludeagent, 0, | |
OR_OPTIONS, | |
"User-Agent to always exclude from redirecting (wildcards allowed)"), | |
AP_INIT_TAKE1("MirrorBrainExcludeNetwork", mb_cmd_excludenetwork, 0, | |
OR_OPTIONS, | |
"Network to always exclude from redirecting (simple string prefix)"), | |
AP_INIT_TAKE1("MirrorBrainExcludeIP", mb_cmd_excludeip, 0, | |
OR_OPTIONS, | |
"IP address to always exclude from redirecting"), | |
AP_INIT_TAKE1("MirrorBrainExcludeFileMask", mb_cmd_exclude_filemask, NULL, | |
ACCESS_CONF, | |
"Regexp which determines which files will be excluded form redirecting"), | |
/* obsolete, and to removed later */ | |
AP_INIT_FLAG("MirrorBrainHandleDirectoryIndexLocally", mb_cmd_handle_dirindex_locally, NULL, | |
OR_OPTIONS, | |
"Obsolete directive. You can remove it from your config."), | |
AP_INIT_FLAG("MirrorBrainHandleHEADRequestLocally", mb_cmd_handle_headrequest_locally, NULL, | |
OR_OPTIONS, | |
"Set to On to handle HEAD requests locally (instead of redirecting " | |
"them to a mirror). Default: Off."), | |
AP_INIT_TAKE1("MirrorBrainMetalinkTorrentAddMask", mb_cmd_metalink_torrentadd_mask, NULL, | |
ACCESS_CONF, | |
"Regexp which determines for which files to look for correspondant " | |
".torrent files, and add them into generated metalinks"), | |
AP_INIT_TAKE3("MirrorBrainFallback", mb_cmd_fallback, NULL, | |
ACCESS_CONF, | |
"region code, country code and base URL of a mirror that is used when no " | |
"mirror can be found in the database. These mirrors are assumed to have " | |
"*all* files. (Or they could be configured per directory.)"), | |
AP_INIT_TAKE1("MirrorBrainRedirectStampKey_EXPERIMENTAL", mb_cmd_redirect_stamp_key, NULL, | |
ACCESS_CONF, | |
"Causes MirrorBrain to append a signed timestamp to redirection URLs. The " | |
"argument is a string that defines the key to encrypt the timestamp with. " | |
"Can be configured on directory-level."), | |
AP_INIT_RAW_ARGS("MirrorBrainYumDir", mb_cmd_add_yumdir, NULL, | |
RSRC_CONF, /* RSRC_CONF|ACCESS_CONF, */ | |
"Specify query arguments mapping to a directory that must have a certain file. " | |
"Syntax: arg1=<regexp> arg2=<regexp> <basedir> <mandatory_file>. " | |
"Parts of basedir can be substituted with query arguments $1-$9. " | |
"Patterns are forced to be anchored to start and end for security reasons. " | |
"Example: MirrorBrainYumDir release=(5\\.5) repo=(os|updates) arch=i586 " | |
"$1/$2/i386 repodata/repomd.xml"), | |
/* to be used only in server context */ | |
AP_INIT_TAKE1("MirrorBrainDBDQuery", mb_cmd_dbd_query, NULL, | |
RSRC_CONF, | |
"The SQL query for fetching the mirrors from the backend database"), | |
AP_INIT_TAKE1("MirrorBrainDBDQueryHash", mb_cmd_dbd_query_hash, NULL, | |
RSRC_CONF, | |
"The SQL query for fetching verification hashes from the backend database"), | |
AP_INIT_TAKE1("MirrorBrainGeoIPFile", mb_cmd_geoip_filename, NULL, | |
RSRC_CONF, | |
"Obsolete directive - use mod_geoip, please."), | |
#ifdef WITH_MEMCACHE | |
AP_INIT_TAKE1("MirrorBrainInstance", mb_cmd_instance, NULL, | |
RSRC_CONF, | |
"Name of the MirrorBrain instance (used by Memcache)"), | |
AP_INIT_FLAG("MirrorBrainMemcached", mb_cmd_memcached_on, NULL, | |
RSRC_CONF, | |
"Set to On/Off to use memcached to give clients repeatedly the same mirror"), | |
AP_INIT_TAKE1("MirrorBrainMemcachedLifeTime", mb_cmd_memcached_lifetime, NULL, | |
RSRC_CONF, | |
"Lifetime (in seconds) associated with stored objects in " | |
"memcache daemon(s). Default is 600 s."), | |
#endif | |
AP_INIT_TAKE1("MirrorBrainMetalinkHashesPathPrefix", mb_cmd_metalink_hashes_prefix, NULL, | |
RSRC_CONF, | |
"Prefix this path when looking for prepared hashes to inject into metalinks. " | |
"This directive is obsolete (with 2.13.0) and is going to be removed later."), | |
AP_INIT_FLAG("MirrorBrainHashesSuppressFilenames", mb_cmd_hashes_suppress_filenames, NULL, | |
RSRC_CONF, | |
"Set to On to suppress the filename included when hashes are sent. " | |
"Normally, they come as \"99eaed37390ba0571f8d285829ff63fc foobar\" " | |
"as in the format well-known from the md5sum/sha1sum tools. Default: Off"), | |
AP_INIT_TAKE2("MirrorBrainMetalinkPublisher", mb_cmd_metalink_publisher, NULL, | |
RSRC_CONF, | |
"Name and URL for the metalinks publisher elements"), | |
AP_INIT_TAKE1("MirrorBrainTorrentTrackerURL", mb_cmd_tracker_url, NULL, | |
RSRC_CONF, | |
"Define the URL a BitTorrent Tracker to be included in Torrents and in Magnet " | |
"links. Directive can be repeated to specify multiple URLs."), | |
AP_INIT_TAKE2("MirrorBrainDHTNode", mb_cmd_dht_node, NULL, | |
RSRC_CONF, | |
"Define a DHT node to be included in Torrents " | |
"links. Directive can be repeated to specify multiple nodes, and takes " | |
"two arguments (hostname, port)."), | |
AP_INIT_FLAG("MirrorBrainMetalinkMagnetLinks", mb_cmd_metalink_magnet_links, NULL, | |
RSRC_CONF, | |
"If set to On, Magnet links will be included in Metalinks. Default is Off."), | |
AP_INIT_TAKE1("MirrorBrainMetalinkBrokenTestMirrors", mb_cmd_metalink_broken_test_mirrors, NULL, | |
RSRC_CONF, | |
"Filename with snippet to include at the top of a metalink's " | |
"<resources> section, for testing broken mirrors"), | |
AP_INIT_TAKE1("MirrorBrainMirrorlistStyleSheet", mb_cmd_mirrorlist_stylesheet, NULL, | |
RSRC_CONF, | |
"Sets a CSS stylesheet to add to mirror lists"), | |
AP_INIT_TAKE1("MirrorBrainMirrorlistHeader", mb_cmd_mirrorlist_header, NULL, | |
RSRC_CONF, | |
"Absolute path to header to be included at the top of the mirror " | |
"lists/details page, instead of the built-in header."), | |
AP_INIT_TAKE1("MirrorBrainMirrorlistFooter", mb_cmd_mirrorlist_footer, NULL, | |
RSRC_CONF, | |
"Absolute path to footer to be appended to the mirror " | |
"lists/details pages, instead of the built-in footer."), | |
{ NULL } | |
}; | |
/* Tell Apache what phases of the transaction we handle */ | |
static void mb_register_hooks(apr_pool_t *p) | |
{ | |
#ifdef WITH_MEMCACHE | |
ap_hook_pre_config (mb_pre_config, NULL, NULL, APR_HOOK_MIDDLE); | |
#endif | |
ap_hook_post_config (mb_post_config, NULL, NULL, APR_HOOK_MIDDLE); | |
ap_hook_handler (mb_handler, NULL, NULL, APR_HOOK_FIRST); | |
ap_hook_child_init (mb_child_init, NULL, NULL, APR_HOOK_MIDDLE ); | |
} | |
module AP_MODULE_DECLARE_DATA mirrorbrain_module = | |
{ | |
STANDARD20_MODULE_STUFF, | |
create_mb_dir_config, /* create per-directory config structures */ | |
merge_mb_dir_config, /* merge per-directory config structures */ | |
create_mb_server_config, /* create per-server config structures */ | |
merge_mb_server_config, /* merge per-server config structures */ | |
mb_cmds, /* command handlers */ | |
mb_register_hooks /* register hooks */ | |
}; | |
/* vim: set ts=4 sw=4 expandtab smarttab: */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment