1441 |
ariadna |
1 |
<?php
|
|
|
2 |
// This file is part of Moodle - http://moodle.org/
|
|
|
3 |
//
|
|
|
4 |
// Moodle is free software: you can redistribute it and/or modify
|
|
|
5 |
// it under the terms of the GNU General Public License as published by
|
|
|
6 |
// the Free Software Foundation, either version 3 of the License, or
|
|
|
7 |
// (at your option) any later version.
|
|
|
8 |
//
|
|
|
9 |
// Moodle is distributed in the hope that it will be useful,
|
|
|
10 |
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
11 |
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
12 |
// GNU General Public License for more details.
|
|
|
13 |
//
|
|
|
14 |
// You should have received a copy of the GNU General Public License
|
|
|
15 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
16 |
|
|
|
17 |
namespace core\task;
|
|
|
18 |
|
|
|
19 |
use core\http_client;
|
|
|
20 |
use moodle_exception;
|
|
|
21 |
use PharData;
|
|
|
22 |
|
|
|
23 |
/**
|
|
|
24 |
* Simple task to update the GeoIP database file.
|
|
|
25 |
*
|
|
|
26 |
* @package core
|
|
|
27 |
* @author Trisha Milan <trishamilan@catalyst-au.net>
|
|
|
28 |
* @copyright Monash University 2024
|
|
|
29 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
|
30 |
*/
|
|
|
31 |
class update_geoip2file_task extends scheduled_task {
|
|
|
32 |
|
|
|
33 |
/**
|
|
|
34 |
* Get a descriptive name for this task (shown to admins).
|
|
|
35 |
*
|
|
|
36 |
* @return string
|
|
|
37 |
*/
|
|
|
38 |
public function get_name(): string {
|
|
|
39 |
return get_string('taskupdategeoip2file', 'admin');
|
|
|
40 |
}
|
|
|
41 |
|
|
|
42 |
/**
|
|
|
43 |
* Execute the task to update the GeoIP2 database file.
|
|
|
44 |
*
|
|
|
45 |
* @throws \GuzzleHttp\Exception\GuzzleException
|
|
|
46 |
* @throws moodle_exception
|
|
|
47 |
*/
|
|
|
48 |
public function execute(): void {
|
|
|
49 |
global $CFG;
|
|
|
50 |
|
|
|
51 |
if (!$CFG->geoipmaxmindaccid || !$CFG->geoipmaxmindlicensekey) {
|
|
|
52 |
mtrace("MaxMind account information is incomplete. Please configure the account ID and license key.");
|
|
|
53 |
return;
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
// Setup base directory path and permissions.
|
|
|
57 |
$geoip2file = $CFG->geoip2file;
|
|
|
58 |
$geoipdirectory = dirname($geoip2file);
|
|
|
59 |
if (!check_dir_exists($geoipdirectory) && !mkdir($geoipdirectory, $CFG->directorypermissions, true)) {
|
|
|
60 |
throw new moodle_exception("Cannot create output directory $geoipdirectory");
|
|
|
61 |
}
|
|
|
62 |
|
|
|
63 |
$geoippermalink = 'https://download.maxmind.com/geoip/databases/' . $CFG->geoipdbedition . '/download';
|
|
|
64 |
|
|
|
65 |
$client = new http_client(['auth' => [$CFG->geoipmaxmindaccid, $CFG->geoipmaxmindlicensekey]]);
|
|
|
66 |
$response = $client->head($geoippermalink, ['query' => ['suffix' => 'tar.gz']]);
|
|
|
67 |
$headers = $response->getHeaders();
|
|
|
68 |
$lastmodified = strtotime($headers['Last-Modified'][0]);
|
|
|
69 |
if (!$this->is_update_needed($geoip2file, $lastmodified)) {
|
|
|
70 |
mtrace("No update needed. The GeoIP database is up to date.");
|
|
|
71 |
return;
|
|
|
72 |
}
|
|
|
73 |
|
|
|
74 |
// Define path for downloading the GeoIP2 archive.
|
|
|
75 |
$archivefilename = 'GeoIP-City.tar.gz';
|
|
|
76 |
$tempdirectory = make_request_directory(true);
|
|
|
77 |
$geoipdownloadpath = $tempdirectory . '/' . $archivefilename;
|
|
|
78 |
|
|
|
79 |
mtrace("Downloading $CFG->geoipdbedition database from MaxMind......");
|
|
|
80 |
$response = $client->request('GET', $geoippermalink, [
|
|
|
81 |
'query' => ['suffix' => 'tar.gz'],
|
|
|
82 |
'sink' => $geoipdownloadpath,
|
|
|
83 |
]);
|
|
|
84 |
if ($response->getStatusCode() != 200) {
|
|
|
85 |
throw new moodle_exception("Error downloading file.");
|
|
|
86 |
}
|
|
|
87 |
|
|
|
88 |
mtrace("File downloaded successfully to $geoipdownloadpath");
|
|
|
89 |
mtrace("Verifying checksum......");
|
|
|
90 |
|
|
|
91 |
// Get the latest checksum from MaxMind.
|
|
|
92 |
$checksumcontent = $client->get($geoippermalink, ['query' => ['suffix' => 'tar.gz.sha256']])->getBody()->getContents();
|
|
|
93 |
list($checksum) = explode(' ', $checksumcontent);
|
|
|
94 |
if (!$this->verify_checksum($checksum, $geoipdownloadpath)) {
|
|
|
95 |
throw new moodle_exception("Checksum verification failed.");
|
|
|
96 |
}
|
|
|
97 |
|
|
|
98 |
mtrace("Checksum verified successfully.");
|
|
|
99 |
if ($this->update_geoip2file($geoipdownloadpath, $tempdirectory, $geoip2file)) {
|
|
|
100 |
// Store the last seen timestamp.
|
|
|
101 |
set_config('geoip_last_seen_timestamp', $lastmodified);
|
|
|
102 |
mtrace("GeoIP database update successful!");
|
|
|
103 |
} else {
|
|
|
104 |
throw new moodle_exception("GeoIP database update failed.");
|
|
|
105 |
}
|
|
|
106 |
}
|
|
|
107 |
|
|
|
108 |
/**
|
|
|
109 |
* Determines if an update is needed for the GeoIP2 file based on the last modified date.
|
|
|
110 |
*
|
|
|
111 |
* @param string $geoip2file The path to the GeoIP2 file that needs to be checked for updates.
|
|
|
112 |
* @param string $lastmodified The last modified date to be compared against the stored last seen timestamp.
|
|
|
113 |
* @return bool
|
|
|
114 |
*/
|
|
|
115 |
private function is_update_needed(string $geoip2file, string $lastmodified): bool {
|
|
|
116 |
return !file_exists($geoip2file) || $lastmodified !== get_config('core', 'geoip_last_seen_timestamp');
|
|
|
117 |
}
|
|
|
118 |
|
|
|
119 |
/**
|
|
|
120 |
* Verify the checksum of the downloaded file against an expected checksum.
|
|
|
121 |
*
|
|
|
122 |
* @param string $expectedchecksum The checksum expected for the file.
|
|
|
123 |
* @param string $geoipdownloadpath The path where the downloaded geoip archive is located.
|
|
|
124 |
* @return bool Returns true if the checksums match, returns false otherwise.
|
|
|
125 |
*/
|
|
|
126 |
private function verify_checksum(string $expectedchecksum, string $geoipdownloadpath): bool {
|
|
|
127 |
$actualchecksum = hash_file('sha256', $geoipdownloadpath);
|
|
|
128 |
return $expectedchecksum === $actualchecksum;
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
/**
|
|
|
132 |
* Extract the archive and update the GeoIP2 database file.
|
|
|
133 |
*
|
|
|
134 |
* @param string $archivepath The path to the archive file that needs to be extracted.
|
|
|
135 |
* @param string $targetdirectory Directory where the archive contents will be extracted.
|
|
|
136 |
* @param string $geoip2file The path to move the extracted GeoIP2 file.
|
|
|
137 |
* @return bool Returns true if the file was successfully extracted and moved to the specified location,
|
|
|
138 |
* false if any part of the process fails.
|
|
|
139 |
*/
|
|
|
140 |
private function update_geoip2file(string $archivepath, string $targetdirectory, string $geoip2file): bool {
|
|
|
141 |
$archive = new PharData($archivepath);
|
|
|
142 |
$archivename = $archive->getFilename();
|
|
|
143 |
|
|
|
144 |
mtrace("Extracting file......");
|
|
|
145 |
$archive->extractTo($targetdirectory);
|
|
|
146 |
$sourcefolder = $targetdirectory . '/' . $archivename;
|
|
|
147 |
|
|
|
148 |
// Find the mmdb file.
|
|
|
149 |
$mmdbfiles = glob($sourcefolder . '/*.mmdb');
|
|
|
150 |
if (count($mmdbfiles) > 1) {
|
|
|
151 |
throw new moodle_exception("Multiple .mmdb files found in the extracted folder.");
|
|
|
152 |
} else if (count($mmdbfiles) === 0) {
|
|
|
153 |
throw new moodle_exception("GeoIP file does not exist.");
|
|
|
154 |
}
|
|
|
155 |
|
|
|
156 |
// Backup existing GeoIP file before attempting to update.
|
|
|
157 |
$geoip2filename = basename($geoip2file);
|
|
|
158 |
$backuppath = $targetdirectory . '/' . 'backup_' . $geoip2filename;
|
|
|
159 |
if (file_exists($geoip2file)) {
|
|
|
160 |
if (!rename($geoip2file, $backuppath)) {
|
|
|
161 |
mtrace("Failed to create a backup of the existing GeoIP database.");
|
|
|
162 |
}
|
|
|
163 |
mtrace("Temporary backup of existing GeoIP file has been created.");
|
|
|
164 |
}
|
|
|
165 |
|
|
|
166 |
mtrace("Moving {$mmdbfiles[0]} into $geoip2file");
|
|
|
167 |
if (!copy($mmdbfiles[0], $geoip2file)) {
|
|
|
168 |
mtrace("Failed to update $geoip2filename.");
|
|
|
169 |
// Attempt to restore the original file from the backup.
|
|
|
170 |
if (file_exists($backuppath)) {
|
|
|
171 |
mtrace("Attempting to restore from backup.");
|
|
|
172 |
if (!copy($backuppath, $geoip2file)) {
|
|
|
173 |
throw new moodle_exception("Failed to restore the GeoIP database from backup.");
|
|
|
174 |
} else {
|
|
|
175 |
mtrace("The GeoIP database has been restored from the backup successfully.");
|
|
|
176 |
}
|
|
|
177 |
}
|
|
|
178 |
return false;
|
|
|
179 |
}
|
|
|
180 |
mtrace("$geoip2filename updated successfully.");
|
|
|
181 |
return true;
|
|
|
182 |
}
|
|
|
183 |
}
|