Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
/**
18
 * Quarantine file
19
 *
20
 * @package    core_antivirus
21
 * @author     Nathan Nguyen <nathannguyen@catalyst-au.net>
22
 * @copyright  Catalyst IT
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace core\antivirus;
27
 
28
defined('MOODLE_INTERNAL') || die();
29
require_once($CFG->libdir.'/filelib.php');
30
 
31
/**
32
 * Quarantine file
33
 *
34
 * @package    core_antivirus
35
 * @author     Nathan Nguyen <nathannguyen@catalyst-au.net>
36
 * @copyright  Catalyst IT
37
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38
 */
39
class quarantine {
40
 
41
    /** Default quarantine folder */
42
    const DEFAULT_QUARANTINE_FOLDER = 'antivirus_quarantine';
43
 
44
    /** Zip infected file  */
45
    const FILE_ZIP_INFECTED = '_infected_file.zip';
46
 
47
    /** Zip all infected file */
48
    const FILE_ZIP_ALL_INFECTED = '_all_infected_files.zip';
49
 
50
    /** Incident details file */
51
    const FILE_HTML_DETAILS = '_details.html';
52
 
53
    /** Incident details file */
54
    const DEFAULT_QUARANTINE_TIME = DAYSECS * 28;
55
 
56
    /** Date format in filename */
57
    const FILE_NAME_DATE_FORMAT = '%Y%m%d%H%M%S';
58
 
59
    /**
60
     * Move the infected file to the quarantine folder.
61
     *
62
     * @param string $file infected file.
63
     * @param string $filename infected file name.
64
     * @param string $incidentdetails incident details.
65
     * @param string $notice notice details.
66
     * @return string|null the name of the newly created quarantined file.
67
     * @throws \dml_exception
68
     */
69
    public static function quarantine_file(string $file, string $filename, string $incidentdetails, string $notice): ?string {
70
        if (!self::is_quarantine_enabled()) {
71
            return null;
72
        }
73
        // Generate file names.
74
        $date = userdate(time(), self::FILE_NAME_DATE_FORMAT) . "_" . rand();
75
        $zipfilepath = self::get_quarantine_folder() . $date . self::FILE_ZIP_INFECTED;
76
        $detailsfilename = $date . self::FILE_HTML_DETAILS;
77
 
78
        // Create Zip file.
79
        $ziparchive = new \zip_archive();
80
        if ($ziparchive->open($zipfilepath, \file_archive::CREATE)) {
81
            $ziparchive->add_file_from_string($detailsfilename, format_text($incidentdetails, FORMAT_MOODLE));
82
            $ziparchive->add_file_from_pathname($filename, $file);
83
            $ziparchive->close();
84
        }
85
        $zipfile = basename($zipfilepath);
86
        self::create_infected_file_record($filename, $zipfile, $notice);
87
        return $zipfile;
88
    }
89
 
90
    /**
91
     * Move the infected file to the quarantine folder.
92
     *
93
     * @param string $data data which is infected.
94
     * @param string $filename infected file name.
95
     * @param string $incidentdetails incident details.
96
     * @param string $notice notice details.
97
     * @return string|null the name of the newly created quarantined file.
98
     * @throws \dml_exception
99
     */
100
    public static function quarantine_data(string $data, string $filename, string $incidentdetails, string $notice): ?string {
101
        if (!self::is_quarantine_enabled()) {
102
            return null;
103
        }
104
        // Generate file names.
105
        $date = userdate(time(), self::FILE_NAME_DATE_FORMAT) . "_" . rand();
106
        $zipfilepath = self::get_quarantine_folder() . $date . self::FILE_ZIP_INFECTED;
107
        $detailsfilename = $date . self::FILE_HTML_DETAILS;
108
 
109
        // Create Zip file.
110
        $ziparchive = new \zip_archive();
111
        if ($ziparchive->open($zipfilepath, \file_archive::CREATE)) {
112
            $ziparchive->add_file_from_string($detailsfilename, format_text($incidentdetails, FORMAT_MOODLE));
113
            $ziparchive->add_file_from_string($filename, $data);
114
            $ziparchive->close();
115
        }
116
        $zipfile = basename($zipfilepath);
117
        self::create_infected_file_record($filename, $zipfile, $notice);
118
        return $zipfile;
119
    }
120
 
121
    /**
122
     * Check if the virus quarantine is allowed
123
     *
124
     * @return bool
125
     * @throws \dml_exception
126
     */
127
    public static function is_quarantine_enabled(): bool {
128
        return !empty(get_config("antivirus", "enablequarantine"));
129
    }
130
 
131
    /**
132
     * Get quarantine folder
133
     *
134
     * @return string path of quarantine folder
135
     */
136
    private static function get_quarantine_folder(): string {
137
        global $CFG;
138
        $quarantinefolder = $CFG->dataroot . DIRECTORY_SEPARATOR . self::DEFAULT_QUARANTINE_FOLDER;
139
        if (!file_exists($quarantinefolder)) {
140
            make_upload_directory(self::DEFAULT_QUARANTINE_FOLDER);
141
        }
142
        return $quarantinefolder . DIRECTORY_SEPARATOR;
143
    }
144
 
145
    /**
146
     * Checks whether a file exists inside the antivirus quarantine folder.
147
     *
148
     * @param string $filename the filename to check.
149
     * @return boolean whether file exists.
150
     */
151
    public static function quarantined_file_exists(string $filename): bool {
152
        $folder = self::get_quarantine_folder();
153
        return file_exists($folder . $filename);
154
    }
155
 
156
    /**
157
     * Download quarantined file.
158
     *
159
     * @param int $fileid the id of file to be downloaded.
160
     */
161
    public static function download_quarantined_file(int $fileid) {
162
        global $DB;
163
 
164
        // Get the filename to be downloaded.
165
        $filename = $DB->get_field('infected_files', 'quarantinedfile', ['id' => $fileid], IGNORE_MISSING);
166
        // If file record isnt found, user might be doing something naughty in params, or a stale request.
167
        if (empty($filename)) {
168
            return;
169
        }
170
 
171
        $file = self::get_quarantine_folder() . $filename;
172
        send_file($file, $filename);
173
    }
174
 
175
    /**
176
     * Delete quarantined file.
177
     *
178
     * @param int $fileid id of file to be deleted.
179
     */
180
    public static function delete_quarantined_file(int $fileid) {
181
        global $DB;
182
 
183
        // Get the filename to be deleted.
184
        $filename = $DB->get_field('infected_files', 'quarantinedfile', ['id' => $fileid], IGNORE_MISSING);
185
        // If file record isnt found, user might be doing something naughty in params, or a stale request.
186
        if (empty($filename)) {
187
            return;
188
        }
189
 
190
        // Delete the file from the folder.
191
        $file = self::get_quarantine_folder() . $filename;
192
        if (file_exists($file)) {
193
            unlink($file);
194
        }
195
 
196
        // Now we are finished with the record, delete the quarantine information.
197
        self::delete_infected_file_record($fileid);
198
    }
199
 
200
    /**
201
     * Download all quarantined files.
202
     *
203
     * @return void
204
     */
205
    public static function download_all_quarantined_files() {
206
        $files = new \DirectoryIterator(self::get_quarantine_folder());
207
        // Add all infected files to a zip file.
208
        $date = userdate(time(), self::FILE_NAME_DATE_FORMAT);
209
        $zipfilename = $date . self::FILE_ZIP_ALL_INFECTED;
210
        $zipfilepath = self::get_quarantine_folder() . DIRECTORY_SEPARATOR . $zipfilename;
211
        $tempfilestocleanup = [];
212
 
213
        $ziparchive = new \zip_archive();
214
        if ($ziparchive->open($zipfilepath, \file_archive::CREATE)) {
215
            foreach ($files as $file) {
216
                if (!$file->isDot()) {
217
                    // Only send the actual files.
218
                    $filename = $file->getFilename();
219
                    $filepath = $file->getPathname();
220
                    $ziparchive->add_file_from_pathname($filename, $filepath);
221
                }
222
            }
223
            $ziparchive->close();
224
        }
225
 
226
        // Clean up temp files.
227
        foreach ($tempfilestocleanup as $tempfile) {
228
            if (file_exists($tempfile)) {
229
                unlink($tempfile);
230
            }
231
        }
232
 
233
        send_temp_file($zipfilepath, $zipfilename);
234
    }
235
 
236
    /**
237
     * Return array of quarantined files.
238
     *
239
     * @return array list of quarantined files.
240
     */
241
    public static function get_quarantined_files(): array {
242
        $files = new \DirectoryIterator(self::get_quarantine_folder());
243
        $filestosort = [];
244
 
245
        // Grab all files that match the naming structure.
246
        foreach ($files as $file) {
247
            $filename = $file->getFilename();
248
            if (!$file->isDot() && strpos($filename, self::FILE_ZIP_INFECTED) !== false) {
249
                $filestosort[$filename] = $file->getPathname();
250
            }
251
        }
252
 
253
        krsort($filestosort, SORT_NATURAL);
254
        return $filestosort;
255
    }
256
 
257
    /**
258
     * Clean up quarantine folder
259
     *
260
     * @param int $timetocleanup time to clean up
261
     */
262
    public static function clean_up_quarantine_folder(int $timetocleanup) {
263
        $files = new \DirectoryIterator(self::get_quarantine_folder());
264
        // Clean up the folder.
265
        foreach ($files as $file) {
266
            $filename = $file->getFilename();
267
 
268
            // Only delete files that match the correct name structure.
269
            if (!$file->isDot() && strpos($filename, self::FILE_ZIP_INFECTED) !== false) {
270
                $modifiedtime = $file->getMTime();
271
 
272
                if ($modifiedtime <= $timetocleanup) {
273
                    unlink($file->getPathname());
274
                }
275
            }
276
        }
277
 
278
        // Lastly cleanup the infected files table as well.
279
        self::clean_up_infected_records($timetocleanup);
280
    }
281
 
282
    /**
283
     * This function removes any stale records from the infected files table.
284
     *
285
     * @param int $timetocleanup the time to cleanup from
286
     * @return void
287
     */
288
    private static function clean_up_infected_records(int $timetocleanup) {
289
        global $DB;
290
 
291
        $select = "timecreated <= ?";
292
        $DB->delete_records_select('infected_files', $select, [$timetocleanup]);
293
    }
294
 
295
    /**
296
     * Create an infected file record
297
     *
298
     * @param string $filename original file name
299
     * @param string $zipfile quarantined file name
300
     * @param string $reason failure reason
301
     * @throws \dml_exception
302
     */
303
    private static function create_infected_file_record(string $filename, string $zipfile, string $reason) {
304
        global $DB, $USER;
305
 
306
        $record = new \stdClass();
307
        $record->filename = $filename;
308
        $record->quarantinedfile = $zipfile;
309
        $record->userid = $USER->id;
310
        $record->reason = $reason;
311
        $record->timecreated = time();
312
 
313
        $DB->insert_record('infected_files', $record);
314
    }
315
 
316
    /**
317
     * Delete the database record for an infected file.
318
     *
319
     * @param int $fileid quarantined file id
320
     * @throws \dml_exception
321
     */
322
    private static function delete_infected_file_record(int $fileid) {
323
        global $DB;
324
        $DB->delete_records('infected_files', ['id' => $fileid]);
325
    }
326
}