xref: /plugin/hideip/action.php (revision 047cf1274130bf2cc2a8d4fd101f0662c03da350)
1e6a02230Stracker-user<?php
2*047cf127Stracker-userif (!defined('DOKU_INC')) die();
3*047cf127Stracker-user
4e6a02230Stracker-user/**
5e6a02230Stracker-user * Hide IP — action component.
6e6a02230Stracker-user *
7*047cf127Stracker-user * Hooks INIT_LANG_LOAD and rewrites the server-side IP variables so that every
8*047cf127Stracker-user * later piece of code — clientIP(), $INPUT->server->str('REMOTE_ADDR'),
9*047cf127Stracker-user * changelog writers, page metadata, the mailer's X-Originating-IP header,
10*047cf127Stracker-user * AJAX info, etc. — sees a constant placeholder instead of the real address.
11e6a02230Stracker-user *
12e6a02230Stracker-user * Why a constant ('0.0.0.0') rather than a session-hashed pseudo-IPv6:
13e6a02230Stracker-user *  - This wiki has no anonymous edits; the username field already
14e6a02230Stracker-user *    differentiates editors. A pseudo-IP adds no investigative value.
15e6a02230Stracker-user *  - Less surface area for re-identification attacks. The original anonip
16e6a02230Stracker-user *    leaked the first IPv4 octet via auth_browseruid(); even the fork's
17e6a02230Stracker-user *    session-hash variant still depends on session-stability assumptions.
18e6a02230Stracker-user *  - Page locking is not affected: inc/common.php::lock() writes only the
19e6a02230Stracker-user *    username when REMOTE_USER is set (which it always will be here), and
20e6a02230Stracker-user *    unlock() has session_id() as a fallback even when it isn't.
21*047cf127Stracker-user *
22*047cf127Stracker-user * Note: DOKU_URL / DOKU_BASE are constants defined at init.php:103-104,
23*047cf127Stracker-user * before INIT_LANG_LOAD fires. If your wiki relies on trustedproxy-based
24*047cf127Stracker-user * SSL detection at runtime (is_ssl() called after init), set $conf['baseurl']
25*047cf127Stracker-user * explicitly so DokuWiki does not consult REMOTE_ADDR for URL construction.
26e6a02230Stracker-user */
27e6a02230Stracker-user
28e6a02230Stracker-useruse dokuwiki\Extension\ActionPlugin;
29e6a02230Stracker-useruse dokuwiki\Extension\EventHandler;
30e6a02230Stracker-useruse dokuwiki\Extension\Event;
31e6a02230Stracker-user
32e6a02230Stracker-userclass action_plugin_hideip extends ActionPlugin
33e6a02230Stracker-user{
34e6a02230Stracker-user    /** The placeholder all anonymised reads will return. */
35*047cf127Stracker-user    public const PLACEHOLDER_IP = '0.0.0.0';
36e6a02230Stracker-user
37e6a02230Stracker-user    /**
38*047cf127Stracker-user     * Register event hooks.
39*047cf127Stracker-user     *
40e6a02230Stracker-user     * @param EventHandler $controller
41*047cf127Stracker-user     * @return void
42e6a02230Stracker-user     */
43e6a02230Stracker-user    public function register(EventHandler $controller)
44e6a02230Stracker-user    {
45*047cf127Stracker-user        // INIT_LANG_LOAD fires at init.php:233, after the plugin controller and
46*047cf127Stracker-user        // $INPUT are ready, but before auth_setup() and any page-handling code
47*047cf127Stracker-user        // reads the client IP. Clobbering here covers every downstream consumer.
48e6a02230Stracker-user        $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'handleAnonymise');
49e6a02230Stracker-user    }
50e6a02230Stracker-user
51e6a02230Stracker-user    /**
52*047cf127Stracker-user     * Overwrite REMOTE_ADDR and all forwarding headers with the placeholder.
53*047cf127Stracker-user     *
54e6a02230Stracker-user     * @param Event $event  unused, dispatch signature only
55e6a02230Stracker-user     * @param mixed $param  unused
56*047cf127Stracker-user     * @return void
57e6a02230Stracker-user     */
58e6a02230Stracker-user    public function handleAnonymise(Event $event, $param)
59e6a02230Stracker-user    {
60e6a02230Stracker-user        // Direct $_SERVER write: DokuWiki's dokuwiki\Input\Server class uses
61e6a02230Stracker-user        // $access =& $_SERVER (see inc/Input/Server.php), so $INPUT->server
62e6a02230Stracker-user        // reads pick up the new value without anything else to do.
63e6a02230Stracker-user        $_SERVER['REMOTE_ADDR'] = self::PLACEHOLDER_IP;
64e6a02230Stracker-user
65e6a02230Stracker-user        // Also clear every common forwarding header. inc/Ip.php::clientIps()
66e6a02230Stracker-user        // walks HTTP_X_FORWARDED_FOR and appends every IP it finds; if we
67e6a02230Stracker-user        // left these set, the real client address would still leak into
68e6a02230Stracker-user        // clientIP(false) (multi-IP mode) and into header logs/UIs that
69e6a02230Stracker-user        // consume those raw values.
70e6a02230Stracker-user        foreach (
71e6a02230Stracker-user            [
72e6a02230Stracker-user                'HTTP_X_FORWARDED_FOR',
73e6a02230Stracker-user                'HTTP_X_REAL_IP',
74e6a02230Stracker-user                'HTTP_CLIENT_IP',
75e6a02230Stracker-user                'HTTP_FORWARDED',
76e6a02230Stracker-user                'HTTP_CF_CONNECTING_IP',   // Cloudflare
77e6a02230Stracker-user                'HTTP_TRUE_CLIENT_IP',     // Akamai / Cloudflare Enterprise
78e6a02230Stracker-user            ] as $header
79e6a02230Stracker-user        ) {
80e6a02230Stracker-user            if (isset($_SERVER[$header])) unset($_SERVER[$header]);
81e6a02230Stracker-user        }
82e6a02230Stracker-user    }
83e6a02230Stracker-user}
84