Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SyslogClient.cs
1using System;
3using System.Diagnostics;
4using System.IO;
5using System.Security.Authentication;
6using System.Security.Cryptography.X509Certificates;
7using System.Text;
8using System.Threading.Tasks;
13using Waher.Security;
14
16{
23 public class SyslogClient : CommunicationLayer, IDisposable, IDisposableAsync,
25 {
29 public const int DefaultPort = 514;
30
34 public const int DefaultTlsPort = 6514;
35
36 private readonly SyslogEventSeparation separation;
37 private readonly byte[] localHostName;
38 private readonly byte[] appName;
39 private readonly string host;
40 private readonly int port;
41 private readonly bool tls;
42 private X509Certificate clientCertificate;
43 private BinaryTcpClient client = null;
44
58 public SyslogClient(string Host, int Port, bool Tls, string LocalHostName,
59 string AppName, SyslogEventSeparation Separation, params ISniffer[] Sniffers)
60 : base(true, Sniffers)
61 {
62 this.host = Host;
63 this.port = Port;
64 this.tls = Tls;
65 this.localHostName = EncodeName(LocalHostName);
66 this.appName = EncodeName(AppName);
67 this.separation = Separation;
68 }
69
84 public SyslogClient(string Host, int Port, X509Certificate ClientCertificate,
85 string LocalHostName, string AppName, SyslogEventSeparation Separation,
86 params ISniffer[] Sniffers)
87 : this(Host, Port, true, LocalHostName, AppName, Separation, Sniffers)
88 {
89 this.clientCertificate = ClientCertificate;
90 }
91
95 public string Host => this.host;
96
100 public int Port => this.port;
101
105 public bool Tls => this.tls;
106
110 public SyslogEventSeparation Separation => this.separation;
111
116 public Task Send(Event Event)
117 {
118 return this.Send(Event, false);
119 }
120
126 public async Task Send(Event Event, bool WaitSent)
127 {
128 bool ResendIfError = true;
129
130 while (true)
131 {
132 if (!(this.client is null) && !this.client.Connected)
133 {
134 this.Warning("Disposing/Closing TCP client.");
135
136 await this.client.DisposeAsync();
137 this.client = null;
138 }
139
140 if (this.client is null)
141 {
142 this.Information("Connecting to " + this.host + ":" + this.port.ToString());
143
144 this.client = new BinaryTcpClient(true, true);
145 this.client.OnDisconnected += this.Client_OnDisconnected;
146 this.client.OnError += this.Client_OnError;
147 this.client.OnReceived += this.Client_OnReceived;
148
149 await this.client.ConnectAsync(this.host, this.port, this.tls);
150
151 this.Information("Connected to " + this.host + ":" + this.port.ToString());
152
153 if (this.tls)
154 {
155 this.Information("Upgrading to TLS.");
156
157 if (this.clientCertificate is null)
158 await this.client.UpgradeToTlsAsClient(SslProtocols.Tls12);
159 else
160 {
161 await this.client.UpgradeToTlsAsClient(this.clientCertificate,
162 SslProtocols.Tls12);
163 }
164
165 this.client.Continue();
166 }
167 }
168
169 byte[] Message = this.CreateMessage(Event);
170 int MessageLength = Message.Length;
171 byte[] Packet;
172
173 switch (this.separation)
174 {
175 case SyslogEventSeparation.OctetCounting:
176 default:
177 byte[] Prefix = Encoding.ASCII.GetBytes(MessageLength.ToString());
178 int PrefixLength = Prefix.Length;
179 Packet = new byte[PrefixLength + MessageLength + 1];
180 Buffer.BlockCopy(Prefix, 0, Packet, 0, PrefixLength);
181 Packet[PrefixLength] = (byte)' ';
182 Buffer.BlockCopy(Message, 0, Packet, PrefixLength + 1, MessageLength);
183 break;
184
185 case SyslogEventSeparation.CrLf:
186 Packet = new byte[MessageLength + 2];
187 Buffer.BlockCopy(Message, 0, Packet, 0, MessageLength);
188 Packet[MessageLength] = (byte)'\r';
189 Packet[MessageLength + 1] = (byte)'\n';
190 break;
191
192 case SyslogEventSeparation.Lf:
193 Packet = new byte[MessageLength + 1];
194 Buffer.BlockCopy(Message, 0, Packet, 0, MessageLength);
195 Packet[MessageLength] = (byte)'\n';
196 break;
197
198 case SyslogEventSeparation.Null:
199 Packet = new byte[MessageLength + 1];
200 Buffer.BlockCopy(Message, 0, Packet, 0, MessageLength);
201 Packet[MessageLength] = 0;
202 break;
203 }
204
205 bool Ok;
206
207 try
208 {
209 if (this.HasSniffers)
210 this.TransmitText(Encoding.UTF8.GetString(Packet));
211
212 if (WaitSent)
213 {
214 TaskCompletionSource<bool> Sent = new TaskCompletionSource<bool>();
215
216 Ok = await this.client.SendAsync(true, Packet,
217 (sender, e) =>
218 {
219 Sent.TrySetResult(e.Ok);
220 return Task.CompletedTask;
221 }, null);
222
223 if (Ok)
224 await Sent.Task;
225 }
226 else
227 Ok = await this.client.SendAsync(true, Packet);
228 }
229 catch (Exception ex)
230 {
231 this.Exception(ex);
232 Ok = false;
233 }
234
235 if (Ok)
236 break;
237 else
238 {
239 if (ResendIfError)
240 {
241 this.Information("Resending...");
242
243 ResendIfError = false;
244 await this.client.DisposeAsync();
245 this.client = null;
246 }
247 else
248 throw new IOException("Unable to send message to Syslog server.");
249 }
250 }
251 }
252
253 private Task<bool> Client_OnReceived(object Sender, bool ConstantBuffer, byte[] Buffer, int Offset, int Count)
254 {
255 if (this.HasSniffers)
256 {
257 this.ReceiveText(Encoding.UTF8.GetString(Buffer, Offset, Count));
258 this.Warning("Received unexpected data from Syslog server. Data ignored.");
259 }
260
261 return Task.FromResult(true);
262 }
263
264 private Task Client_OnError(object Sender, Exception e)
265 {
266 this.Error(e.Message);
267 return Task.CompletedTask;
268 }
269
270 private Task Client_OnDisconnected(object Sender, EventArgs e)
271 {
272 this.Information("Disconnected.");
273 return Task.CompletedTask;
274 }
275
281 public byte[] CreateMessage(Event Event)
282 {
283 // Building message, as defined in RFC 5424:
284
285 using MemoryStream ms = new MemoryStream();
286
287 #region HEADER
288
289 // PRI (Priority)
290
291 int Facility = 16; // Local use 0
292 int Severity = Event.Type switch
293 {
294 EventType.Emergency => 0,
295 EventType.Alert => 1,
296 EventType.Critical => 2,
297 EventType.Error => 3,
298 EventType.Warning => 4,
299 EventType.Notice => 5,
300 EventType.Debug => 7,
301 _ => 6,
302 };
303
304 ms.WriteByte((byte)'<');
305 ms.Write(Encoding.ASCII.GetBytes((Facility * 8 + Severity).ToString()));
306 ms.WriteByte((byte)'>');
307
308 // VERSION
309
310 ms.WriteByte((byte)' ');
311 ms.WriteByte((byte)'1');
312
313 // TIMESTAMP
314
315 ms.WriteByte((byte)' ');
316 ms.Write(Encoding.ASCII.GetBytes(XML.Encode(Event.Timestamp.ToUniversalTime())));
317
318 // HOSTNAME
319
320 ms.WriteByte((byte)' ');
321 ms.Write(this.localHostName);
322
323 // APP-NAME
324
325 ms.WriteByte((byte)' ');
326 ms.Write(this.appName);
327
328 // PROCID
329
330 ms.WriteByte((byte)' ');
331 ms.Write(EncodeName(Process.GetCurrentProcess().Id.ToString()));
332
333 // MSGID
334
335 ms.WriteByte((byte)' ');
336 ms.Write(EncodeName(Event.EventId));
337
338 #endregion
339
340 #region STRUCTURED-DATA
341
342 ms.Write(eventType);
343 ms.Write(this.EncodeValue(Event.Type.ToString()));
344 ms.Write(eventLevel);
345 ms.Write(this.EncodeValue(Event.Level.ToString()));
346
347 if (!string.IsNullOrEmpty(Event.Object))
348 {
349 ms.Write(eventObject);
350 ms.Write(this.EncodeValue(Event.Object));
351 }
352
353 if (!string.IsNullOrEmpty(Event.Actor))
354 {
355 ms.Write(eventActor);
356 ms.Write(this.EncodeValue(Event.Actor));
357 }
358
359 if (!string.IsNullOrEmpty(Event.Facility))
360 {
361 ms.Write(eventFacility);
362 ms.Write(this.EncodeValue(Event.Facility));
363 }
364
365 if (!string.IsNullOrEmpty(Event.Module))
366 {
367 ms.Write(eventModule);
368 ms.Write(this.EncodeValue(Event.Module));
369 }
370
371 if (!string.IsNullOrEmpty(Event.StackTrace))
372 {
373 ms.Write(eventStackTrace);
374 ms.Write(this.EncodeValue(Event.StackTrace));
375 }
376
377 if ((Event.Tags?.Length ?? 0) > 0)
378 {
379 foreach (KeyValuePair<string, object> Tag in Event.Tags)
380 {
381 ms.WriteByte((byte)'"');
382 ms.WriteByte((byte)' ');
383 ms.Write(EncodeName(Tag.Key));
384 ms.WriteByte((byte)'=');
385 ms.WriteByte((byte)'"');
386 ms.Write(this.EncodeValue(Tag.Value?.ToString() ?? string.Empty));
387 }
388 }
389
390 ms.WriteByte((byte)'"');
391 ms.WriteByte((byte)']');
392
393 #endregion
394
395 ms.WriteByte((byte)' ');
396 ms.Write(this.EncodeValue(Event.Message));
397
398 ms.WriteByte((byte)'\r');
399 ms.WriteByte((byte)'\n');
400
401 return ms.ToArray();
402 }
403
404 private static byte[] EncodeName(string s)
405 {
406 if (string.IsNullOrEmpty(s))
407 return nil;
408 else
409 return Encoding.ASCII.GetBytes(s.Replace(' ', '_'));
410 }
411
412 private byte[] EncodeValue(string s)
413 {
414 s = s.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("]", "\\]");
415
416 switch (this.separation)
417 {
418 case SyslogEventSeparation.CrLf:
419 case SyslogEventSeparation.Lf:
420 s = s.Replace("\r", "<CR>").Replace("\n", "<LF>");
421 break;
422 }
423
424 return Strings.Utf8WithBom.GetBytes(s);
425 }
426
430 [Obsolete("Use DisposeAsync instead.")]
431 public void Dispose()
432 {
433 this.DisposeAsync().Wait();
434 }
435
439 public async Task DisposeAsync()
440 {
441 if (!(this.client is null))
442 {
443 await this.client.DisposeAsync();
444 this.client = null;
445 }
446 }
447
448 private static readonly byte[] nil = new byte[] { (byte)'-' };
449 private static readonly byte[] eventType = Encoding.ASCII.GetBytes(" [Event Type=\"");
450 private static readonly byte[] eventLevel = Encoding.ASCII.GetBytes("\" Level=\"");
451 private static readonly byte[] eventObject = Encoding.ASCII.GetBytes("\" Object=\"");
452 private static readonly byte[] eventActor = Encoding.ASCII.GetBytes("\" Actor=\"");
453 private static readonly byte[] eventFacility = Encoding.ASCII.GetBytes("\" Facility=\"");
454 private static readonly byte[] eventModule = Encoding.ASCII.GetBytes("\" Module=\"");
455 private static readonly byte[] eventStackTrace = Encoding.ASCII.GetBytes("\" StackTrace=\"");
456
461 public void UpdateCertificate(X509Certificate Certificate)
462 {
463 this.clientCertificate = Certificate;
464 }
465 }
466}
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
Class representing an event.
Definition: Event.cs:11
string Message
Free-text event message.
Definition: Event.cs:132
EventType Type
Type of event.
Definition: Event.cs:122
string Object
Object related to the event.
Definition: Event.cs:137
EventLevel Level
Event Level.
Definition: Event.cs:127
string Actor
Actor responsible for the action causing the event.
Definition: Event.cs:142
string Module
Module where the event is reported.
Definition: Event.cs:157
DateTime Timestamp
Timestamp of event.
Definition: Event.cs:117
KeyValuePair< string, object >[] Tags
Variable set of tags providing event-specific information.
Definition: Event.cs:167
string EventId
Computer-readable Event ID identifying type of even.
Definition: Event.cs:147
string Facility
Facility can be either a facility in the network sense or in the system sense.
Definition: Event.cs:152
string StackTrace
Stack Trace of event.
Definition: Event.cs:162
Syslog over TCP/IP client application, as defined in RFCs 5224, 5425, 6587: https://datatracker....
Definition: SyslogClient.cs:25
const int DefaultTlsPort
Default Syslog over TLS over TCP/IP port number (6514).
Definition: SyslogClient.cs:34
async Task Send(Event Event, bool WaitSent)
Sends an event to the syslog server.
int Port
Port number of the Syslog server.
const int DefaultPort
Default Syslog over TCP/IP port number (514).
Definition: SyslogClient.cs:29
Task Send(Event Event)
Sends an event to the syslog server.
string Host
Host name or address of the Syslog server.
Definition: SyslogClient.cs:95
void Dispose()
Disposes the syslog client.
void UpdateCertificate(X509Certificate Certificate)
Updates the certificate used in mTLS negotiation.
SyslogClient(string Host, int Port, bool Tls, string LocalHostName, string AppName, SyslogEventSeparation Separation, params ISniffer[] Sniffers)
Syslog over TCP/IP client application, as defined in RFCs 5224, 5425, 6587: https://datatracker....
Definition: SyslogClient.cs:58
SyslogClient(string Host, int Port, X509Certificate ClientCertificate, string LocalHostName, string AppName, SyslogEventSeparation Separation, params ISniffer[] Sniffers)
Syslog over TCP/IP client application, as defined in RFCs 5224, 5425, 6587: https://datatracker....
Definition: SyslogClient.cs:84
byte[] CreateMessage(Event Event)
Creates a binary Syslog message from an event.
async Task DisposeAsync()
Disposes the syslog client.
SyslogEventSeparation Separation
How events are separated in the event stream.
Implements a binary TCP Client, by encapsulating a TcpClient. It also makes the use of TcpClient safe...
Task UpgradeToTlsAsClient(SslProtocols Protocols)
Upgrades a client connection to TLS.
bool Connected
If the connection is open.
Task< bool > SendAsync(byte[] Packet)
Sends a binary packet.
void Continue()
Continues reading from the socket, if paused in an event handler.
virtual Task DisposeAsync()
Disposes of the object asynchronously. The underlying TcpClient is either disposed directly,...
Task< bool > ConnectAsync(string Host, int Port)
Connects to a host using TCP.
Simple base class for classes implementing communication protocols.
void TransmitText(string Text)
Called when text has been transmitted.
void Exception(Exception Exception)
Called to inform the viewer of an exception state.
void ReceiveText(string Text)
Called when text has been received.
ISniffer[] Sniffers
Registered sniffers.
bool HasSniffers
If there are sniffers registered on the object.
void Error(string Error)
Called to inform the viewer of an error state.
void Warning(string Warning)
Called to inform the viewer of a warning state.
void Information(string Comment)
Called to inform the viewer of something.
Static class managing binary representations of strings.
Definition: Strings.cs:10
static Encoding Utf8WithBom
UTF-8 encoding with Byte Order Mark (BOM)
Definition: Strings.cs:231
Interface for asynchronously disposable objects.
Interface for sniffers. Sniffers can be added to ICommunicationLayer classes to eavesdrop on communic...
Definition: ISniffer.cs:10
Interface for Mutual TLS (mTLS) Clients or TLS servers.
Definition: ImplTypes.g.cs:58
SyslogEventSeparation
How events are separated in the Syslog event stream.