diff options
Diffstat (limited to 'src/ext/Firewall/wixext/FirewallCompiler.cs')
-rw-r--r-- | src/ext/Firewall/wixext/FirewallCompiler.cs | 354 |
1 files changed, 354 insertions, 0 deletions
diff --git a/src/ext/Firewall/wixext/FirewallCompiler.cs b/src/ext/Firewall/wixext/FirewallCompiler.cs new file mode 100644 index 00000000..cbe82d37 --- /dev/null +++ b/src/ext/Firewall/wixext/FirewallCompiler.cs | |||
@@ -0,0 +1,354 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Firewall | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Xml.Linq; | ||
8 | using WixToolset.Data; | ||
9 | using WixToolset.Extensibility; | ||
10 | using WixToolset.Extensibility.Data; | ||
11 | using WixToolset.Firewall.Symbols; | ||
12 | |||
13 | /// <summary> | ||
14 | /// The compiler for the WiX Toolset Firewall Extension. | ||
15 | /// </summary> | ||
16 | public sealed class FirewallCompiler : BaseCompilerExtension | ||
17 | { | ||
18 | public override XNamespace Namespace => "http://wixtoolset.org/schemas/v4/wxs/firewall"; | ||
19 | |||
20 | /// <summary> | ||
21 | /// Processes an element for the Compiler. | ||
22 | /// </summary> | ||
23 | /// <param name="sourceLineNumbers">Source line number for the parent element.</param> | ||
24 | /// <param name="parentElement">Parent element of element to process.</param> | ||
25 | /// <param name="element">Element to process.</param> | ||
26 | /// <param name="contextValues">Extra information about the context in which this element is being parsed.</param> | ||
27 | public override void ParseElement(Intermediate intermediate, IntermediateSection section, XElement parentElement, XElement element, IDictionary<string, string> context) | ||
28 | { | ||
29 | switch (parentElement.Name.LocalName) | ||
30 | { | ||
31 | case "File": | ||
32 | var fileId = context["FileId"]; | ||
33 | var fileComponentId = context["ComponentId"]; | ||
34 | |||
35 | switch (element.Name.LocalName) | ||
36 | { | ||
37 | case "FirewallException": | ||
38 | this.ParseFirewallExceptionElement(intermediate, section, element, fileComponentId, fileId); | ||
39 | break; | ||
40 | default: | ||
41 | this.ParseHelper.UnexpectedElement(parentElement, element); | ||
42 | break; | ||
43 | } | ||
44 | break; | ||
45 | case "Component": | ||
46 | var componentId = context["ComponentId"]; | ||
47 | |||
48 | switch (element.Name.LocalName) | ||
49 | { | ||
50 | case "FirewallException": | ||
51 | this.ParseFirewallExceptionElement(intermediate, section, element, componentId, null); | ||
52 | break; | ||
53 | default: | ||
54 | this.ParseHelper.UnexpectedElement(parentElement, element); | ||
55 | break; | ||
56 | } | ||
57 | break; | ||
58 | default: | ||
59 | this.ParseHelper.UnexpectedElement(parentElement, element); | ||
60 | break; | ||
61 | } | ||
62 | } | ||
63 | |||
64 | /// <summary> | ||
65 | /// Parses a FirewallException element. | ||
66 | /// </summary> | ||
67 | /// <param name="element">The element to parse.</param> | ||
68 | /// <param name="componentId">Identifier of the component that owns this firewall exception.</param> | ||
69 | /// <param name="fileId">The file identifier of the parent element (null if nested under Component).</param> | ||
70 | private void ParseFirewallExceptionElement(Intermediate intermediate, IntermediateSection section, XElement element, string componentId, string fileId) | ||
71 | { | ||
72 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
73 | Identifier id = null; | ||
74 | string name = null; | ||
75 | int attributes = 0; | ||
76 | string file = null; | ||
77 | string program = null; | ||
78 | string port = null; | ||
79 | int? protocol = null; | ||
80 | int? profile = null; | ||
81 | string scope = null; | ||
82 | string remoteAddresses = null; | ||
83 | string description = null; | ||
84 | int? direction = null; | ||
85 | |||
86 | foreach (var attrib in element.Attributes()) | ||
87 | { | ||
88 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
89 | { | ||
90 | switch (attrib.Name.LocalName) | ||
91 | { | ||
92 | case "Id": | ||
93 | id = this.ParseHelper.GetAttributeIdentifier(sourceLineNumbers, attrib); | ||
94 | break; | ||
95 | case "Name": | ||
96 | name = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
97 | break; | ||
98 | case "File": | ||
99 | if (null != fileId) | ||
100 | { | ||
101 | this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "File", "File")); | ||
102 | } | ||
103 | else | ||
104 | { | ||
105 | file = this.ParseHelper.GetAttributeIdentifierValue(sourceLineNumbers, attrib); | ||
106 | } | ||
107 | break; | ||
108 | case "IgnoreFailure": | ||
109 | if (YesNoType.Yes == this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib)) | ||
110 | { | ||
111 | attributes |= 0x1; // feaIgnoreFailures | ||
112 | } | ||
113 | break; | ||
114 | case "Program": | ||
115 | if (null != fileId) | ||
116 | { | ||
117 | this.Messaging.Write(ErrorMessages.IllegalAttributeWhenNested(sourceLineNumbers, element.Name.LocalName, "Program", "File")); | ||
118 | } | ||
119 | else | ||
120 | { | ||
121 | program = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
122 | } | ||
123 | break; | ||
124 | case "Port": | ||
125 | port = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
126 | break; | ||
127 | case "Protocol": | ||
128 | var protocolValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
129 | switch (protocolValue) | ||
130 | { | ||
131 | case "tcp": | ||
132 | protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP; | ||
133 | break; | ||
134 | case "udp": | ||
135 | protocol = FirewallConstants.NET_FW_IP_PROTOCOL_UDP; | ||
136 | break; | ||
137 | default: | ||
138 | this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Protocol", protocolValue, "tcp", "udp")); | ||
139 | break; | ||
140 | } | ||
141 | break; | ||
142 | case "Scope": | ||
143 | scope = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
144 | switch (scope) | ||
145 | { | ||
146 | case "any": | ||
147 | remoteAddresses = "*"; | ||
148 | break; | ||
149 | case "localSubnet": | ||
150 | remoteAddresses = "LocalSubnet"; | ||
151 | break; | ||
152 | default: | ||
153 | this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Scope", scope, "any", "localSubnet")); | ||
154 | break; | ||
155 | } | ||
156 | break; | ||
157 | case "Profile": | ||
158 | var profileValue = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
159 | switch (profileValue) | ||
160 | { | ||
161 | case "domain": | ||
162 | profile = FirewallConstants.NET_FW_PROFILE2_DOMAIN; | ||
163 | break; | ||
164 | case "private": | ||
165 | profile = FirewallConstants.NET_FW_PROFILE2_PRIVATE; | ||
166 | break; | ||
167 | case "public": | ||
168 | profile = FirewallConstants.NET_FW_PROFILE2_PUBLIC; | ||
169 | break; | ||
170 | case "all": | ||
171 | profile = FirewallConstants.NET_FW_PROFILE2_ALL; | ||
172 | break; | ||
173 | default: | ||
174 | this.Messaging.Write(ErrorMessages.IllegalAttributeValue(sourceLineNumbers, element.Name.LocalName, "Profile", profileValue, "domain", "private", "public", "all")); | ||
175 | break; | ||
176 | } | ||
177 | break; | ||
178 | case "Description": | ||
179 | description = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
180 | break; | ||
181 | case "Outbound": | ||
182 | direction = this.ParseHelper.GetAttributeYesNoValue(sourceLineNumbers, attrib) == YesNoType.Yes | ||
183 | ? FirewallConstants.NET_FW_RULE_DIR_OUT | ||
184 | : FirewallConstants.NET_FW_RULE_DIR_IN; | ||
185 | break; | ||
186 | default: | ||
187 | this.ParseHelper.UnexpectedAttribute(element, attrib); | ||
188 | break; | ||
189 | } | ||
190 | } | ||
191 | else | ||
192 | { | ||
193 | this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | // parse RemoteAddress children | ||
198 | foreach (var child in element.Elements()) | ||
199 | { | ||
200 | if (this.Namespace == child.Name.Namespace) | ||
201 | { | ||
202 | switch (child.Name.LocalName) | ||
203 | { | ||
204 | case "RemoteAddress": | ||
205 | if (null != scope) | ||
206 | { | ||
207 | this.Messaging.Write(FirewallErrors.IllegalRemoteAddressWithScopeAttribute(sourceLineNumbers)); | ||
208 | } | ||
209 | else | ||
210 | { | ||
211 | this.ParseRemoteAddressElement(intermediate, section, child, ref remoteAddresses); | ||
212 | } | ||
213 | break; | ||
214 | default: | ||
215 | this.ParseHelper.UnexpectedElement(element, child); | ||
216 | break; | ||
217 | } | ||
218 | } | ||
219 | else | ||
220 | { | ||
221 | this.ParseHelper.ParseExtensionElement(this.Context.Extensions, intermediate, section, element, child); | ||
222 | } | ||
223 | } | ||
224 | |||
225 | if (null == id) | ||
226 | { | ||
227 | id = this.ParseHelper.CreateIdentifier("fex", name, remoteAddresses, componentId); | ||
228 | } | ||
229 | |||
230 | // Name is required | ||
231 | if (null == name) | ||
232 | { | ||
233 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Name")); | ||
234 | } | ||
235 | |||
236 | // Scope or child RemoteAddress(es) are required | ||
237 | if (null == remoteAddresses) | ||
238 | { | ||
239 | this.Messaging.Write(ErrorMessages.ExpectedAttributeOrElement(sourceLineNumbers, element.Name.LocalName, "Scope", "RemoteAddress")); | ||
240 | } | ||
241 | |||
242 | // can't have both Program and File | ||
243 | if (null != program && null != file) | ||
244 | { | ||
245 | this.Messaging.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(sourceLineNumbers, element.Name.LocalName, "File", "Program")); | ||
246 | } | ||
247 | |||
248 | // must be nested under File, have File or Program attributes, or have Port attribute | ||
249 | if (String.IsNullOrEmpty(fileId) && String.IsNullOrEmpty(file) && String.IsNullOrEmpty(program) && String.IsNullOrEmpty(port)) | ||
250 | { | ||
251 | this.Messaging.Write(FirewallErrors.NoExceptionSpecified(sourceLineNumbers)); | ||
252 | } | ||
253 | |||
254 | if (!this.Messaging.EncounteredError) | ||
255 | { | ||
256 | // at this point, File attribute and File parent element are treated the same | ||
257 | if (null != file) | ||
258 | { | ||
259 | fileId = file; | ||
260 | } | ||
261 | |||
262 | var symbol = section.AddSymbol(new WixFirewallExceptionSymbol(sourceLineNumbers, id) | ||
263 | { | ||
264 | Name = name, | ||
265 | RemoteAddresses = remoteAddresses, | ||
266 | Profile = profile ?? FirewallConstants.NET_FW_PROFILE2_ALL, | ||
267 | ComponentRef = componentId, | ||
268 | Description = description, | ||
269 | Direction = direction ?? FirewallConstants.NET_FW_RULE_DIR_IN, | ||
270 | }); | ||
271 | |||
272 | if (!String.IsNullOrEmpty(port)) | ||
273 | { | ||
274 | symbol.Port = port; | ||
275 | |||
276 | if (!protocol.HasValue) | ||
277 | { | ||
278 | // default protocol is "TCP" | ||
279 | protocol = FirewallConstants.NET_FW_IP_PROTOCOL_TCP; | ||
280 | } | ||
281 | } | ||
282 | |||
283 | if (protocol.HasValue) | ||
284 | { | ||
285 | symbol.Protocol = protocol.Value; | ||
286 | } | ||
287 | |||
288 | if (!String.IsNullOrEmpty(fileId)) | ||
289 | { | ||
290 | symbol.Program = $"[#{fileId}]"; | ||
291 | this.ParseHelper.CreateSimpleReference(section, sourceLineNumbers, SymbolDefinitions.File, fileId); | ||
292 | } | ||
293 | else if (!String.IsNullOrEmpty(program)) | ||
294 | { | ||
295 | symbol.Program = program; | ||
296 | } | ||
297 | |||
298 | if (CompilerConstants.IntegerNotSet != attributes) | ||
299 | { | ||
300 | symbol.Attributes = attributes; | ||
301 | } | ||
302 | |||
303 | this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4SchedFirewallExceptionsInstall", this.Context.Platform, CustomActionPlatforms.ARM64 | CustomActionPlatforms.X64 | CustomActionPlatforms.X86); | ||
304 | this.ParseHelper.CreateCustomActionReference(sourceLineNumbers, section, "Wix4SchedFirewallExceptionsUninstall", this.Context.Platform, CustomActionPlatforms.ARM64 | CustomActionPlatforms.X64 | CustomActionPlatforms.X86); | ||
305 | } | ||
306 | } | ||
307 | |||
308 | /// <summary> | ||
309 | /// Parses a RemoteAddress element | ||
310 | /// </summary> | ||
311 | /// <param name="element">The element to parse.</param> | ||
312 | private void ParseRemoteAddressElement(Intermediate intermediate, IntermediateSection section, XElement element, ref string remoteAddresses) | ||
313 | { | ||
314 | var sourceLineNumbers = this.ParseHelper.GetSourceLineNumbers(element); | ||
315 | string address = null; | ||
316 | |||
317 | // no attributes | ||
318 | foreach (var attrib in element.Attributes()) | ||
319 | { | ||
320 | if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || this.Namespace == attrib.Name.Namespace) | ||
321 | { | ||
322 | switch (attrib.Name.LocalName) | ||
323 | { | ||
324 | case "Value": | ||
325 | address = this.ParseHelper.GetAttributeValue(sourceLineNumbers, attrib); | ||
326 | break; | ||
327 | } | ||
328 | } | ||
329 | else | ||
330 | { | ||
331 | this.ParseHelper.ParseExtensionAttribute(this.Context.Extensions, intermediate, section, element, attrib); | ||
332 | } | ||
333 | } | ||
334 | |||
335 | this.ParseHelper.ParseForExtensionElements(this.Context.Extensions, intermediate, section, element); | ||
336 | |||
337 | if (String.IsNullOrEmpty(address)) | ||
338 | { | ||
339 | this.Messaging.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, element.Name.LocalName, "Value")); | ||
340 | } | ||
341 | else | ||
342 | { | ||
343 | if (String.IsNullOrEmpty(remoteAddresses)) | ||
344 | { | ||
345 | remoteAddresses = address; | ||
346 | } | ||
347 | else | ||
348 | { | ||
349 | remoteAddresses = String.Concat(remoteAddresses, ",", address); | ||
350 | } | ||
351 | } | ||
352 | } | ||
353 | } | ||
354 | } | ||