1<?php
2/*
3** Zabbix
4** Copyright (C) 2001-2021 Zabbix SIA
5**
6** This program is free software; you can redistribute it and/or modify
7** it under the terms of the GNU General Public License as published by
8** the Free Software Foundation; either version 2 of the License, or
9** (at your option) any later version.
10**
11** This program is distributed in the hope that it will be useful,
12** but WITHOUT ANY WARRANTY; without even the implied warranty of
13** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14** GNU General Public License for more details.
15**
16** You should have received a copy of the GNU General Public License
17** along with this program; if not, write to the Free Software
18** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19**/
20
21
22/**
23 * A parser for AD samAccountName or userPrincipalName parser.
24 */
25class CADNameAttributeParser extends CParser {
26
27	const ZBX_TYPE_UNKNOWN = 0;
28	const ZBX_TYPE_SAMA = 0x1;
29	const ZBX_TYPE_UPN = 0x2;
30
31	/**
32	 * User name attribute type.
33	 *
34	 * @var int
35	 */
36	private $name_type;
37
38	/**
39	 * @var string
40	 */
41	private $user_name;
42
43	/**
44	 * @var string
45	 */
46	private $domain_name;
47
48	/**
49	 * @var array
50	 */
51	private $options;
52
53	/**
54	 * Create instance of parser.
55	 *
56	 * @param array $options              Array of options.
57	 * @param int   $options['strict']    For sAMAccount name check length of parsed domain and user. Default false.
58	 * @param int   $options['nametype']  Bit mask what type of user name should be parsed.
59	 *                                    Default parse sAMAccountName and UserPrincipalName.
60	 */
61	public function __construct(array $options = []) {
62		$options += ['nametype' => self::ZBX_TYPE_SAMA | self::ZBX_TYPE_UPN];
63		$this->options = [
64			'strict' => array_key_exists('strict', $options) && (bool) $options['strict'],
65			'type_sama' => array_key_exists('nametype', $options) && ($options['nametype'] & self::ZBX_TYPE_SAMA),
66			'type_upn' => array_key_exists('nametype', $options) && ($options['nametype'] & self::ZBX_TYPE_UPN)
67		];
68	}
69
70	/**
71	 * Parse given name attribute value.
72	 *
73	 * @param string $source  Source string that needs to be parsed.
74	 * @param int    $pos     Position offset.
75	 *
76	 * @return int
77	 */
78	public function parse($source, $pos = 0) {
79		$this->length = 0;
80		$this->user_name = '';
81		$this->domain_name = '';
82		$this->match = '';
83		$this->name_type = self::ZBX_TYPE_UNKNOWN;
84
85		if (($this->options['type_upn']) && $this->parseUserPrincipalName($source, $pos) == self::PARSE_SUCCESS) {
86			$this->name_type = self::ZBX_TYPE_UPN;
87		}
88		elseif ($this->options['type_sama'] && $this->parseSamAccountName($source, $pos) == self::PARSE_SUCCESS) {
89			$this->name_type = self::ZBX_TYPE_SAMA;
90		}
91		else {
92			return self::PARSE_FAIL;
93		}
94
95		return isset($source[$pos]) ? self::PARSE_SUCCESS_CONT : self::PARSE_SUCCESS;
96	}
97
98	/**
99	 * Get parsed name attribute type.
100	 *
101	 * @return int
102	 */
103	public function getNameType() {
104		return $this->name_type;
105	}
106
107	/**
108	 * Get parsed user name value.
109	 *
110	 * @return string|null
111	 */
112	public function getUserName() {
113		return ($this->name_type == self::ZBX_TYPE_UNKNOWN) ? null : $this->user_name;
114	}
115
116	/**
117	 * Get parsed user domain name value.
118	 *
119	 * @return string|null
120	 */
121	public function getDomainName() {
122		return ($this->name_type == self::ZBX_TYPE_UNKNOWN) ? null : $this->domain_name;
123	}
124
125	/**
126	 * Parse string searching for samAccountName.
127	 * https://docs.microsoft.com/en-us/windows/desktop/ADSchema/a-samaccountname
128	 * https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx
129	 *
130	 * @param string $source  Source string that needs to be parsed.
131	 * @param int    $pos     Position offset.
132	 *
133	 * @return int
134	 */
135	private function parseSamAccountName($source, &$pos) {
136		$strict = $this->options['strict'];
137		$regex = '/^(?<domain>[^\\\\\/\:\*\?\"\<\>]'.
138			($strict ? '{1,15}' : '+').
139			')\\\(?<user>[^\\\\\/\:\*\?\"\<\>@]'.
140			($strict ? '{1,20}' : '+').
141			')/i';
142
143		if (preg_match($regex, substr($source, $pos), $matches)) {
144			$this->length = strlen($matches[0]);
145		}
146		else {
147			return self::PARSE_FAIL;
148		}
149
150		$this->match = $matches[0];
151		$this->domain_name = $matches['domain'];
152		$this->user_name = $matches['user'];
153		$pos += $this->length;
154
155		return self::PARSE_SUCCESS;
156	}
157
158	/**
159	 * Parse string searching for UserPrincipalName.
160	 * https://docs.microsoft.com/en-us/windows/desktop/ADSchema/a-userprincipalname
161	 *
162	 * @param string $source  Source string that needs to be parsed.
163	 * @param int    $pos     Position offset.
164	 *
165	 * @return int
166	 */
167	private function parseUserPrincipalName($source, &$pos) {
168		$regex = '/^(?<user>[_a-z0-9-@]+(\.[_a-z0-9-]+)*)@(?<domain>[a-z0-9-]+(\.[a-z0-9-]+)*)/i';
169
170		if (preg_match($regex, substr($source, $pos), $matches)) {
171			$this->length = strlen($matches[0]);
172		}
173		else {
174			return self::PARSE_FAIL;
175		}
176
177		$this->match = $matches[0];
178		$this->domain_name = $matches['domain'];
179		$this->user_name = $matches['user'];
180		$pos += $this->length;
181
182		return self::PARSE_SUCCESS;
183	}
184}
185