├── .deps ├── archiveblock.load ├── modules.mk ├── archiveblock.conf ├── Makefile ├── README.md └── mod_archiveblock.c /.deps: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /archiveblock.load: -------------------------------------------------------------------------------- 1 | LoadModule archiveblock_module /usr/lib/apache2/modules/mod_archiveblock.so 2 | -------------------------------------------------------------------------------- /modules.mk: -------------------------------------------------------------------------------- 1 | mod_archiveblock.la: mod_archiveblock.slo 2 | $(SH_LINK) -rpath $(libexecdir) -module -avoid-version mod_archiveblock.lo 3 | DISTCLEAN_TARGETS = modules.mk 4 | shared = mod_archiveblock.la 5 | -------------------------------------------------------------------------------- /archiveblock.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | # This config file is provided to give an overview of the directives, 4 | # which are only allowed in the 'server config' context. 5 | # See https://github.com/iftechfoundation/ifarchive-archiveblock for 6 | # the full explanation. 7 | 8 | # ArchiveBlockMapPath /var/ifarchive/lib/blocktag.map 9 | # The path to the block map file. 10 | 11 | # ArchiveBlockRestrictDomain ukrestrict.ifarchive.org 12 | # The domain which handles tagged files. 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## 2 | ## Makefile -- Build procedure for sample archiveblock Apache module 3 | ## Autogenerated via ``apxs -n archiveblock -g''. 4 | ## 5 | 6 | builddir=. 7 | top_srcdir=/usr/share/apache2 8 | top_builddir=/usr/share/apache2 9 | include /usr/share/apache2/build/special.mk 10 | 11 | # the used tools 12 | APACHECTL=apachectl 13 | 14 | # additional defines, includes and libraries 15 | #DEFS=-Dmy_define=my_value 16 | #INCLUDES=-Imy/include/dir 17 | #LIBS=-Lmy/lib/dir -lmylib 18 | 19 | # the default target 20 | all: local-shared-build 21 | 22 | # install the shared object file into Apache 23 | install: install-modules-yes 24 | 25 | # cleanup 26 | clean: 27 | -rm -f mod_archiveblock.o mod_archiveblock.lo mod_archiveblock.slo mod_archiveblock.la 28 | 29 | # simple test 30 | test: reload 31 | lynx -mime_header http://localhost/archiveblock 32 | 33 | # install and activate shared object by reloading Apache to 34 | # force a reload of the shared object file 35 | reload: install restart 36 | 37 | # the general Apache start/restart/stop 38 | # procedures 39 | start: 40 | $(APACHECTL) start 41 | restart: 42 | $(APACHECTL) restart 43 | stop: 44 | $(APACHECTL) stop 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apache plugin for restricting files 2 | 3 | This tool is part of a system for restricting certain [IF Archive][ifarch] files from being served in the UK. It sucks that we have to do this, but we do. See discussion of the [UK Online Safety Act][ukosa]. 4 | 5 | [ifarch]: https://ifarchive.org/ 6 | [ukosa]: https://intfiction.org/t/uk-online-safety-act/75867 7 | 8 | This plugin does not handle the geolocation check itself. The entire process looks like this: 9 | 10 | - The `Index` files on the [Archive][ifarch] contain file tags like `safety: self-harm`. 11 | - The [ifmap][] script reads these and constructs a text file which maps filenames to tag lists. 12 | - This Apache plugin loads the map file. When a request comes in for a tagged file, the browser is redirected to the `ukrestrict.ifarchive.org` domain. 13 | - Cloudflare (the front-end for the public Archive service) does a geolocation check for any request that hits the `ukrestrict.ifarchive.org` domain. If the request comes from the UK, it is redirected to [https://ifarchive.org/misc/uk-block.html](https://ifarchive.org/misc/uk-block.html). 14 | 15 | [ifmap]: https://github.com/iftechfoundation/ifarchive-ifmap-py 16 | 17 | Why an Apache plugin? The redirect step is a bit too messy to handle with standard Apache tools like [`mod_alias`][mod_alias] or [`mod_rewrite`][mod_rewrite]. The tricky requirements: 18 | 19 | [mod_alias]: https://httpd.apache.org/docs/current/mod/mod_alias.html 20 | [mod_rewrite]: https://httpd.apache.org/docs/current/mod/mod_rewrite.html 21 | 22 | - The map file may be updated at any time. We must watch it and reload if the file timestamp changes. (This does not require an Apache restart.) 23 | - We must be able to tag entire directories, since the tagging process is being worked on incrementally. (Many directories have not even been looked at yet.) 24 | - All tagged files must get a `X-IFArchive-Safety` HTTP header listing the tags. 25 | - Redirects must have the `X-IFArchive-Safety` header, and also `Access-Control-Allow-Origin: *`. (So that client-side services like [`iplayif.com`][iplayif.com] can detect them.) 26 | 27 | [iplayif.com]: https://iplayif.com/ 28 | 29 | Happily, the [Apache C API][modapi] for modules is powerful enough to do what we need. 30 | 31 | [modapi]: https://httpd.apache.org/docs/2.4/developer/modguide.html 32 | 33 | ## Configuration 34 | 35 | The plugin has two configuration parameters. Both must be used at the server level; they cannot be customized per-directory. 36 | 37 | ``` 38 | ArchiveBlockMapPath /var/ifarchive/lib/blocktag.map 39 | # The path to the block map file. 40 | 41 | ArchiveBlockRestrictDomain ukrestrict.ifarchive.org 42 | # The domain which handles tagged files. 43 | ``` 44 | 45 | The default values are correct for the live Archive. 46 | 47 | ## The map file 48 | 49 | The map file syntax can charitably be described as "dank". It's meant to be parsed by relatively simple C code. 50 | 51 | Map lines have the form: 52 | 53 | ``` 54 | PATHNAME [TAB] FLAGS:TAGS 55 | ``` 56 | 57 | For example: 58 | 59 | ``` 60 | /if-archive/games/foo.z5 u:visual-gore, self-harm 61 | # Lines starting with a hash are comments 62 | ``` 63 | 64 | The separator is a tab character because filenames can contain spaces. If a filename contains a literal tab, I don't know what to tell you. 65 | 66 | The *PATHNAME* will always start with `/if-archive`. It can look like: 67 | 68 | ``` 69 | # An exact filename 70 | /if-archive/dir/filename.txt 71 | 72 | # Tags will apply to all files in this directory 73 | /if-archive/dir/* 74 | 75 | # Tags will apply to all files in this directory *and* its subdirs 76 | /if-archive/dir/** 77 | ``` 78 | 79 | Filename lines take precedence over directory lines; directory lines take precedence over subtree lines. This lets you build a tree of rules and exceptions in a sensible way. 80 | 81 | The second part of the line is zero or more *FLAG* characters, followed by a (mandatory) colon, followed by a comma-separated list of tags (taken from the `safety: tags` metadata line). No spaces around the colon, please. If this segment is just `:`, then there are no tags and no flags. (Again, useful for creating exceptions to a directory rule.) 82 | 83 | At present, the only meaningful *FLAG* character is `u`, meaning that the file must be restricted in the UK. If there is no `u` before the colon, the file has tags but none of the tags are UK-restricted. 84 | 85 | (It's the [ifmap][] script's job to decide which tags are UK-restricted. This plugin just looks for the `u:` prefix.) (In the future we may need to restrict files in other regions, such as the EU.) 86 | 87 | ## Building the plugin 88 | 89 | You will need the [`apxs`][apxs] tool. If you're on Linux, you may need to install the `apache2-dev` package. 90 | 91 | [apxs]: https://httpd.apache.org/docs/2.4/programs/apxs.html 92 | 93 | To build and install the plugin: 94 | 95 | ``` 96 | sudo apxs -i -c mod_archiveblock.c 97 | ``` 98 | 99 | Note that the `Makefile` in this repository is set up for the Archive's Linux environment. You will need a different config for MacOS, etc. Type `apxs -n archiveblock -g` in an empty directory to create a buildable plugin template. 100 | 101 | In your Apache config file, add lines like 102 | 103 | ``` 104 | LoadModule archiveblock_module lib/httpd/modules/mod_archiveblock.so 105 | 106 | 107 | 108 | 109 | SetHandler archiveblock 110 | 111 | 112 | 113 | ``` 114 | 115 | The `` directive means that the plugin only applies to archived files (the `/if-archive` directory), *not* to `/indexes` or `/misc`. We do not restrict file *listings*, only the files themselves. 116 | 117 | The `` directive ensures that the plugin applies only to existing files (and symlinks to files), not directories or missing files. This prevents the plugin from interfering with Apache's `mod_autoindex` directory generator. I don't know why it interferes, but it does. 118 | 119 | -------------------------------------------------------------------------------- /mod_archiveblock.c: -------------------------------------------------------------------------------- 1 | /* 2 | ** mod_archiveblock.c -- Apache archiveblock module 3 | ** (Originally autogenerated via "apxs -n archiveblock -g". It's been 4 | ** around the block a few times since then, though.) 5 | ** 6 | ** Docs at: https://httpd.apache.org/docs/2.4/developer/modguide.html 7 | ** https://httpd.apache.org/docs/2.4/programs/apxs.html 8 | ** https://apr.apache.org/docs/apr/1.4/modules.html 9 | ** https://github.com/apache/httpd/blob/trunk/modules/examples/ 10 | */ 11 | 12 | #include "apr_escape.h" 13 | #include "apr_strings.h" 14 | #include "ap_config.h" 15 | #include "httpd.h" 16 | #include "http_core.h" 17 | #include "http_config.h" 18 | #include "http_protocol.h" 19 | #include "http_log.h" 20 | 21 | static int archiveblock_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp); 22 | static int archiveblock_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s); 23 | static void archiveblock_child_init(apr_pool_t *p, server_rec *s); 24 | static int archiveblock_handler(request_rec *r); 25 | static apr_status_t find_tags_for_uri(request_rec *r, const char **tags, int *redirect); 26 | static const char *find_tags_for_uri_inner(request_rec *r, int *redirect); 27 | static apr_status_t check_config(const request_rec *r); 28 | static apr_status_t read_config(const request_rec *r); 29 | static void read_config_line(char *ln, const request_rec *r); 30 | 31 | /* Struct for module configuration parameters */ 32 | typedef struct { 33 | const char *mappath; /* path to the map file */ 34 | const char *rootdomain; /* canonical domain */ 35 | const char *restrictdomain; /* domain to redirect to for geoblock check */ 36 | } archiveblock_config; 37 | 38 | static archiveblock_config config; 39 | 40 | /* Gotta thread-lock while accessing or loading the map file. */ 41 | static apr_thread_mutex_t *tagmap_lock = NULL; 42 | static apr_time_t tagmap_mtime = 0; 43 | 44 | /* Entries for individual files */ 45 | static apr_table_t *tagmap_files = NULL; 46 | /* Entries that cover all the files in a directory */ 47 | static apr_table_t *tagmap_dirs = NULL; 48 | /* Entries that cover all the files in a directory *tree* */ 49 | static apr_table_t *tagmap_trees = NULL; 50 | 51 | /* Handler for setting the mappath. */ 52 | const char *archiveblock_set_map_path(cmd_parms *cmd, void *cfg, const char *arg) 53 | { 54 | config.mappath = arg; 55 | return NULL; 56 | } 57 | 58 | /* Handler for setting the rootdomain. */ 59 | const char *archiveblock_set_root_domain(cmd_parms *cmd, void *cfg, const char *arg) 60 | { 61 | config.rootdomain = arg; 62 | return NULL; 63 | } 64 | 65 | /* Handler for setting the restrictdomain. */ 66 | const char *archiveblock_set_restrict_domain(cmd_parms *cmd, void *cfg, const char *arg) 67 | { 68 | config.restrictdomain = arg; 69 | return NULL; 70 | } 71 | 72 | /* Apache module config directives. */ 73 | static const command_rec archiveblock_directives[] = { 74 | AP_INIT_TAKE1("ArchiveBlockMapPath", archiveblock_set_map_path, NULL, RSRC_CONF, "The path to the block map file."), 75 | AP_INIT_TAKE1("ArchiveBlockRootDomain", archiveblock_set_root_domain, NULL, RSRC_CONF, "The root domain."), 76 | AP_INIT_TAKE1("ArchiveBlockRestrictDomain", archiveblock_set_restrict_domain, NULL, RSRC_CONF, "The domain which handles tagged files."), 77 | { NULL } 78 | }; 79 | 80 | /* Set up the Apache module callbacks. 81 | */ 82 | static void archiveblock_register_hooks(apr_pool_t *p) 83 | { 84 | /* Set default config parameters. */ 85 | config.mappath = "/var/ifarchive/lib/blocktag.map"; 86 | config.rootdomain = "ifarchive.org"; 87 | config.restrictdomain = "ukrestrict.ifarchive.org"; 88 | 89 | ap_hook_pre_config(archiveblock_pre_config, NULL, NULL, APR_HOOK_MIDDLE); 90 | ap_hook_post_config(archiveblock_post_config, NULL, NULL, APR_HOOK_MIDDLE); 91 | ap_hook_child_init(archiveblock_child_init, NULL, NULL, APR_HOOK_MIDDLE); 92 | ap_hook_handler(archiveblock_handler, NULL, NULL, APR_HOOK_MIDDLE); 93 | } 94 | 95 | static int archiveblock_pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) 96 | { 97 | return OK; 98 | } 99 | 100 | static int archiveblock_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) 101 | { 102 | apr_status_t rc; 103 | 104 | if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) 105 | return OK; 106 | 107 | ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, s, "ArchiveBlock: ### post_config initing"); 108 | 109 | rc = apr_thread_mutex_create(&tagmap_lock, APR_THREAD_MUTEX_DEFAULT, pconf); 110 | if (rc != APR_SUCCESS) { 111 | ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, "ArchiveBlock: Unable to create thread lock"); 112 | } 113 | 114 | tagmap_files = apr_table_make(pconf, 64); 115 | tagmap_dirs = apr_table_make(pconf, 64); 116 | tagmap_trees = apr_table_make(pconf, 64); 117 | 118 | return OK; 119 | } 120 | 121 | /* This hook is called when a child process (not thread!) is spawned. 122 | */ 123 | static void archiveblock_child_init(apr_pool_t *p, server_rec *s) 124 | { 125 | } 126 | 127 | /* The request handler. 128 | We check the URI against our table of files (and directories) to block. 129 | If we find tags, add them as a X-IFArchive-Safety header, and then 130 | redirect to the ukrestrict domain (unless the request was already 131 | to that domain). 132 | */ 133 | static int archiveblock_handler(request_rec *r) 134 | { 135 | apr_status_t rc; 136 | 137 | if (strcmp(r->handler, "archiveblock")) { 138 | return DECLINED; 139 | } 140 | 141 | /* If the request is not to the root domain, send a header indicating 142 | that the root domain is canonical. 143 | https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls 144 | */ 145 | if (strcmp(r->hostname, config.rootdomain)) { 146 | const char *relheader = apr_psprintf(r->pool, "; rel=\"canonical\"", config.rootdomain, r->uri); 147 | apr_table_add(r->headers_out, "Link", relheader); 148 | } 149 | 150 | apr_finfo_t finfo; 151 | rc = apr_stat(&finfo, r->filename, APR_FINFO_TYPE, r->pool); 152 | if (rc != APR_SUCCESS || finfo.filetype != APR_REG) { 153 | /* File does not exist, or it's a directory. We don't restrict 154 | these; allow regular Apache handling to proceed. 155 | (If the file is a symlink to a restricted file, it should be 156 | listed in the map with the same tags as the destination.) 157 | (In practice we only apply this handler to regular files anyway, 158 | so as not to mess up Apache's autoindex module. So this test 159 | never fails.) */ 160 | return DECLINED; 161 | } 162 | 163 | const char *tags = NULL; 164 | int redirect = FALSE; 165 | rc = find_tags_for_uri(r, &tags, &redirect); 166 | if (rc != APR_SUCCESS) { 167 | /* Error already logged. */ 168 | return DECLINED; 169 | } 170 | if (!tags && !redirect) { 171 | /* No safety tags. Allow the regular Apache handling to proceed. */ 172 | return DECLINED; 173 | } 174 | 175 | apr_table_add(r->headers_out, "X-IFArchive-Safety", tags); 176 | if (!redirect) { 177 | /* Tags, but not ones that are restricted in the UK. We let it 178 | proceed, except that we've added the X-IFArchive-Safety header. 179 | (Note that if the request winds up as an error, that 180 | header will be lost. But that's fine.) */ 181 | return DECLINED; 182 | } 183 | 184 | if (!strcmp(r->hostname, config.restrictdomain)) { 185 | /* The request came to the ukrestrict domain. Again, let it 186 | proceed with the magic header. (UK geoblocking will happen 187 | at the Cloudflare level.) */ 188 | return DECLINED; 189 | } 190 | 191 | /* We contruct our own 302-redirect response. (If we let Apache 192 | do it, it would lose our headers.) */ 193 | 194 | /* Testing indicates we don't need to percent-encode r->uri. */ 195 | const char *newurl = apr_psprintf(r->pool, "https://%s%s", config.restrictdomain, r->uri); 196 | 197 | apr_table_add(r->headers_out, "Location", newurl); 198 | apr_table_add(r->headers_out, "Access-Control-Allow-Origin", "*"); 199 | 200 | r->status = 302; 201 | ap_set_content_type(r, "text/plain"); 202 | ap_rprintf(r, "File tagged: %s\n", tags); 203 | ap_rprintf(r, "Redirecting to: %s\n", newurl); 204 | 205 | return OK; 206 | } 207 | 208 | /* This locks the mutex for table access. 209 | */ 210 | static apr_status_t find_tags_for_uri(request_rec *r, const char **tags, int *redirect) 211 | { 212 | apr_status_t rc; 213 | 214 | *tags = NULL; 215 | *redirect = FALSE; 216 | 217 | rc = apr_thread_mutex_lock(tagmap_lock); 218 | if (rc != APR_SUCCESS) { 219 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to lock mutex"); 220 | return rc; 221 | } 222 | 223 | rc = check_config(r); 224 | /* Nothing to be done if there was an error. (Error was already 225 | logged.) Likely the tables are now empty, so it doesn't hurt 226 | to continue. */ 227 | 228 | const char *intags = find_tags_for_uri_inner(r, redirect); 229 | if (intags) { 230 | *tags = apr_pstrdup(r->pool, intags); 231 | } 232 | 233 | rc = apr_thread_mutex_unlock(tagmap_lock); 234 | if (rc != APR_SUCCESS) { 235 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to unlock mutex"); 236 | return rc; 237 | } 238 | 239 | return APR_SUCCESS; 240 | } 241 | 242 | /* See whether we have safety tags for the given request URI. 243 | If the tags include any that must be blocked in the UK, we also 244 | set *redirect. 245 | (This must be called under the mutex.) 246 | */ 247 | static const char *find_tags_for_uri_inner(request_rec *r, int *redirect) 248 | { 249 | const char *tags = NULL; 250 | const char *cx; 251 | 252 | tags = apr_table_get(tagmap_files, r->uri); 253 | 254 | if (!tags) { 255 | char *uri = apr_pstrdup(r->pool, r->uri); 256 | int len = strlen(uri); 257 | int backcount = 0; 258 | while (len >= 0) { 259 | while (len >= 1 && uri[len-1] != '/') 260 | len--; 261 | if (len <= 1) 262 | break; 263 | if (len) { 264 | uri[len-1] = '\0'; 265 | len--; 266 | if (backcount == 0) { 267 | tags = apr_table_get(tagmap_dirs, uri); 268 | if (tags) 269 | break; 270 | } 271 | tags = apr_table_get(tagmap_trees, uri); 272 | if (tags) 273 | break; 274 | backcount++; 275 | } 276 | if (tags) 277 | break; 278 | } 279 | } 280 | 281 | /* An empty tags string means no tags. (Empty after the colon, 282 | in practice.) */ 283 | if (!tags || *tags == '\0' || !strcmp(tags, ":")) { 284 | *redirect = FALSE; 285 | return NULL; 286 | } 287 | 288 | /* Advance past the "FLAG:" prefix. If we find a "u" flag, we're 289 | redirecting. */ 290 | *redirect = FALSE; 291 | for (cx=tags; *cx && *cx != ':'; cx++) { 292 | if (*cx == 'u') 293 | *redirect = TRUE; 294 | } 295 | if (*cx == ':') 296 | cx++; 297 | 298 | return cx; 299 | } 300 | 301 | static int dump_func(void *rec, const char *key, const char *tags) 302 | { 303 | const request_rec *r = rec; 304 | ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "...%s: \"%s\"", key, tags); 305 | return TRUE; 306 | } 307 | 308 | /* Log the contents of the the tagmaps. For debug only. */ 309 | static void dump_tagmap(const request_rec *r) 310 | { 311 | int count; 312 | 313 | count = apr_table_elts(tagmap_files)->nelts; 314 | ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "%d file entries:", count); 315 | apr_table_do(dump_func, (void *)r, tagmap_files, NULL); 316 | 317 | count = apr_table_elts(tagmap_dirs)->nelts; 318 | ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "%d dir entries:", count); 319 | apr_table_do(dump_func, (void *)r, tagmap_dirs, NULL); 320 | 321 | count = apr_table_elts(tagmap_trees)->nelts; 322 | ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "%d tree entries:", count); 323 | apr_table_do(dump_func, (void *)r, tagmap_trees, NULL); 324 | } 325 | 326 | /* Check if we need to reload the block file. If it's been updated since 327 | our last load, we reload it. 328 | (This must be called under the mutex.) 329 | */ 330 | static apr_status_t check_config(const request_rec *r) 331 | { 332 | apr_status_t rc; 333 | apr_finfo_t finfo; 334 | 335 | rc = apr_stat(&finfo, config.mappath, APR_FINFO_MTIME, r->pool); 336 | if (rc != APR_SUCCESS) { 337 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to stat file: %s", config.mappath); 338 | return rc; 339 | } 340 | 341 | rc = APR_SUCCESS; 342 | 343 | if (finfo.mtime > tagmap_mtime) { 344 | tagmap_mtime = finfo.mtime; 345 | rc = read_config(r); 346 | /* error already logged */ 347 | if (FALSE) 348 | dump_tagmap(r); 349 | } 350 | 351 | return rc; 352 | } 353 | 354 | #define BUFSIZE (256) 355 | 356 | /* Read the block file into a table. 357 | (This must be called under the mutex.) 358 | */ 359 | static apr_status_t read_config(const request_rec *r) 360 | { 361 | apr_status_t rc; 362 | apr_file_t *file = NULL; 363 | char buf[BUFSIZE]; 364 | char *lbuf = NULL; 365 | apr_size_t lbufsize, lbuflen; 366 | 367 | /* Note that the tagmap table is allocated from the server pool, but 368 | our temporary workspace for reading is allocated from the 369 | connection. */ 370 | 371 | lbufsize = 2*BUFSIZE; 372 | lbuflen = 0; 373 | lbuf = apr_palloc(r->pool, lbufsize+1); 374 | if (!lbuf) { 375 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to allocate %ld bytes for buffer", lbufsize); 376 | return APR_ENOMEM; 377 | } 378 | 379 | rc = apr_file_open(&file, config.mappath, APR_READ, APR_OS_DEFAULT, r->pool); 380 | if (rc != APR_SUCCESS) { 381 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to open config: %s", config.mappath); 382 | return rc; 383 | } 384 | 385 | apr_table_clear(tagmap_files); 386 | apr_table_clear(tagmap_dirs); 387 | apr_table_clear(tagmap_trees); 388 | 389 | while (TRUE) { 390 | apr_size_t len = BUFSIZE; 391 | rc = apr_file_read(file, buf, &len); 392 | if (rc == APR_EOF) { 393 | break; 394 | } 395 | if (rc != APR_SUCCESS) { 396 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to read config: %s", config.mappath); 397 | apr_file_close(file); 398 | return rc; 399 | } 400 | 401 | if (lbuflen + len > lbufsize) { 402 | lbufsize = (lbuflen + len) * 2; 403 | char *newlbuf = apr_palloc(r->pool, lbufsize+1); 404 | if (!newlbuf) { 405 | ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "ArchiveBlock: Unable to allocate %ld bytes for buffer", lbufsize); 406 | apr_file_close(file); 407 | return rc; 408 | } 409 | 410 | memcpy(newlbuf, lbuf, lbuflen); 411 | lbuf = newlbuf; 412 | // allow the old lbuf to be cleaned up with the pool 413 | } 414 | 415 | memcpy(lbuf+lbuflen, buf, len); 416 | lbuflen += len; 417 | 418 | while (TRUE) { 419 | int ix; 420 | for (ix=0; ix 0) { 438 | lbuf[lbuflen] = '\0'; 439 | read_config_line(lbuf, r); 440 | } 441 | 442 | apr_file_close(file); 443 | 444 | int filecount = apr_table_elts(tagmap_files)->nelts; 445 | int dircount = apr_table_elts(tagmap_dirs)->nelts; 446 | int treecount = apr_table_elts(tagmap_trees)->nelts; 447 | ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r, "ArchiveBlock: Read config: %d files, %d dirs, %d trees", filecount, dircount, treecount); 448 | 449 | return APR_SUCCESS; 450 | } 451 | 452 | /* Parse one line of the block file. Ignore blank lines and comments. 453 | This alters the line as it works. (Inserting nulls to break it up.) 454 | (This must be called under the mutex.) 455 | */ 456 | static void read_config_line(char *ln, const request_rec *r) 457 | { 458 | char *cx = ln; 459 | char *key, *tags; 460 | 461 | while (*cx && (*cx == ' ' || *cx == '\t')) 462 | cx++; 463 | 464 | if (!*cx) 465 | return; 466 | if (*cx == '#') 467 | return; 468 | 469 | key = cx; 470 | 471 | while (*cx && *cx != '\t') 472 | cx++; 473 | 474 | if (!*cx) { 475 | ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "unrecognized block line: %s", ln); 476 | return; 477 | } 478 | 479 | *cx = '\0'; 480 | cx++; 481 | 482 | while (*cx && (*cx == ' ' || *cx == '\t')) 483 | cx++; 484 | 485 | tags = cx; 486 | 487 | /* Right-strip whitespace. */ 488 | int taglen = strlen(tags); 489 | while (taglen > 0 && (tags[taglen-1] == ' ' || tags[taglen-1] == '\t')) { 490 | tags[taglen-1] = '\0'; 491 | taglen--; 492 | } 493 | 494 | /* Make sure the tags string contains a colon. */ 495 | if (!strchr(tags, ':')) { 496 | ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "block line lacks colon: %s", tags); 497 | return; 498 | } 499 | 500 | /* If the key ends with slash-star, it's a dir entry. If it ends with 501 | slash-star-star, it's a subtree entry. If it ends with a bare slash 502 | somebody screwed up. 503 | In all these cases, we trim the slash-star marker. */ 504 | int keylen = strlen(key); 505 | 506 | if (keylen > 1 && key[keylen-1] == '/') { 507 | ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, "block line ends with slash: %s", key); 508 | } 509 | else if (keylen > 2 && key[keylen-2] == '/' && key[keylen-1] == '*') { 510 | key[keylen-2] = '\0'; 511 | apr_table_set(tagmap_dirs, key, tags); 512 | } 513 | else if (keylen > 3 && key[keylen-3] == '/' && key[keylen-2] == '*' && key[keylen-1] == '*') { 514 | key[keylen-3] = '\0'; 515 | apr_table_set(tagmap_trees, key, tags); 516 | } 517 | else { 518 | apr_table_set(tagmap_files, key, tags); 519 | } 520 | } 521 | 522 | /* Apache module configuration. */ 523 | module AP_MODULE_DECLARE_DATA archiveblock_module = { 524 | STANDARD20_MODULE_STUFF, 525 | NULL, /* create per-dir config structures */ 526 | NULL, /* merge per-dir config structures */ 527 | NULL, /* create per-server config structures */ 528 | NULL, /* merge per-server config structures */ 529 | archiveblock_directives, /* table of config file commands */ 530 | archiveblock_register_hooks /* register hooks */ 531 | }; 532 | 533 | --------------------------------------------------------------------------------