Rbl   F
last analyzed

Complexity

Total Complexity 184

Size/Duplication

Total Lines 1011
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 184
eloc 479
dl 0
loc 1011
rs 2
c 0
b 0
f 0

26 Methods

Rating   Name   Duplication   Size   Complexity  
B getColorByIps() 0 25 7
A findIp() 0 13 3
A getSenders() 0 15 4
C addReport() 0 55 16
B verifyDmarcSpf() 0 26 10
B sendReport() 0 37 6
A parse() 0 3 2
A getRequestById() 0 10 2
B getDmarcRecord() 0 24 7
A getInstance() 0 6 1
B getIpByName() 0 18 7
B verifySpf() 0 38 10
F getSender() 0 50 21
A getDomain() 0 10 4
A verifyDmarcDkim() 0 9 6
A sync() 0 22 5
A getColorByList() 0 14 4
C getReceived() 0 32 10
A parseHeaderParams() 0 10 3
A getPublicList() 0 23 4
B findIpByName() 0 24 9
C verifyDmarc() 0 27 12
B verifyDkim() 0 22 10
B verifySender() 0 32 10
B updateList() 0 39 8
A getHeaderEmail() 0 6 3

How to fix   Complexity   

Complex Class

Complex classes like Rbl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Rbl, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Mail RBL file.
4
 *
5
 * @package App
6
 *
7
 * @copyright YetiForce S.A.
8
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
9
 * @author    Mariusz Krzaczkowski <[email protected]>
10
 * @author    Radosław Skrzypczak <[email protected]>
11
 */
12
13
namespace App\Mail;
14
15
/**
16
 * Mail RBL class.
17
 */
18
class Rbl extends \App\Base
19
{
20
	/**
21
	 * Request statuses.
22
	 *
23
	 * @var array
24
	 */
25
	public const REQUEST_STATUS = [
26
		0 => ['label' => 'LBL_FOR_VERIFICATION', 'icon' => 'fas fa-question'],
27
		1 => ['label' => 'LBL_ACCEPTED', 'icon' => 'fas fa-check text-success'],
28
		2 => ['label' => 'LBL_REJECTED', 'icon' => 'fas fa-times text-danger'],
29
		3 => ['label' => 'PLL_CANCELLED', 'icon' => 'fas fa-minus'],
30
		4 => ['label' => 'LBL_REPORTED', 'icon' => 'fas fa-paper-plane text-primary'],
31
	];
32
	/**
33
	 * List statuses.
34
	 *
35
	 * @var array
36
	 */
37
	public const LIST_STATUS = [
38
		0 => ['label' => 'LBL_ACTIVE', 'icon' => 'fas fa-check text-success'],
39
		1 => ['label' => 'LBL_CANCELED', 'icon' => 'fas fa-times text-danger'],
40
	];
41
	/**
42
	 * List statuses.
43
	 *
44
	 * @var array
45
	 */
46
	public const LIST_TYPES = [
47
		0 => ['label' => 'LBL_BLACK_LIST', 'icon' => 'fas fa-ban text-danger', 'alertColor' => '#ff555233', 'listColor' => '#ff555233'],
48
		1 => ['label' => 'LBL_WHITE_LIST', 'icon' => 'far fa-check-circle text-success', 'alertColor' => '#E1FFE3', 'listColor' => '#fff'],
49
		2 => ['label' => 'LBL_PUBLIC_BLACK_LIST', 'icon' => 'fas fa-ban text-danger', 'alertColor' => '#eaeaea', 'listColor' => '#ff555233'],
50
		3 => ['label' => 'LBL_PUBLIC_WHITE_LIST', 'icon' => 'far fa-check-circle text-success', 'alertColor' => '#E1FFE3', 'listColor' => '#fff'],
51
	];
52
	/**
53
	 * List categories.
54
	 *
55
	 * @var array
56
	 */
57
	public const LIST_CATEGORIES = [
58
		'Black' => [
59
			'[SPAM] Single unwanted message' => 'LBL_SPAM_SINGLE_UNWANTED_MESSAGE',
60
			'[SPAM] Mass unwanted message' => 'LBL_SPAM_MASS_UNWANTED_MESSAGE',
61
			'[SPAM] Sending an unsolicited message repeatedly' => 'LBL_SPAM_SENDING_UNSOLICITED_MESSAGE_REPEATEDLY',
62
			'[Fraud] Money scam' => 'LBL_FRAUD_MONEY_SCAM',
63
			'[Fraud] Phishing' => 'LBL_FRAUD_PHISHING',
64
			'[Fraud] An attempt to persuade people to buy a product or service' => 'LBL_FRAUD_ATTEMPT_TO_PERSUADE_PEOPLE_TO_BUY',
65
			'[Security] An attempt to impersonate another person' => 'LBL_SECURITY_ATTEMPT_TO_IMPERSONATE_ANOTHER_PERSON',
66
			'[Security] An attempt to persuade the recipient to open a resource from outside the organization' => 'LBL_SECURITY_ATTEMPT_TO_PERSUADE_FROM_ORGANIZATION',
67
			'[Security] An attempt to persuade the recipient to open a resource inside the organization' => 'LBL_SECURITY_ATTEMPT_TO_PERSUADE_INSIDE_ORGANIZATION',
68
			'[Security] Infrastructure and application scanning' => 'LBL_SECURITY_INFRASTRUCTURE_AND_APPLICATION_SCANNING',
69
			'[Security] Attack on infrastructure or application' => 'LBL_SECURITY_ATTACK_INFRASTRUCTURE_OR_APPLICATION',
70
			'[Security] Overloading infrastructure or application' => 'LBL_SECURITY_OVERLOADING_INFRASTRUCTURE_OR_APPLICATION',
71
			'[Other] The message contains inappropriate words' => 'LBL_OTHER_MESSAGE_CONTAINS_INAPPROPRIATE_WORDS',
72
			'[Other] The message contains inappropriate materials' => 'LBL_OTHER_MESSAGE_CONTAINS_INAPPROPRIATE_MATERIALS',
73
			'[Other] Malicious message' => 'LBL_OTHER_MALICIOUS_MESSAGE',
74
		],
75
		'White' => [
76
			'[Whitelist] Trusted sender' => 'LBL_TRUSTED_SENDER',
77
		],
78
	];
79
	/**
80
	 * RLB black list type.
81
	 *
82
	 * @var int
83
	 */
84
	public const LIST_TYPE_BLACK_LIST = 0;
85
	/**
86
	 * RLB white list type.
87
	 *
88
	 * @var int
89
	 */
90
	public const LIST_TYPE_WHITE_LIST = 1;
91
	/**
92
	 * RLB public black list type.
93
	 *
94
	 * @var int
95
	 */
96
	public const LIST_TYPE_PUBLIC_BLACK_LIST = 2;
97
	/**
98
	 * RLB public white list type.
99
	 *
100
	 * @var int
101
	 */
102
	public const LIST_TYPE_PUBLIC_WHITE_LIST = 3;
103
104
	/**
105
	 * SPF statuses.
106
	 *
107
	 * @var array
108
	 */
109
	public const SPF = [
110
		1 => ['label' => 'LBL_NONE', 'desc' => 'LBL_SPF_NONE_DESC', 'class' => 'badge-secondary', 'icon' => 'fas fa-question'],
111
		2 => ['label' => 'LBL_CORRECT', 'desc' => 'LBL_SPF_PASS_DESC', 'class' => 'badge-success', 'icon' => 'fas fa-check'],
112
		3 => ['label' => 'LBL_INCORRECT', 'desc' => 'LBL_SPF_FAIL_DESC', 'class' => 'badge-danger', 'icon' => 'fas fa-times'],
113
	];
114
	/**
115
	 * Check result: None, Neutral, TempError, PermError.
116
	 *
117
	 * @var int
118
	 */
119
	public const SPF_NONE = 1;
120
	/**
121
	 * Check result: Pass (the SPF record stated that the IP address is authorized).
122
	 *
123
	 * @var int
124
	 */
125
	public const SPF_PASS = 2;
126
	/**
127
	 * Check result: Fail, SoftFail.
128
	 *
129
	 * @var int
130
	 */
131
	public const SPF_FAIL = 3;
132
	/**
133
	 * DKIM statuses.
134
	 *
135
	 * @var array
136
	 */
137
	public const DKIM = [
138
		0 => ['label' => 'LBL_NONE', 'desc' => 'LBL_DKIM_NONE_DESC', 'class' => 'badge-secondary', 'icon' => 'fas fa-question'],
139
		1 => ['label' => 'LBL_CORRECT', 'desc' => 'LBL_DKIM_PASS_DESC', 'class' => 'badge-success', 'icon' => 'fas fa-check'],
140
		2 => ['label' => 'LBL_INCORRECT', 'desc' => 'LBL_DKIM_FAIL_DESC', 'class' => 'badge-danger', 'icon' => 'fas fa-times'],
141
	];
142
	/**
143
	 * DKIM header not found.
144
	 *
145
	 * @var int
146
	 */
147
	public const DKIM_NONE = 0;
148
	/**
149
	 * DKIM header verified correctly.
150
	 *
151
	 * @var int
152
	 */
153
	public const DKIM_PASS = 1;
154
	/**
155
	 * DKIM header verified incorrectly.
156
	 *
157
	 * @var int
158
	 */
159
	public const DKIM_FAIL = 2;
160
	/**
161
	 * DMARC statuses.
162
	 *
163
	 * @var array
164
	 */
165
	public const DMARC = [
166
		0 => ['label' => 'LBL_NONE', 'desc' => 'LBL_DMARC_NONE_DESC', 'class' => 'badge-secondary', 'icon' => 'fas fa-question'],
167
		1 => ['label' => 'LBL_CORRECT', 'desc' => 'LBL_DMARC_PASS_DESC', 'class' => 'badge-success', 'icon' => 'fas fa-check'],
168
		2 => ['label' => 'LBL_INCORRECT', 'desc' => 'LBL_DMARC_FAIL_DESC', 'class' => 'badge-danger', 'icon' => 'fas fa-times'],
169
	];
170
	/**
171
	 * DMARC header not found.
172
	 *
173
	 * @var int
174
	 */
175
	public const DMARC_NONE = 0;
176
	/**
177
	 * DMARC header verified correctly.
178
	 *
179
	 * @var int
180
	 */
181
	public const DMARC_PASS = 1;
182
	/**
183
	 * DMARC header verified incorrectly.
184
	 *
185
	 * @var int
186
	 */
187
	public const DMARC_FAIL = 2;
188
	/**
189
	 * Message mail mime parser instance.
190
	 *
191
	 * @var \ZBateson\MailMimeParser\Message
192
	 */
193
	public $mailMimeParser;
194
	/**
195
	 * Sender cache.
196
	 *
197
	 * @var array
198
	 */
199
	private $senderCache;
200
	/**
201
	 * SPF cache.
202
	 *
203
	 * @var array
204
	 */
205
	private $spfCache;
206
	/**
207
	 * DKIM cache.
208
	 *
209
	 * @var array
210
	 */
211
	private $dkimCache;
212
213
	/**
214
	 * Function to get the instance of advanced permission record model.
215
	 *
216
	 * @param int $id
217
	 *
218
	 * @return \self instance, if exists
0 ignored issues
show
Bug introduced by
The type self was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
219
	 */
220
	public static function getRequestById(int $id)
221
	{
222
		$query = (new \App\Db\Query())->from('s_#__mail_rbl_request')->where(['id' => $id]);
223
		$row = $query->createCommand(\App\Db::getInstance('admin'))->queryOne();
224
		$instance = false;
225
		if (false !== $row) {
226
			$instance = new self();
227
			$instance->setData($row);
228
		}
229
		return $instance;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $instance returns the type App\Mail\Rbl|false which is incompatible with the documented return type self.
Loading history...
230
	}
231
232
	/**
233
	 * Create an empty object and initialize it with data.
234
	 *
235
	 * @param array $data
236
	 *
237
	 * @return self
238
	 */
239
	public static function getInstance(array $data): self
240
	{
241
		$instance = new self();
242
		$instance->setData($data);
243
		$instance->mailMimeParser = \ZBateson\MailMimeParser\Message::from($instance->get('header'), false);
244
		return $instance;
245
	}
246
247
	/**
248
	 * Parsing the email body or headers.
249
	 *
250
	 * @return void
251
	 */
252
	public function parse(): void
253
	{
254
		$this->mailMimeParser = \ZBateson\MailMimeParser\Message::from($this->has('rawBody') ? $this->get('rawBody') : $this->get('header') . "\r\n\r\n", false);
255
	}
256
257
	/**
258
	 * Get email from header by name.
259
	 *
260
	 * @param string $name
261
	 *
262
	 * @return string
263
	 */
264
	protected function getHeaderEmail(string $name): string
265
	{
266
		if ($header = $this->mailMimeParser->getHeader($name)) {
267
			return $header->getEmail() ?: '';
0 ignored issues
show
Bug introduced by
The method getEmail() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\AddressHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

267
			return $header->/** @scrutinizer ignore-call */ getEmail() ?: '';
Loading history...
268
		}
269
		return '';
270
	}
271
272
	/**
273
	 * Get received header.
274
	 *
275
	 * @return array
276
	 */
277
	public function getReceived(): array
278
	{
279
		$rows = [];
280
		foreach ($this->mailMimeParser->getAllHeadersByName('Received') as $key => $received) {
281
			$row = ['key' => $key, 'fromIP' => $received->getFromAddress() ?? ''];
0 ignored issues
show
Bug introduced by
The method getFromAddress() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

281
			$row = ['key' => $key, 'fromIP' => $received->/** @scrutinizer ignore-call */ getFromAddress() ?? ''];
Loading history...
282
			if ($received->getFromName()) {
0 ignored issues
show
Bug introduced by
The method getFromName() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

282
			if ($received->/** @scrutinizer ignore-call */ getFromName()) {
Loading history...
283
				$row['fromName'] = $received->getFromName();
284
			}
285
			if ($received->getFromHostname()) {
0 ignored issues
show
Bug introduced by
The method getFromHostname() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

285
			if ($received->/** @scrutinizer ignore-call */ getFromHostname()) {
Loading history...
286
				$row['fromName'] .= PHP_EOL . '(' . $received->getFromHostname() . ')';
287
			}
288
			if ($received->getByName()) {
0 ignored issues
show
Bug introduced by
The method getByName() does not exist on ZBateson\MailMimeParser\Header\IHeader. Did you maybe mean getName()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

288
			if ($received->/** @scrutinizer ignore-call */ getByName()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
289
				$row['byName'] = $received->getByName();
290
			}
291
			if ($received->getByHostname()) {
0 ignored issues
show
Bug introduced by
The method getByHostname() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

291
			if ($received->/** @scrutinizer ignore-call */ getByHostname()) {
Loading history...
292
				$row['byName'] .= PHP_EOL . '(' . $received->getByHostname() . ')';
293
			}
294
			if ($received->getByAddress()) {
0 ignored issues
show
Bug introduced by
The method getByAddress() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

294
			if ($received->/** @scrutinizer ignore-call */ getByAddress()) {
Loading history...
295
				$row['byIP'] = $received->getByAddress();
296
			}
297
			if ($received->getValueFor('with')) {
0 ignored issues
show
Bug introduced by
The method getValueFor() does not exist on ZBateson\MailMimeParser\Header\IHeader. Did you maybe mean getValue()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

297
			if ($received->/** @scrutinizer ignore-call */ getValueFor('with')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
298
				$row['extraWith'] = $received->getValueFor('with');
299
			}
300
			if ($received->getComments()) {
0 ignored issues
show
Bug introduced by
The method getComments() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

300
			if ($received->/** @scrutinizer ignore-call */ getComments()) {
Loading history...
301
				$row['extraComments'] = preg_replace('/\s+/', ' ', trim(implode(' | ', $received->getComments())));
302
			}
303
			if ($received->getDateTime()) {
0 ignored issues
show
Bug introduced by
The method getDateTime() does not exist on ZBateson\MailMimeParser\Header\IHeader. It seems like you code against a sub-type of ZBateson\MailMimeParser\Header\IHeader such as ZBateson\MailMimeParser\Header\DateHeader or ZBateson\MailMimeParser\Header\ReceivedHeader. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

303
			if ($received->/** @scrutinizer ignore-call */ getDateTime()) {
Loading history...
304
				$row['dateTime'] = $received->getDateTime()->format('Y-m-d H:i:s');
305
			}
306
			$rows[] = $row;
307
		}
308
		return array_reverse($rows);
309
	}
310
311
	/**
312
	 * Get sender details.
313
	 *
314
	 * @return array
315
	 */
316
	public function getSender(): array
317
	{
318
		if (isset($this->senderCache)) {
319
			return $this->senderCache;
320
		}
321
		$first = $row = [];
322
		foreach ($this->mailMimeParser->getAllHeadersByName('Received') as $key => $received) {
323
			if ($received->getFromName() && $received->getByName() && $received->getFromName() !== $received->getByName()) {
324
				$fromDomain = $this->getDomain($received->getFromName());
325
				$byDomain = $this->getDomain($received->getByName());
326
				if (!($fromIp = $received->getFromAddress())) {
327
					if (!($fromIp = $this->findIpByName($received, 'from'))) {
328
						$fromIp = $this->getIpByName($received->getFromName(), $received->getFromHostname());
329
					}
330
				}
331
				if (!($byIp = $received->getByAddress())) {
332
					if (!($byIp = $this->findIpByName($received, 'by'))) {
333
						$byIp = $this->getIpByName($received->getByName(), $received->getByHostname());
334
					}
335
				}
336
				if ($fromIp && $byIp && $fromIp !== $byIp && ((!$fromDomain && !$byDomain) || $fromDomain !== $byDomain)) {
337
					$row['ip'] = $fromIp;
338
					$row['key'] = $key;
339
					$row['from'] = $received->getFromName();
340
					$row['by'] = $received->getByName();
341
					break;
342
				}
343
				if (empty($first)) {
344
					$first = [
345
						'ip' => $fromIp,
346
						'key' => $key,
347
						'from' => $received->getFromName(),
348
						'by' => $received->getByName(),
349
					];
350
				}
351
			}
352
			if (!empty($byIp)) {
353
				$row['ip'] = $byIp;
354
			} elseif ($received->getByAddress()) {
355
				$row['ip'] = $received->getByAddress();
356
			}
357
		}
358
		if (!isset($row['key'])) {
359
			if ($first) {
360
				$row = $first;
361
			} else {
362
				$row['key'] = false;
363
			}
364
		}
365
		return $this->senderCache = $row;
366
	}
367
368
	/**
369
	 * Get domain from URL.
370
	 *
371
	 * @param string $url
372
	 *
373
	 * @return string
374
	 */
375
	public function getDomain(string $url): string
376
	{
377
		if (']' === substr($url, -1) || '[' === substr($url, 0, 1)) {
378
			$url = rtrim(ltrim($url, '['), ']');
379
		}
380
		if (filter_var($url, FILTER_VALIDATE_IP)) {
381
			return $url;
382
		}
383
		$domains = explode('.', $url, substr_count($url, '.'));
384
		return end($domains);
385
	}
386
387
	/**
388
	 * Find mail ip address.
389
	 *
390
	 * @param \ZBateson\MailMimeParser\Header\ReceivedHeader $received
391
	 * @param string                                         $type
392
	 *
393
	 * @return string
394
	 */
395
	public function findIpByName(\ZBateson\MailMimeParser\Header\ReceivedHeader $received, string $type): string
396
	{
397
		$value = $received->getValueFor($type);
398
		$pattern = '~\[(IPv[64])?([a-f\d\.\:]+)\]~i';
399
		if (preg_match($pattern, $value, $matches)) {
400
			if (!empty($matches[2])) {
401
				return $matches[2];
402
			}
403
		}
404
		$lastReceivedPart = null;
405
		foreach ($received->getParts() as $part) {
406
			if ($part instanceof \ZBateson\MailMimeParser\Header\Part\ReceivedPart) {
407
				$lastReceivedPart = $part->getName();
408
			} elseif ($part instanceof \ZBateson\MailMimeParser\Header\Part\CommentPart) {
409
				if ($lastReceivedPart === $type) {
410
					if (preg_match($pattern, $part->getComment(), $matches)) {
411
						if (!empty($matches[2])) {
412
							return $matches[2];
413
						}
414
					}
415
				}
416
			}
417
		}
418
		return '';
419
	}
420
421
	/**
422
	 * Get mail ip address by hostname or ehloName.
423
	 *
424
	 * @param string  $fromName
425
	 * @param ?string $hostName
426
	 *
427
	 * @return string
428
	 */
429
	public function getIpByName(string $fromName, ?string $hostName = null): string
430
	{
431
		if (']' === substr($fromName, -1) || '[' === substr($fromName, 0, 1)) {
432
			$fromName = rtrim(ltrim($fromName, '['), ']');
433
		}
434
		if (filter_var($fromName, FILTER_VALIDATE_IP)) {
435
			return $fromName;
436
		}
437
		if (0 === stripos($hostName, 'helo=')) {
438
			$hostName = substr($hostName, 5);
439
			if ($ip = \App\RequestUtil::getIpByName($hostName)) {
440
				return $ip;
441
			}
442
		}
443
		if ($ip = \App\RequestUtil::getIpByName($fromName)) {
444
			return $ip;
445
		}
446
		return '';
447
	}
448
449
	/**
450
	 * Get senders.
451
	 *
452
	 * @return string
453
	 */
454
	public function getSenders(): array
455
	{
456
		$senders = [
457
			'From' => $this->mailMimeParser->getHeaderValue('From'),
458
		];
459
		if ($returnPath = $this->mailMimeParser->getHeaderValue('Return-Path')) {
460
			$senders['Return-Path'] = $returnPath;
461
		}
462
		if ($sender = $this->mailMimeParser->getHeaderValue('Sender')) {
463
			$senders['Sender'] = $sender;
464
		}
465
		if ($sender = $this->mailMimeParser->getHeaderValue('X-Sender')) {
466
			$senders['X-Sender'] = $sender;
467
		}
468
		return $senders;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $senders returns the type array<string,null|string> which is incompatible with the documented return type string.
Loading history...
469
	}
470
471
	/**
472
	 * Verify sender email address.
473
	 *
474
	 * @return array
475
	 */
476
	public function verifySender(): array
477
	{
478
		$from = $this->getHeaderEmail('from');
479
		if (!$from) {
480
			return ['status' => true, 'info' => ''];
481
		}
482
		$status = true;
483
		$info = '';
484
		if ($returnPath = $this->getHeaderEmail('Return-Path')) {
485
			if (0 === stripos($returnPath, 'SRS') && ($separator = substr($returnPath, 4, 1)) !== '') {
486
				$returnPathSrs = '';
487
				$parts = explode($separator, $returnPath);
488
				$mail = explode('@', array_pop($parts));
489
				if (isset($mail[1])) {
490
					$last = array_pop($parts);
491
					$returnPathSrs = "{$mail[0]}@{$last}";
492
				}
493
				$status = $from === $returnPathSrs;
494
			} else {
495
				$status = $from === $returnPath;
496
			}
497
			if (!$status) {
498
				$info .= "From: {$from} <> Return-Path: {$returnPath}" . PHP_EOL;
499
			}
500
		}
501
		if ($status && ($sender = $this->getHeaderEmail('Sender'))) {
502
			$status = $from === $sender;
503
			if (!$status) {
504
				$info .= "From: {$from} <> Sender: {$sender}" . PHP_EOL;
505
			}
506
		}
507
		return ['status' => $status, 'info' => $info];
508
	}
509
510
	/**
511
	 * Verify SPF (Sender Policy Framework) for Authorizing Use of Domains in Email.
512
	 *
513
	 * @see https://tools.ietf.org/html/rfc7208
514
	 *
515
	 * @return array
516
	 */
517
	public function verifySpf(): array
518
	{
519
		if (isset($this->spfCache)) {
520
			return $this->spfCache;
521
		}
522
		$returnPath = $this->getHeaderEmail('Return-Path');
523
		$sender = $this->getSender();
524
		$return = ['status' => self::SPF_NONE];
525
		if ($email = $this->getHeaderEmail('from')) {
526
			$return['domain'] = explode('@', $email)[1];
527
		}
528
		if (isset($sender['ip'])) {
529
			$cacheKey = "{$sender['ip']}-{$returnPath}";
530
			if (\App\Cache::has('RBL:verifySpf', $cacheKey)) {
531
				$status = \App\Cache::get('RBL:verifySpf', $cacheKey);
532
			} else {
533
				$status = null;
534
				try {
535
					$environment = new \SPFLib\Check\Environment($sender['ip'], '', $returnPath);
536
					switch ((new \SPFLib\Checker())->check($environment, \SPFLib\Checker::FLAG_CHECK_MAILFROADDRESS)->getCode()) {
537
						case \SPFLib\Check\Result::CODE_PASS:
538
							$status = self::SPF_PASS;
539
							break;
540
						case \SPFLib\Check\Result::CODE_FAIL:
541
						case \SPFLib\Check\Result::CODE_SOFTFAIL:
542
							$status = self::SPF_FAIL;
543
							break;
544
					}
545
				} catch (\Throwable $e) {
546
					\App\Log::warning($e->getMessage(), __NAMESPACE__);
547
				}
548
				\App\Cache::save('RBL:verifySpf', $cacheKey, $status, \App\Cache::LONG);
549
			}
550
			if (isset($status)) {
551
				$return['status'] = $status;
552
			}
553
		}
554
		return $this->spfCache = $return + self::SPF[$return['status']];
555
	}
556
557
	/**
558
	 * Verify DKIM (DomainKeys Identified Mail).
559
	 *
560
	 * @see https://tools.ietf.org/html/rfc4871
561
	 *
562
	 * @return array
563
	 */
564
	public function verifyDkim(): array
565
	{
566
		if (isset($this->dkimCache)) {
567
			return $this->dkimCache;
568
		}
569
		$content = $this->has('rawBody') ? $this->get('rawBody') : $this->get('header') . "\r\n\r\n";
570
		$status = self::DKIM_NONE;
571
		$logs = '';
572
		if ($this->mailMimeParser->getHeader('DKIM-Signature')) {
573
			try {
574
				$validate = (new \PHPMailer\DKIMValidator\Validator($content))->validate();
575
				$status = ((1 === \count($validate)) && (1 === \count($validate[0])) && ('SUCCESS' === $validate[0][0]['status'])) ? self::DKIM_PASS : self::DKIM_FAIL;
576
				foreach ($validate as $rows) {
577
					foreach ($rows as $row) {
578
						$logs .= "[{$row['status']}] {$row['reason']}\n";
579
					}
580
				}
581
			} catch (\Throwable $e) {
582
				\App\Log::warning($e->getMessage(), __NAMESPACE__);
583
			}
584
		}
585
		return $this->dkimCache = \App\Utils::merge(['status' => $status, 'logs' => trim($logs)], self::DKIM[$status]);
586
	}
587
588
	/**
589
	 * Verify DMARC (Domain-based Message Authentication, Reporting and Conformance).
590
	 *
591
	 * @return array
592
	 */
593
	public function verifyDmarc(): array
594
	{
595
		$fromDomain = explode('@', $this->getHeaderEmail('from'))[1] ?? '';
596
		$status = self::DMARC_NONE;
597
		if (empty($fromDomain) || !($dmarcRecord = $this->getDmarcRecord($fromDomain))) {
598
			return ['status' => $status, 'logs' => \App\Language::translateArgs('LBL_NO_DMARC_DNS', 'Settings:MailRbl', $fromDomain)] + self::DMARC[$status];
599
		}
600
		$logs = '';
601
		if ($this->mailMimeParser->getHeader('DKIM-Signature')) {
602
			$verifyDmarcDkim = $this->verifyDmarcDkim($fromDomain, $dmarcRecord['adkim']);
603
			$status = $verifyDmarcDkim['status'] && self::DKIM_PASS === $this->verifyDkim()['status'] ? self::DMARC_PASS : self::DMARC_FAIL;
604
			if (!$status) {
605
				$logs = $verifyDmarcDkim['log'];
606
			}
607
		}
608
		if (self::DMARC_FAIL !== $status) {
609
			$verifyDmarcSpf = $this->verifyDmarcSpf($fromDomain, $dmarcRecord['aspf']);
610
			if (null === $verifyDmarcSpf['status']) {
611
				$logs = \App\Language::translate('LBL_NO_DMARC_FROM', 'Settings:MailRbl');
612
			} else {
613
				$status = $verifyDmarcSpf['status'] && self::SPF_PASS === $this->verifySpf()['status'] ? self::DMARC_PASS : self::DMARC_FAIL;
614
				if (!$verifyDmarcSpf['status']) {
615
					$logs = $verifyDmarcSpf['log'];
616
				}
617
			}
618
		}
619
		return ['status' => $status, 'logs' => trim($logs)] + self::DMARC[$status];
620
	}
621
622
	/**
623
	 * Get DMARC TXT record.
624
	 *
625
	 * @param string $domain
626
	 *
627
	 * @return array
628
	 */
629
	public function getDmarcRecord(string $domain): array
630
	{
631
		if (\App\Cache::has('RBL:getDmarcRecord', $domain)) {
632
			return \App\Cache::get('RBL:getDmarcRecord', $domain);
633
		}
634
		$dns = dns_get_record('_dmarc.' . $domain, DNS_TXT);
635
		if (!$dns) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dns of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
636
			return [];
637
		}
638
		$dkimParams = [];
639
		foreach ($dns as $key) {
640
			if (preg_match('/^v=dmarc(.*)/i', $key['txt'])) {
641
				$dkimParams = self::parseHeaderParams($key['txt']);
642
				break;
643
			}
644
		}
645
		if (empty($dkimParams['adkim'])) {
646
			$dkimParams['adkim'] = 'r';
647
		}
648
		if (empty($dkimParams['aspf'])) {
649
			$dkimParams['aspf'] = 'r';
650
		}
651
		\App\Cache::save('RBL:getDmarcRecord', $domain, $dkimParams, \App\Cache::LONG);
652
		return $dkimParams;
653
	}
654
655
	/**
656
	 * Verify DMARC ADKIM Tag.
657
	 *
658
	 * @param string $fromDomain
659
	 * @param string $adkim
660
	 *
661
	 * @return array
662
	 */
663
	private function verifyDmarcDkim(string $fromDomain, string $adkim): array
664
	{
665
		$dkimDomain = self::parseHeaderParams($this->mailMimeParser->getHeaderValue('DKIM-Signature'))['d'];
666
		$status = $fromDomain === $dkimDomain;
667
		if ($status || 's' === $adkim) {
668
			return ['status' => $status, 'log' => ($status ? '' : "From: $fromDomain | DKIM domain: $dkimDomain")];
669
		}
670
		$status = (mb_strlen($fromDomain) - mb_strlen('.' . $dkimDomain)) === strpos($fromDomain, '.' . $dkimDomain) || (mb_strlen($dkimDomain) - mb_strlen('.' . $fromDomain)) === strpos($dkimDomain, '.' . $fromDomain);
671
		return ['status' => $status, 'log' => ($status ? '' : "From: $fromDomain | DKIM domain: $dkimDomain")];
672
	}
673
674
	/**
675
	 * Verify DMARC ASPF Tag.
676
	 *
677
	 * @param string $fromDomain
678
	 * @param string $aspf
679
	 *
680
	 * @return array
681
	 */
682
	private function verifyDmarcSpf(string $fromDomain, string $aspf): array
683
	{
684
		$mailFrom = '';
685
		if ($returnPath = $this->getHeaderEmail('Return-Path')) {
686
			$mailFrom = explode('@', $returnPath)[1];
687
		}
688
		if (!$mailFrom && !($mailFrom = $this->getSender()['from'] ?? '')) {
689
			return ['status' => null];
690
		}
691
		$status = $fromDomain === $mailFrom;
692
		if ($status || 's' === $aspf) {
693
			return ['status' => $status, 'log' => ($status ? '' : "RFC5321.MailFrom domain: $mailFrom | RFC5322.From domain: $fromDomain")];
694
		}
695
		$logs = '';
696
		$status = (mb_strlen($mailFrom) - mb_strlen('.' . $fromDomain)) === strpos($mailFrom, '.' . $fromDomain);
697
		if (!$status) {
698
			$logs = "RFC5321.MailFrom domain: $mailFrom | RFC5322.From domain: $fromDomain \n";
699
			if ($this->mailMimeParser->getHeader('DKIM-Signature')) {
700
				$dkimDomain = self::parseHeaderParams($this->mailMimeParser->getHeaderValue('DKIM-Signature'))['d'];
701
				$status = (mb_strlen($dkimDomain) - mb_strlen('.' . $fromDomain)) === strpos($dkimDomain, '.' . $fromDomain);
702
				if (!$status) {
703
					$logs .= "DKIM domain: $dkimDomain | RFC5322.From domain: $fromDomain";
704
				}
705
			}
706
		}
707
		return ['status' => $status, 'log' => trim($logs)];
708
	}
709
710
	/**
711
	 * Update list by request id.
712
	 *
713
	 * @param int $record
714
	 *
715
	 * @return int
716
	 */
717
	public function updateList(int $record): int
718
	{
719
		$sender = $this->getSender();
720
		$return = 0;
721
		if (!empty($sender['ip'])) {
722
			$dbCommand = \App\Db::getInstance('admin')->createCommand();
723
			$id = false;
724
			if ($rows = self::findIp($sender['ip'])) {
725
				foreach ($rows as $row) {
726
					if ((self::LIST_TYPE_BLACK_LIST !== (int) $row['type']) && (self::LIST_TYPE_PUBLIC_BLACK_LIST !== (int) $row['type'])) {
727
						$id = $row['id'];
728
						break;
729
					}
730
				}
731
			}
732
			if ($id) {
733
				$dbCommand->update('s_#__mail_rbl_list', [
734
					'status' => 0,
735
					'type' => $this->get('type'),
736
					'request' => $record,
737
				], ['id' => $id])->execute();
738
			} else {
739
				$dbCommand->insert('s_#__mail_rbl_list', [
740
					'ip' => $sender['ip'],
741
					'status' => 0,
742
					'type' => $this->get('type'),
743
					'request' => $record,
744
					'source' => '',
745
				])->execute();
746
			}
747
			\App\Cache::delete('MailRblIpColor', $sender['ip']);
748
			\App\Cache::delete('MailRblList', $sender['ip']);
749
			$return = 2;
750
			if (\Config\Components\Mail::$rcListSendReportAutomatically ?? false) {
0 ignored issues
show
Bug introduced by
The type Config\Components\Mail was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
751
				self::sendReport(['id' => $record]);
752
				$return = 3;
753
			}
754
		}
755
		return $return;
756
	}
757
758
	/**
759
	 * Get color by ips.
760
	 *
761
	 * @param array $ips
762
	 *
763
	 * @return array
764
	 */
765
	public static function getColorByIps(array $ips): array
766
	{
767
		$return = $find = [];
768
		foreach ($ips as $uid => $ip) {
769
			if (\App\Cache::has('MailRblIpColor', $ip)) {
770
				$return[$uid] = \App\Cache::get('MailRblIpColor', $ip);
771
			} else {
772
				$find[$ip][] = $uid;
773
			}
774
		}
775
		if ($find) {
776
			$list = [];
777
			$dataReader = (new \App\Db\Query())->select(['id', 'ip', 'status', 'source', 'type'])->from('s_#__mail_rbl_list')->where(['ip' => array_keys($find)])
778
				->createCommand(\App\Db::getInstance('admin'))->query();
779
			while ($row = $dataReader->read()) {
780
				$list[$row['ip']][] = $row;
781
			}
782
			foreach ($list as $ip => $rows) {
783
				$color = self::getColorByList($ip, $rows);
784
				foreach ($find[$ip] as $uid) {
785
					$return[$uid] = $color;
786
				}
787
			}
788
		}
789
		return $return;
790
	}
791
792
	/**
793
	 * Find ip in list.
794
	 *
795
	 * @param string $ip
796
	 * @param bool   $onlyActive
797
	 *
798
	 * @return array
799
	 */
800
	public static function findIp(string $ip, $onlyActive = false): array
801
	{
802
		$cacheName = "$ip|$onlyActive";
803
		if (\App\Cache::has('MailRblList', $cacheName)) {
804
			return \App\Cache::get('MailRblList', $cacheName);
805
		}
806
		$query = (new \App\Db\Query())->from('s_#__mail_rbl_list')->where(['ip' => $ip])->orderBy(['type' => SORT_ASC]);
807
		if ($onlyActive) {
808
			$query->andWhere(['status' => 0]);
809
		}
810
		$rows = $query->all(\App\Db::getInstance('admin'));
811
		\App\Cache::save('MailRblList', $cacheName, $rows, \App\Cache::LONG);
812
		return $rows;
813
	}
814
815
	/**
816
	 * Get color by list.
817
	 *
818
	 * @param string $ip
819
	 * @param array  $rows
820
	 *
821
	 * @return string
822
	 */
823
	public static function getColorByList(string $ip, array $rows): string
824
	{
825
		if (\App\Cache::has('MailRblIpColor', $ip)) {
826
			return \App\Cache::get('MailRblIpColor', $ip);
827
		}
828
		$color = '';
829
		foreach ($rows as $row) {
830
			if (1 !== (int) $row['status']) {
831
				$color = self::LIST_TYPES[$row['type']]['listColor'];
832
				break;
833
			}
834
		}
835
		\App\Cache::save('MailRblIpColor', $ip, $color, \App\Cache::LONG);
836
		return $color;
837
	}
838
839
	/**
840
	 * Parse header params.
841
	 *
842
	 * @param string $string
843
	 *
844
	 * @return array
845
	 */
846
	public static function parseHeaderParams(string $string): array
847
	{
848
		$params = [];
849
		foreach (explode(';', rtrim(preg_replace('/\s+/', '', $string), ';')) as $param) {
850
			[$tagName, $tagValue] = explode('=', trim($param), 2);
851
			if ('' !== $tagName) {
852
				$params[$tagName] = $tagValue;
853
			}
854
		}
855
		return $params;
856
	}
857
858
	/**
859
	 * Add report.
860
	 *
861
	 * @param array $data
862
	 *
863
	 * @return string
864
	 */
865
	public static function addReport(array $data): string
866
	{
867
		$status = 0;
868
		if (\Config\Components\Mail::$rcListAcceptAutomatically ?? false) {
869
			$status = 1;
870
		}
871
		$db = \App\Db::getInstance('admin');
872
		$dbCommand = $db->createCommand();
873
		$dbCommand->insert('s_#__mail_rbl_request', [
874
			'status' => $status,
875
			'datetime' => date('Y-m-d H:i:s'),
876
			'user' => \App\User::getCurrentUserId(),
877
			'type' => $data['type'],
878
			'header' => $data['header'],
879
			'body' => $data['body'] ?? null,
880
		])->execute();
881
		$record = $db->getLastInsertID();
882
		$return = 'LBL_RC_ERROR_RBL_REPORT';
883
		if ($record) {
884
			$return = 'LBL_RC_ADDED_RBL_REPORT_LOCAL';
885
			if ($status) {
886
				$rblRecord = self::getRequestById($record);
0 ignored issues
show
Bug introduced by
$record of type string is incompatible with the type integer expected by parameter $id of App\Mail\Rbl::getRequestById(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

886
				$rblRecord = self::getRequestById(/** @scrutinizer ignore-type */ $record);
Loading history...
887
				$rblRecord->parse();
888
				$sender = $rblRecord->getSender();
889
				if (empty($sender['ip'])) {
890
					$dbCommand->update('s_#__mail_rbl_request', ['status' => 0], ['id' => $record])->execute();
891
					$return = 'LBL_NO_IP_ADDRESS';
892
				} else {
893
					$blacklist = 0 == $rblRecord->get('type');
894
					$skipUpdate = false;
895
					if ($rows = self::findIp($sender['ip'])) {
896
						foreach ($rows as $row) {
897
							if (0 == $row['status']) {
898
								if ($blacklist && (self::LIST_TYPE_WHITE_LIST == $row['type'] || self::LIST_TYPE_PUBLIC_WHITE_LIST == $row['type'])) {
899
									$return = 'LBL_RC_ADDED_RBL_REPORT_IP_WHITE';
900
									$skipUpdate = true;
901
									break;
902
								}
903
								if (!$blacklist && (self::LIST_TYPE_WHITE_LIST == $row['type'] || self::LIST_TYPE_PUBLIC_WHITE_LIST == $row['type'])) {
904
									$return = 'LBL_RC_ADDED_RBL_REPORT_IP_BLACK';
905
									$skipUpdate = true;
906
									break;
907
								}
908
							}
909
						}
910
					}
911
					if ($skipUpdate) {
912
						$dbCommand->update('s_#__mail_rbl_request', ['status' => 0], ['id' => $record])->execute();
913
					} elseif (3 === $rblRecord->updateList($record)) {
914
						$return = 'LBL_RC_ADDED_RBL_REPORT_PUBLIC';
915
					}
916
				}
917
			}
918
		}
919
		return $return;
920
	}
921
922
	/**
923
	 * Send report.
924
	 *
925
	 * @param array $data
926
	 *
927
	 * @return array
928
	 */
929
	public static function sendReport(array $data): array
930
	{
931
		if (!\App\RequestUtil::isNetConnection()) {
932
			return ['status' => false, 'message' => \App\Language::translate('ERR_NO_INTERNET_CONNECTION', 'Other:Exceptions')];
933
		}
934
		$id = $data['id'];
935
		unset($data['id']);
936
		$recordModel = self::getRequestById($id);
937
		$recordModel->parse();
938
		$data['type'] = $recordModel->get('type') ? 'White' : 'Black';
939
		if (empty($data['category'])) {
940
			$data['category'] = '[SPAM] Single unwanted message';
941
		}
942
		$url = 'https://soc.yetiforce.com/api/Application';
943
		\App\Log::beginProfile("POST|Rbl::sendReport|{$url}", __NAMESPACE__);
944
		$response = (new \GuzzleHttp\Client(\App\RequestHttp::getOptions()))->post($url, [
0 ignored issues
show
Security Code Execution introduced by
array('http_errors' => f...rdModel->get('body')))) can contain request data and is used in code execution context(s) leading to a potential security vulnerability.

5 paths for user data to reach this point

  1. Path: Read tainted data from array, and Data is passed through explode(), and explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) is assigned to $code in install/views/Index.php on line 74
  1. Read tainted data from array, and Data is passed through explode(), and explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']) is assigned to $code
    in install/views/Index.php on line 74
  2. $code is assigned to $lang
    in install/views/Index.php on line 79
  3. Request::set() is called
    in install/views/Index.php on line 87
  4. Enters via parameter $value
    in app/Request.php on line 594
  5. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  6. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  7. array('id' => $request->getInteger('id')) is assigned to $data
    in modules/Settings/MailRbl/actions/SendReport.php on line 21
  8. Rbl::sendReport() is called
    in modules/Settings/MailRbl/actions/SendReport.php on line 28
  9. Enters via parameter $data
    in app/Mail/Rbl.php on line 929
  10. Data is passed through array_merge()
    in app/Mail/Rbl.php on line 949
  2. Path: Read from $_REQUEST, and Request::__construct() is called in app/Request.php on line 728
  1. Read from $_REQUEST, and Request::__construct() is called
    in app/Request.php on line 728
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and $this->rawValues[$key] is returned
    in app/Request.php on line 463
  5. $request->getRaw('inventory') is assigned to $rawInventory
    in modules/Vtiger/models/Record.php on line 1323
  6. Request::set() is called
    in modules/Vtiger/models/Record.php on line 1334
  7. Enters via parameter $value
    in app/Request.php on line 594
  8. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  9. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  10. array('id' => $request->getInteger('id')) is assigned to $data
    in modules/Settings/MailRbl/actions/SendReport.php on line 21
  11. Rbl::sendReport() is called
    in modules/Settings/MailRbl/actions/SendReport.php on line 28
  12. Enters via parameter $data
    in app/Mail/Rbl.php on line 929
  13. Data is passed through array_merge()
    in app/Mail/Rbl.php on line 949
  3. Path: Request::set() is called in modules/Vtiger/models/Widget.php on line 615
  1. Request::set() is called
    in modules/Vtiger/models/Widget.php on line 615
  2. Enters via parameter $value
    in app/Request.php on line 594
  3. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  4. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  5. $request->getInteger('dashboardId') is assigned to $dashboardId
    in modules/Vtiger/models/Widget.php on line 608
  6. Request::set() is called
    in modules/Vtiger/models/Widget.php on line 615
  7. Enters via parameter $value
    in app/Request.php on line 594
  8. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  9. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  10. array('id' => $request->getInteger('id')) is assigned to $data
    in modules/Settings/MailRbl/actions/SendReport.php on line 21
  11. Rbl::sendReport() is called
    in modules/Settings/MailRbl/actions/SendReport.php on line 28
  12. Enters via parameter $data
    in app/Mail/Rbl.php on line 929
  13. Data is passed through array_merge()
    in app/Mail/Rbl.php on line 949
  4. Path: Read from $_REQUEST, and Request::__construct() is called in api/webservice/Core/Request.php on line 56
  1. Read from $_REQUEST, and Request::__construct() is called
    in api/webservice/Core/Request.php on line 56
  2. Enters via parameter $rawValues
    in app/Request.php on line 110
  3. $rawValues is assigned to property Request::$rawValues
    in app/Request.php on line 112
  4. Read from property Request::$rawValues, and $this->rawValues[$key] is returned
    in app/Request.php on line 463
  5. $request->getRaw('inventory') is assigned to $rawInventory
    in modules/Vtiger/models/Record.php on line 1323
  6. Request::set() is called
    in modules/Vtiger/models/Record.php on line 1334
  7. Enters via parameter $value
    in app/Request.php on line 594
  8. $this->purifiedValuesByHtml[$key] = $value is assigned to property Request::$purifiedValuesByInteger
    in app/Request.php on line 599
  9. Read from property Request::$purifiedValuesByInteger, and $this->purifiedValuesByInteger[$key] is returned
    in app/Request.php on line 203
  10. array('id' => $request->getInteger('id')) is assigned to $data
    in modules/Settings/MailRbl/actions/SendReport.php on line 21
  11. Rbl::sendReport() is called
    in modules/Settings/MailRbl/actions/SendReport.php on line 28
  12. Enters via parameter $data
    in app/Mail/Rbl.php on line 929
  13. Data is passed through array_merge()
    in app/Mail/Rbl.php on line 949
  5. Path: DateTimeField::__construct() is called in app/Fields/Time.php on line 44
  1. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  2. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  3. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  4. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime) is assigned to $date_value
    in include/fields/DateTimeField.php on line 299
  5. Data is passed through convertToUserFormat(), and self::convertToUserFormat($date_value) is returned
    in include/fields/DateTimeField.php on line 308
  6. new DateTimeField($value)->getDisplayDate() is returned
    in app/Fields/Date.php on line 113
  7. $convertTimeZone ? App\Fields\Date::formatToDisplay(date('Y-m-d'), false) : date('Y-m-d') is assigned to $date
    in app/Fields/Time.php on line 43
  8. DateTimeField::__construct() is called
    in app/Fields/Time.php on line 44
  9. Enters via parameter $value
    in include/fields/DateTimeField.php on line 31
  10. $value is assigned to property DateTimeField::$datetime
    in include/fields/DateTimeField.php on line 38
  11. Read from property DateTimeField::$datetime, and Data is passed through explode(), and explode(' ', $this->datetime, 2) is assigned to $value
    in include/fields/DateTimeField.php on line 47
  12. Data is passed through convertToDBFormat(), and self::convertToDBFormat($value[0]) is assigned to $insert_date
    in include/fields/DateTimeField.php on line 53
  13. $insert_date is returned
    in include/fields/DateTimeField.php on line 55
  14. new DateTimeField($value)->getDBInsertDateValue() is returned
    in app/Fields/Date.php on line 168
  15. App\Validator::dateInUserFormat($input) ? $convert ? App\Fields\Date::formatToDB($input) : $input : null is assigned to $value
    in app/Purifier.php on line 451
  16. $value is returned
    in app/Purifier.php on line 569
  17. App\Purifier::purifyByType($this->rawValues[$key], $type, $convert) is assigned to property Request::$purifiedValuesByType
    in app/Request.php on line 170
  18. Read from property Request::$purifiedValuesByType, and $this->purifiedValuesByType[$key][$type] is returned
    in app/Request.php on line 167
  19. $request->getByType('category', 'Text') is assigned to $data
    in modules/Settings/MailRbl/actions/SendReport.php on line 26
  20. Rbl::sendReport() is called
    in modules/Settings/MailRbl/actions/SendReport.php on line 28
  21. Enters via parameter $data
    in app/Mail/Rbl.php on line 929
  22. Data is passed through array_merge()
    in app/Mail/Rbl.php on line 949

Used in code-execution context

  1. Client::post() is called
    in app/Mail/Rbl.php on line 944
  2. Enters via parameter $options
    in vendor/guzzlehttp/guzzle/src/ClientTrait.php on line 93
  3. Client::request() is called
    in vendor/guzzlehttp/guzzle/src/ClientTrait.php on line 95
  4. Enters via parameter $options
    in vendor/guzzlehttp/guzzle/src/Client.php on line 185
  5. Client::requestAsync() is called
    in vendor/guzzlehttp/guzzle/src/Client.php on line 189
  6. Enters via parameter $options
    in vendor/guzzlehttp/guzzle/src/Client.php on line 153
  7. Data is passed through prepareDefaults()
    in vendor/guzzlehttp/guzzle/src/Client.php on line 155
  8. $this->prepareDefaults($options) is assigned to $options
    in vendor/guzzlehttp/guzzle/src/Client.php on line 155
  9. Client::transfer() is called
    in vendor/guzzlehttp/guzzle/src/Client.php on line 169
  10. Enters via parameter $options
    in vendor/guzzlehttp/guzzle/src/Client.php on line 326
  11. $options['handler'] is assigned to $handler
    in vendor/guzzlehttp/guzzle/src/Client.php on line 330
  12. $handler() is called dynamically
    in vendor/guzzlehttp/guzzle/src/Client.php on line 333

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
945
			'http_errors' => false,
946
			'headers' => [
947
				'crm-ik' => \App\YetiForce\Register::getInstanceKey(),
948
			],
949
			'json' => array_merge($data, [
950
				'ik' => \App\YetiForce\Register::getInstanceKey(),
951
				'ip' => $recordModel->getSender()['ip'] ?? '-',
952
				'header' => $recordModel->get('header'),
953
				'body' => $recordModel->get('body'),
954
			]),
955
		]);
956
		\App\Log::endProfile("POST|Rbl::sendReport|{$url}", __NAMESPACE__);
957
		$body = \App\Json::decode($response->getBody()->getContents());
958
		if (200 == $response->getStatusCode() && 'ok' === $body['result']) {
959
			\App\Db::getInstance('admin')->createCommand()->update('s_#__mail_rbl_request', [
960
				'status' => 4,
961
			], ['id' => $id])->execute();
962
			return ['status' => true];
963
		}
964
		\App\Log::warning($response->getReasonPhrase() . ' | ' . ($body['error']['message'] ?? $body['result']), __METHOD__);
965
		return ['status' => false, 'message' => ($body['error']['message'] ?? $body['result'])];
966
	}
967
968
	/**
969
	 * Get IP list from public RBL.
970
	 *
971
	 * @param int $type
972
	 *
973
	 * @return array
974
	 */
975
	public static function getPublicList(int $type): array
976
	{
977
		if (!\App\RequestUtil::isNetConnection()) {
978
			\App\Log::warning('ERR_NO_INTERNET_CONNECTION', __METHOD__);
979
			return [];
980
		}
981
		$url = 'https://soc.yetiforce.com/list/' . (self::LIST_TYPE_PUBLIC_BLACK_LIST === $type ? 'black' : 'white');
982
		\App\Log::beginProfile("POST|Rbl::sendReport|{$url}", __NAMESPACE__);
983
		$response = (new \GuzzleHttp\Client(\App\RequestHttp::getOptions()))->get($url, [
984
			'http_errors' => false,
985
			'headers' => [
986
				'crm-ik' => \App\YetiForce\Register::getInstanceKey(),
987
			],
988
		]);
989
		\App\Log::endProfile("POST|Rbl::sendReport|{$url}", __NAMESPACE__);
990
		$list = [];
991
		if (200 === $response->getStatusCode()) {
992
			$list = \App\Json::decode($response->getBody()->getContents()) ?? [];
993
		} else {
994
			$body = \App\Json::decode($response->getBody()->getContents());
995
			\App\Log::warning($response->getReasonPhrase() . ' | ' . $body['error']['message'], __METHOD__);
996
		}
997
		return $list;
998
	}
999
1000
	/**
1001
	 * Public list synchronization.
1002
	 *
1003
	 * @param int $type
1004
	 *
1005
	 * @return void
1006
	 */
1007
	public static function sync(int $type): void
1008
	{
1009
		if (!\App\RequestUtil::isNetConnection()) {
1010
			\App\Log::warning('ERR_NO_INTERNET_CONNECTION', __METHOD__);
1011
			return;
1012
		}
1013
		$public = self::getPublicList($type);
1014
		$publicKeys = array_keys($public);
1015
		$db = \App\Db::getInstance('admin');
1016
		$dbCommand = $db->createCommand();
1017
		$query = (new \App\Db\Query())->select(['ip', 'source', 'id'])->from('s_#__mail_rbl_list')->where(['type' => $type]);
1018
		$rows = $query->createCommand($db)->queryAllByGroup(1);
1019
		$keys = array_keys($rows);
1020
		foreach (array_chunk(array_diff($publicKeys, $keys), 50, true) as $chunk) {
1021
			$insertData = [];
1022
			foreach ($chunk as $ip) {
1023
				$insertData[] = [$ip, 0, $type, $public[$ip]['source'], $public[$ip]['comment']];
1024
			}
1025
			$dbCommand->batchInsert('s_#__mail_rbl_list', ['ip', 'status', 'type', 'source', 'comment'], $insertData)->execute();
1026
		}
1027
		foreach (array_diff($keys, $publicKeys) as $ip) {
1028
			$dbCommand->delete('s_#__mail_rbl_list', ['id' => $rows[$ip]['id']])->execute();
1029
		}
1030
	}
1031
}
1032