XmppServerConnection.cs 15.9 KB
Newer Older
Alex's avatar
Alex committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
using System;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;
using System.Net.Sockets;
using Matrix.Xmpp.Bind;
using Matrix.Xmpp.Client;
using Matrix.Xmpp.Roster;
using Matrix.Xmpp.Sasl;
using Matrix.Xmpp.Session;
using Matrix.Xmpp.Stream;
using Matrix.Xml;
using Matrix.Xmpp;
using Matrix;

using Iq = Matrix.Xmpp.Base.Iq;
using Message = Matrix.Xmpp.Base.Message;
using MxAuth = Matrix.Xmpp.Sasl.Auth;
using Presence = Matrix.Xmpp.Base.Presence;
using Stream = Matrix.Xmpp.Client.Stream;

namespace Server
{
	/// <summary>
	/// XMPPSeverConnection class.
	/// </summary>
	public class XmppSeverConnection
	{
		#region << Constructors >>
		public XmppSeverConnection()
		{
		    SessionId = null;
		    streamParser = new XmppStreamParser();
            
            streamParser.OnStreamStart += streamParser_OnStreamStart;
            streamParser.OnStreamEnd += streamParser_OnStreamEnd;
            streamParser.OnStreamElement += streamParser_OnStreamElement;
		}

		public XmppSeverConnection(Socket sock) : this()
		{	
			m_Sock = sock;
			m_Sock.BeginReceive(buffer, 0, BUFFERSIZE, 0, ReadCallback, null);		
		}
		#endregion
       
        private XmppStreamParser			streamParser;
		private Socket					m_Sock;
        private const int BUFFERSIZE = 1024;
        private byte[] buffer = new byte[BUFFERSIZE];

        // Jid binded to this connection
	    public Jid Jid;
	    private bool InitialPresence;
	    private Presence LastPresence;
        private bool streamFooterSent;

	    public void ReadCallback(IAsyncResult ar)
	    {
	        // Retrieve the state object and the handler socket
	        // from the asynchronous state object

	        // Read data from the client socket. 
	        try
	        {
	            int bytesRead = m_Sock.EndReceive(ar);

	            if (bytesRead > 0)
	            {
	                //streamParser.Push(buffer, 0, bytesRead);
	                streamParser.Write(buffer, 0, bytesRead);
	                // Not all data received. Get more.
	                m_Sock.BeginReceive(buffer, 0, BUFFERSIZE, 0, ReadCallback, null);
	            }
	            else
	                Disconnect();
	        }
	        catch (Exception)
	        {
	            Disconnect();
	        }
	    }

	    /// <summary>
        /// Disconnect socket
        /// </summary>
        public void Disconnect()
        {
            Debug.WriteLine("Socket Disconnect");

	        if (!streamFooterSent && m_Sock.Connected)
	        {
                Send("</stream:stream>");
                streamFooterSent = true;    
	        }

            // return right away if have not created socket
            if (m_Sock == null)
                return;

            try
            {
                // first, shutdown the socket (when connected
                if (m_Sock.Connected)
                    m_Sock.Shutdown(SocketShutdown.Both);
            }
            catch (Exception)
            {
            }
            try
            {
                // next, close the socket which terminates any pending
                // async operations
                m_Sock.Close();
            }
            catch (Exception)
            {
            }

            if (Global.ServerConnections.Contains(this))
                Global.ServerConnections.Remove(this);
        }

		private void Send(string data) 
		{
			// Convert the string data to byte data using ASCII encoding.
			byte[] byteData = Encoding.UTF8.GetBytes(data);

			// Begin sending the data to the remote device.
			m_Sock.BeginSend(byteData, 0, byteData.Length, 0, SendCallback, null);
		}

		private void SendCallback(IAsyncResult ar) 
		{
			try 
			{
				// Complete sending the data to the remote device.
				int bytesSent = m_Sock.EndSend(ar);
				Console.WriteLine("Sent {0} bytes to client.", bytesSent);

			} 
			catch (Exception e) 
			{
				Console.WriteLine(e.ToString());
			}
		}
	
		
		public void Stop()
		{
		    Disconnect();
		}
			
		
		#region << Properties and Member Variables >>
	    public const string XmppDomain = "localhost";
        public string   User            { get; set; }
        public string   Resource        { get; set; }
	    public string   SessionId       { get; set; }
        public bool     IsAuthenticated { get; set; }
        public bool     IsBinded        { get; set; }
        
	    #endregion

        #region << StreamParser events >>
        void streamParser_OnStreamEnd(object sender, Matrix.EventArgs e)
        {
            Disconnect();
        }

        void streamParser_OnStreamElement(object sender, StanzaEventArgs e)
        {
            Console.WriteLine("OnStreamElement: " + e);

            if (e.Stanza is Presence)
            {
                ProcessPresence(e.Stanza as Presence);
            }
            else if (e.Stanza  is Message)
            {
                ProcessMessage(e.Stanza as Message);
            }
            else if (e.Stanza is Iq)
            {
                ProcessIq(e.Stanza as Iq);
            }

            if (e.Stanza is MxAuth)
            {
                var auth = e.Stanza as MxAuth;
Alex's avatar
Alex committed
192
                if (auth.SaslMechanism == SaslMechanism.Plain)
Alex's avatar
Alex committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
                    ProcessSaslPlainAuth(auth);
            }
        }

	    void streamParser_OnStreamStart(object sender, StanzaEventArgs e)
        {
            SendStreamHeader();
            Send(BuildStreamFeatures());
        }
        #endregion

        private void ProcessMessage(Message msg)
        {
            var to = msg.To;

            // check if the destination of this message is available
            var con = Global.ServerConnections.FirstOrDefault(c => c.Jid.Equals(to, new BareJidComparer()));
            if (con != null)
            {
                // found connection, stamp packet with from and route it.
                msg.From = Jid;
                con.Send(msg);
            }
            else
            {
                // connection not found. Return the message to the sender and stamp it with error
Alex's avatar
Alex committed
219
                msg.Type = MessageType.Error;
Alex's avatar
Alex committed
220
221
222
223
224
225
226
227
228
229
                msg.To = Jid;
                msg.From = to;
                msg.Add(new Matrix.Xmpp.Client.Error(Matrix.Xmpp.Base.ErrorCondition.ServiceUnavailable));
                Send(msg);
            }
        }

	    private void ProcessPresence(Presence pres)
	    {

Alex's avatar
Alex committed
230
            if (pres.Type == PresenceType.Subscribe)
Alex's avatar
Alex committed
231
232
233
            {
                // RFC 3.1.2.  Server Processing of Outbound Subscription Request
            }
Alex's avatar
Alex committed
234
            else if (pres.Type == PresenceType.Subscribed)
Alex's avatar
Alex committed
235
236
237
            {
                // RFC 3.1.5.  Server Processing of Outbound Subscription Approval
            }
Alex's avatar
Alex committed
238
            else if (pres.Type == PresenceType.Unsubscribed)
Alex's avatar
Alex committed
239
240
241
            {
                // RFC 3.2.2.  Server Processing of Outbound Subscription Cancellation
            }
Alex's avatar
Alex committed
242
            else if (pres.Type == PresenceType.Unsubscribe)
Alex's avatar
Alex committed
243
244
245
            {
                // RFC 3.3.2.  Server Processing of Outbound Unsubscribe
            }
Alex's avatar
Alex committed
246
247
            else if (pres.Type == PresenceType.Available
                    || pres.Type == PresenceType.Unavailable)
Alex's avatar
Alex committed
248
249
250
251
252
253
254
255
256
257
            {
                ProcessOutboundPresence(pres);
            }
	    }

        private void ProcessOutboundPresence(Presence pres)
        {
            if (pres.To == null || pres.To.Equals(XmppDomain, new FullJidComparer()))
            {
                // a presence to the server
Alex's avatar
Alex committed
258
                if (pres.Type == PresenceType.Available)
Alex's avatar
Alex committed
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
                {
                    if (IsBinded && !InitialPresence)
                    {
                        InitialPresence = true;

                        // request all initial presences of the contacts
                        for (int i = 0; i < 11; i++)
                        {
                            // but not myself
                            if (Jid.User.EndsWith(i.ToString()))
                                continue;

                            var jid = new Jid("user" + i + "@" + XmppDomain);
                            var con = Global.ServerConnections.FirstOrDefault(sc => sc.Jid.Equals(jid, new BareJidComparer()));
                            if (con != null && con.LastPresence != null)
                                Send(con.LastPresence);
                        }
                    }
                }
Alex's avatar
Alex committed
278
                else if (pres.Type == PresenceType.Unavailable)
Alex's avatar
Alex committed
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
                {
                }

                // distribute presence
                // to all own resources, 
                pres.From = Jid;
                pres.To = null;
                LastPresence = pres;
                

                /*
                    then to all subscribed contacts
                    4.2.2.  Server Processing of Outbound Initial Presence

                    Upon receiving initial presence from a client, the user's server MUST send the initial presence stanza from the
                    full JID <user@domainpart/resourcepart> of the user to all contacts that are subscribed to the user's presence;
                    such contacts are those for which a JID is present in the user's roster with the 'subscription' attribute set to
                    a value of "from" or "both". 
                */

                
                for (int i = 0; i < 11; i++)
                {
                    // but not myself
                    if (Jid.User.EndsWith(i.ToString()))
                        continue;

                    var jid = new Jid("user" + i + "@" + XmppDomain);
                    var con = Global.ServerConnections.FirstOrDefault(sc => sc.Jid.Equals(jid, new BareJidComparer()));
                    if (con != null)
                        con.Send(pres);
                }

                // Send the presence to all my own connected resources (sessions)
                Global.ServerConnections
                    .Where(sc => sc.Jid.Equals(Jid, new BareJidComparer()))
                    .ToList()
                    .ForEach(
                        c => c.Send(pres)
                        );
            }
        }

	    private void ProcessIq(Iq iq)
		{
            if (iq.Query is Roster)
                ProcessRosterIq(iq);
            else if (iq.Query is Bind)
                ProcessBind(iq);
            else if (iq.Query is Session)
                ProcessSession(iq);
            else if (iq.To != null && !iq.To.Equals(XmppDomain, new FullJidComparer()))
                RouteIq(iq);
            else
            {
Alex's avatar
Alex committed
334
335
336
337
338
339
340
341
                // something we don't understand or do not support, reply with error
                Send(
                    new Matrix.Xmpp.Client.Iq()
                    {
                        Type = IqType.Error,
                        Id = iq.Id,
                        Error = new Matrix.Xmpp.Client.Error(Matrix.Xmpp.Base.ErrorCondition.FeatureNotImplemented)
                    });
Alex's avatar
Alex committed
342
343
344
345
346
            }
		}

		private void ProcessRosterIq(Iq iq)
		{
Alex's avatar
Alex committed
347
			if (iq.Type == IqType.Get)
Alex's avatar
Alex committed
348
349
350
351
352
			{
				// Send the roster
				// we send a dummy roster here, you should retrieve it from a
				// database or some kind of directory (LDAP, AD etc...)
				iq.SwitchDirection();
Alex's avatar
Alex committed
353
				iq.Type = IqType.Result;
Alex's avatar
Alex committed
354
355
356
357
358
359
360
361
362
363
				for (int i = 1; i < 11;i++)
				{
                    // don't add yourself to the contact list (aka roster)
				    if (Jid.User.EndsWith(i.ToString()))
				        continue;

				    var ri = new RosterItem
				    {
				        Jid = new Jid("user" + i + "@" + XmppDomain),
				        Name = "User " + i,
Alex's avatar
Alex committed
364
				        Subscription = Subscription.Both
Alex's avatar
Alex committed
365
366
367
368
369
370
				    };
				    ri.AddGroup("Group 1");
					iq.Query.Add(ri);
				}
				Send(iq);
			}
Alex's avatar
Alex committed
371
            else if (iq.Type == IqType.Set)
Alex's avatar
Alex committed
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
            {
                // TODO, handle roster add, remove and update here.
            }
		}

        private void ProcessSaslPlainAuth(MxAuth auth)
        {
            string pass = null;
            string user = null;

            byte[] bytes = Convert.FromBase64String(auth.Value);
            string sasl = Encoding.UTF8.GetString(bytes);
            // trim nullchars
            sasl = sasl.Trim((char) 0);
            string[] split = sasl.Split((char) 0);

            if (split.Length == 3)
            {
                user = split[1];
                pass = split[2];
            }
            else if (split.Length == 2)
            {
                user = split[0];
                pass = split[1];
            }
         
            // here you should get the password from youdatabase or auth provider
            const string dbPass = "secret";
            
            // check if username and password is correct
            if (user != null
                && Regex.IsMatch(user, "user([0-9]|10)$")
                && pass == dbPass)
            {
                // pass correct
                User = user;
                streamParser.Reset();
                IsAuthenticated = true;
                Send(new Success());
            }
            else
            {
                {
                    // user does not exist or wrong password
Alex's avatar
Alex committed
417
                    Send(new Failure(FailureCondition.NotAuthorized));
Alex's avatar
Alex committed
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
                }
            }
        }

        private void ProcessBind(Iq iq)
        {
            var bind = iq.Query as Bind;

            string res = bind.Resource;
            if (!String.IsNullOrEmpty(res))
            {
                var jid = new Jid(User, XmppDomain, res);
                Jid = jid;
                var resIq = new BindIq
                {
                    Id = iq.Id,
Alex's avatar
Alex committed
434
                    Type = IqType.Result,
Alex's avatar
Alex committed
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
                    Bind = { Jid = jid }
                };

                Send(resIq);
                Resource = res;
                IsBinded = true;

                // connection is bindet now. Add it to our global list of connection.
                Global.ServerConnections.Add(this);
            }
            else
            {
                // return error
            }

        }

        private void ProcessSession(Iq iq)
        {
            /*            
                <iq type="set" id="aabca" >
                    <session xmlns="urn:ietf:params:xml:ns:xmpp-session"/>
                </iq>            
             */
Alex's avatar
Alex committed
459
460
            if (iq.Type == IqType.Set)
                Send(new SessionIq { Id = iq.Id, Type = IqType.Result });
Alex's avatar
Alex committed
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
        }

	    private void RouteIq(Iq iq)
	    {
            // route the iq here
            var to = iq.To;

            // check if the destination of this message is available
            var con = Global.ServerConnections.FirstOrDefault(c => c.Jid.Equals(to, new BareJidComparer()));
            if (con != null)
            {
                // found connection, stamp packet with from and route it.
                iq.From = Jid;
                con.Send(iq);
            }
            else
            {
                // connection not found. Return the message to the sender and stamp it with error
Alex's avatar
Alex committed
479
                iq.Type = IqType.Error;
Alex's avatar
Alex committed
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
                iq.To = Jid;
                iq.From = to;
                iq.Add(new Matrix.Xmpp.Client.Error(Matrix.Xmpp.Base.ErrorCondition.ServiceUnavailable));
                Send(iq);
            }
	    }
    
        /// <summary>
        /// sends the XMPP stream header
        /// </summary>
		private void SendStreamHeader()
		{
            var stream = new Stream
            {
                Version = "1.0",
                From = XmppDomain,
                Id = Guid.NewGuid().ToString()
            };

            Send(stream.StartTag());
		}

        private XmppXElement BuildStreamFeatures()
        {
            var feat = new StreamFeatures();
            //feat.Add(new StartTls());
            
            if (!IsAuthenticated)
            {
                var mechs = new Mechanisms();
Alex's avatar
Alex committed
510
                mechs.AddMechanism(SaslMechanism.Plain);
Alex's avatar
Alex committed
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
                feat.Mechanisms = mechs;
            }
            else if (!IsBinded && IsAuthenticated)
            {
                feat.Add(new Bind());
            }

            return feat;
        }

	    private void Send(XmppXElement el)
		{
			Send(el.ToString(false));
		}
	}
}