aboutsummaryrefslogtreecommitdiff
path: root/src/ext/Firewall/wixext/FirewallCompiler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ext/Firewall/wixext/FirewallCompiler.cs')
-rw-r--r--src/ext/Firewall/wixext/FirewallCompiler.cs354
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
3namespace 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}