1 /**
2 * dconnection
3 *
4 * Client/server connection handler spawned
5 * by socket connection dequeue loop.
6 *
7 * Handles all interactions between
8 * the server and the specific client/server.
9 */
10 
11 module dnetd.dconnection;
12 
13 import core.thread : Thread;
14 import std.socket : Socket;
15 import bmessage;
16 import tristanable.encoding : DataMessage;
17 import core.sync.mutex : Mutex;
18 import dnetd.dserver : DServer;
19 import std.string : split;
20 import dnetd.dchannel : DChannel;
21 import std.conv : to;
22 import std.stdio : writeln;
23 import std.algorithm : reverse;
24 import gogga;
25 
26 public class DConnection : Thread
27 {
28 	/* The connection type */
29 	public enum ConnectionType
30 	{
31 		CLIENT, SERVER, UNSPEC
32 	}
33 
34 	/* Command types */
35 	public enum Command
36 	{
37 		JOIN,
38 		PART,
39 		AUTH,
40 		LINK,
41 		REGISTER,
42 		LIST,
43 		MSG,
44 		MEMBER_COUNT,
45 		MEMBER_LIST,
46 		SERVER_INFO,
47 		MOTD,
48 		MEMBER_INFO,
49 		STATUS,
50 		CHAN_PROP,
51 		SET_PROP,
52 
53 
54 		/* User property commands */
55 		GET_USER_PROPS,
56 		GET_USER_PROP,
57 		SET_USER_PROP,
58 		DELETE_USER_PROP,
59 		IS_USER_PROP,
60 
61 
62 		UNKNOWN
63 	}
64 
65 	/**
66 	* Connection information
67 	*/
68 	private DServer server;
69 	private Socket socket;
70 	private bool hasAuthed;
71 	private ConnectionType connType;
72 	private string username;
73 	private string currentStatus;
74 
75 	/**
76 	* User property support
77 	*
78 	* `properties` -  the property store
79 	* `propertiesMutex` - the mutex
80 	*/
81 	private string[string] properties; /* TODO: New, replace old status mechanism */
82 	private Mutex propertiesLock;
83 
84 	/* Write lock for socket */
85 	/* TODO: Forgot how bmessage works, might need, might not, if multipel calls
86 	* then yes, if single then no as it is based off (well glibc's write)
87 	* thread safe code
88 	*/
89 	private Mutex writeLock;
90 
91 	/**
92 	* Mutex to provide safe access to the status message
93 	*/
94 	private Mutex statusMessageLock;
95 
96 	this(DServer server, Socket socket)
97 	{
98 		/* Set the function to be called on thread start */
99 		super(&worker);
100 
101 		/* Set the associated server */
102 		this.server = server;
103 
104 		/* Set the socket */
105 		this.socket = socket;
106 
107 		/* Set the default state */
108 		connType = ConnectionType.UNSPEC;
109 
110 		/* Initialize locks */
111 		initLocks();
112 
113 		/* Initialize status (TODO: Remove, we use props now) */
114 		currentStatus = "available,Hey there I'm using DNET!";
115 
116 		/* Start the connection handler */
117 		start();
118 	}
119 
120 	/**
121 	* Initializes mutexes
122 	*/
123 	private void initLocks()
124 	{
125 		/* Initialize the socket write lock */
126 		writeLock = new Mutex();
127 
128 		/* Initialize the status message lock */
129 		statusMessageLock = new Mutex();
130 
131 		/* Initialize the properties lock */
132 		propertiesLock = new Mutex();
133 	}
134 
135 	/**
136 	* Byte dequeue loop
137 	*/
138 	private void worker()
139 	{
140 		/* Received bytes (for bformat) */
141 		byte[] receivedBytes;
142 
143 		/* Received message */
144 		DataMessage receivedMessage;
145 
146 		while(true)
147 		{		
148 			/**
149 			* Block to receive a bformat message
150 			*
151 			* (Does decoding for bformat too)
152 			*/
153 			bool status = receiveMessage(socket, receivedBytes);
154 
155 			/* TODO: Check status */
156 			if(status)
157 			{
158 				/* Decode the tristanable message (tagged message) */
159 				receivedMessage = DataMessage.decode(receivedBytes);
160 
161 				/* Process the message */
162 				process(receivedMessage);
163 			}
164 			else
165 			{
166 				/* TODO: Error handling */
167 				gprintln("Error with receive: "~to!(string)(this), DebugType.ERROR);
168 				break;
169 			}
170 		}
171 
172 		/* Clean up */
173 		cleanUp();
174 	}
175 
176 	private void cleanUp()
177 	{
178 		gprintln(to!(string)(this)~" Cleaning up connection...");
179 
180 		/* Remove this user from all channels he is in */
181 		DChannel[] channels = server.getChannels();
182 
183 		/* Loop through each channel */
184 		foreach(DChannel currentChannel; channels)
185 		{
186 			/* Check if you are a member of it */
187 			if(currentChannel.isMember(this))
188 			{
189 				/* Leave the channel */
190 				currentChannel.leave(this);
191 				gprintln(to!(string)(this)~" Leaving '"~currentChannel.getName()~"'...");
192 			}
193 		}
194 		
195 		/* Remove this user from the connection queue */
196 		server.removeConnection(this);
197 
198 		gprintln(to!(string)(this)~" Connection cleaned up");
199 	}
200 
201 	/* TODO: add mutex for writing with message and funciton for doing so */
202 
203 	/**
204 	* Write to socket
205 	*
206 	* Encodes the byte array as a tristanable tagged
207 	* message and then encodes that as a bformat
208 	* message
209 	*
210 	* Locks the writeLock mutex, sends it over the
211 	* socket to the client/server, and unlocks the
212 	* mutex
213 	*/
214 	public bool writeSocket(long tag, byte[] data)
215 	{
216 		/* Send status */
217 		bool status;
218 
219 		/* Create the tagged message */
220 		DataMessage message = new DataMessage(tag, data);
221 
222 		gprintln("writeSocket: mutex lock");
223 		/* Lock the write mutex */
224 		writeLock.lock();
225 
226 		/* Send the message */
227 		gprintln("writeSocket: Data: "~to!(string)(data)~" Tag: "~to!(string)(tag));
228 		status = sendMessage(socket, message.encode());
229 
230 		/* Unlock the write mutex */
231 		writeLock.unlock();
232 
233 		gprintln("writeSocket: mutex unlock");
234 
235 		return status;
236 	}
237 
238 	private Command getCommand(ubyte commandByte)
239 	{
240 		Command command = Command.UNKNOWN;
241 		
242 		if(commandByte == 0)
243 		{
244 			command	= Command.AUTH;
245 		}
246 		else if(commandByte == 1)
247 		{
248 			command = Command.LINK;
249 		}
250 		else if(commandByte == 2)
251 		{
252 			command = Command.REGISTER;
253 		}
254 		else if(commandByte == 3)
255 		{
256 			command = Command.JOIN;
257 		}
258 		else if(commandByte == 4)
259 		{
260 			command = Command.PART;
261 		}
262 		else if(commandByte == 5)
263 		{
264 			command = Command.MSG;
265 		}
266 		else if(commandByte == 6)
267 		{
268 			command = Command.LIST;
269 		}
270 		else if(commandByte == 7)
271 		{
272 			command = Command.MSG;
273 		}
274 		else if(commandByte == 8)
275 		{
276 			command = Command.MEMBER_COUNT;
277 		}
278 		else if(commandByte == 9)
279 		{
280 			command = Command.MEMBER_LIST;
281 		}
282 		else if(commandByte == 10)
283 		{
284 			command = Command.SERVER_INFO;
285 		}
286 		else if(commandByte == 11)
287 		{
288 			command = Command.MOTD;
289 		}
290 		else if(commandByte == 12)
291 		{
292 			command = Command.MEMBER_INFO;
293 		}
294 		else if(commandByte == 13)
295 		{
296 			command = Command.STATUS;
297 		}
298 		else if(commandByte == 14)
299 		{
300 			command = Command.CHAN_PROP;
301 		}
302 		else if(commandByte == 15)
303 		{
304 			command = Command.GET_USER_PROPS;
305 		}
306 		else if(commandByte == 16)
307 		{
308 			command = Command.GET_USER_PROP;
309 		}
310 		else if(commandByte == 17)
311 		{
312 			command = Command.SET_USER_PROP;
313 		}
314 		else if(commandByte == 18)
315 		{
316 			command = Command.DELETE_USER_PROP;
317 		}
318 		else if(commandByte == 19)
319 		{
320 			command = Command.IS_USER_PROP;
321 		}
322 		
323 		
324 		
325 		
326 		return command;
327 	}
328 
329 	/**
330 	* Process the received message
331 	*/
332 	private void process(DataMessage message)
333 	{
334 		/* Get the tag */
335 		/**
336 		* TODO: Client side will always do 1, because we don't have
337 		* multi-thread job processing, only need this to differentiate
338 		* between commands and async notifications
339 		*/
340 		long tag = message.tag;
341 		gprintln("tag: "~ to!(string)(tag));
342 
343 		/* The reply */
344 		byte[] reply;
345 
346 		/* Get the command */
347 		byte commandByte = message.data[0];
348 		Command command = getCommand(commandByte);
349 		gprintln(to!(string)(this)~" ~> "~to!(string)(command));
350 
351 		/* If `auth` command (requires: unauthed) */
352 		if(command == Command.AUTH && !hasAuthed)
353 		{
354 			/* Get the length of the username */
355 			ubyte usernameLength = message.data[1];
356 
357 			/* Get the username and password */
358 			string username = cast(string)message.data[2..2+cast(uint)usernameLength];
359 			string password = cast(string)message.data[2+cast(uint)usernameLength..message.data.length];
360 
361 			/* Authenticate */
362 			bool status = authenticate(username, password);
363 
364 			/* TODO: What to do on bad authetication? */
365 
366 			/* Set the username */
367 			this.username = username;
368 
369 			/* Set the type of this connection to `client` */
370 			connType = ConnectionType.CLIENT;
371 			hasAuthed = true;
372 
373 			/* Encode the reply */
374 			reply = [status];
375 		}
376 		/* If `link` command (requires: unauthed) */
377 		else if(command == Command.LINK && !hasAuthed)
378 		{
379 			/* TODO: Implement me later */
380 
381 			/* Check if this connection is a DLink'd one */
382 			//server.getMeyer().get
383 
384 
385 			/* Set the type of this connection to `server` */
386 			connType = ConnectionType.SERVER;
387 			hasAuthed = true;
388 		}
389 		/* If `register` command (requires: unauthed, client) */
390 		else if(command == Command.REGISTER && !hasAuthed && connType == ConnectionType.CLIENT)
391 		{
392 			
393 		}
394 		/* If `join` command (requires: authed, client) */
395 		else if(command == Command.JOIN && hasAuthed && connType == ConnectionType.CLIENT)
396 		{
397 			/* Get the channel names */
398 			string channelList = cast(string)message.data[1..message.data.length];
399 			string[] channels = split(channelList, ",");
400 
401 			/**
402 			* Loop through each channel, check if it
403 			* exists, if so join it, else create it
404 			* and then join it
405 			*/
406 			bool isPresentInfo = false;
407 			foreach(string channelName; channels)
408 			{
409 				/**
410 				* Finds the channel, if it exists then it returns it,
411 				* if it does not exist then it will create it and then
412 				* return it
413 				*/
414 				DChannel channel = server.getChannel(this, channelName);
415 
416 				/* Join the channel */
417 				isPresentInfo = channel.join(this);
418 			}
419 
420 			/* TODO: Do reply */
421 			/* Encode the reply */
422 			reply = [isPresentInfo];
423 		}
424 		/* If `part` command (requires: authed, client) */
425 		else if(command == Command.PART && hasAuthed && connType == ConnectionType.CLIENT)
426 		{
427 			/* Get the channel names */
428 			string channelList = cast(string)message.data[1..message.data.length];
429 			string[] channels = split(channelList, ",");
430 
431 			/**
432 			* Loop through each channel, check if it
433 			* exists, if so leave it
434 			*/
435 			foreach(string channelName; channels)
436 			{
437 				/* Attempt to find the channel */
438 				DChannel channel = server.getChannelByName(channelName);
439 
440 				/* Leave a channel the channel only if it exists */
441 				if(!(channel is null))
442 				{
443 					channel.leave(this);
444 				}
445 			}
446 
447 			/* TODO: Do reply */
448 			/* Encode the reply */
449 			reply = [true];
450 		}
451 		/* If `list` command (requires: authed, client) */
452 		else if(command == Command.LIST && hasAuthed && connType == ConnectionType.CLIENT)
453 		{
454 			/* Get all channels */
455 			DChannel[] channels = server.getChannels();
456 
457 			/* Generate a list of channel names (CSV) */
458 			string channelList;
459 			for(ulong i = 0; i < channels.length; i++)
460 			{
461 				if(i == channels.length-1)
462 				{
463 					channelList ~= channels[i].getName();
464 				}
465 				else
466 				{
467 					channelList ~= channels[i].getName()~",";
468 				}
469 			}
470 
471 			/* TODO: Reply */
472 			/* Encode the reply */
473 			reply = [true];
474 			reply ~= channelList;
475 		}
476 		/* If `msg` command (requires: authed, client) */
477 		else if(command == Command.MSG && hasAuthed && connType == ConnectionType.CLIENT)
478 		{
479 			/* Status */
480 			bool status = true;
481 
482 			/* Get the type of message */
483 			ubyte messageType = message.data[1];
484 
485 			/* Get the location length */
486 			ubyte locationLength = message.data[2];
487 
488 			/* Get the channel/person name */
489 			string destination = cast(string)message.data[3..3+cast(uint)locationLength];
490 
491 			/* Get the message */
492 			string msg = cast(string)message.data[3+cast(uint)locationLength..message.data.length];
493 
494 			/* Send status */
495 			bool sendStatus;
496 
497 			/* If we are sending to a user */
498 			if(messageType == 0)
499 			{
500 				/* Send the message to the user */
501 				sendStatus = sendUserMessage(destination, msg);
502 			}
503 			/* If we are sending to a channel */
504 			else if(messageType == 1)
505 			{
506 				/* The channel wanting to send to */
507 				DChannel channel = server.getChannelByName(destination);
508 
509 				/* If the channel exists */
510 				if(channel)
511 				{
512 					/* TODO Implemet  me */
513 					sendStatus = channel.sendMessage(this, msg);
514 				}
515 				/* If the channel does not exist */
516 				else
517 				{
518 					status = false;
519 				}
520 			}
521 			/* Unknown destination type */
522 			else
523 			{
524 				status = false;
525 			}
526 			
527 			/* TODO: Handling here, should we make the user wait? */
528 
529 			/* Encode the reply */
530 			/* TODO: */
531 			reply = [status];
532 		}
533 		/* If `membercount` command (requires: authed, client) */
534 		else if(command == Command.MEMBER_COUNT && hasAuthed && connType == ConnectionType.CLIENT)
535 		{
536 			/* Status */
537 			bool status = true;
538 
539 			/* Get the channel name */
540 			string channelName = cast(string)message.data[1..message.data.length];
541 
542 			/* The memebr count */
543 			long memberCount;
544 
545 			/* Get the member count */
546 			status = getMemberCount(channelName, memberCount);
547 
548 			/* Encode the status */
549 			reply = [status];
550 
551 			/* If there was no error fetching the member count */
552 			if(status)
553 			{
554 				/* Data bytes */
555 				byte[] numberBytes;
556 				numberBytes.length = 8;
557 
558 				/* Encode the length (Big Endian) from Little Endian */
559 				numberBytes[0] = *((cast(byte*)&memberCount)+7);
560 				numberBytes[1] = *((cast(byte*)&memberCount)+6);
561 				numberBytes[2] = *((cast(byte*)&memberCount)+5);
562 				numberBytes[3] = *((cast(byte*)&memberCount)+4);
563 				numberBytes[4] = *((cast(byte*)&memberCount)+3);
564 				numberBytes[5] = *((cast(byte*)&memberCount)+2);
565 				numberBytes[6] = *((cast(byte*)&memberCount)+1);
566 				numberBytes[7] = *((cast(byte*)&memberCount)+0);
567 
568 				/* Append the length */
569 				reply ~= numberBytes;
570 			}
571 		}
572 		/* If `memberlist` command (requires: authed, client) */
573 		else if(command == Command.MEMBER_LIST && hasAuthed && connType == ConnectionType.CLIENT)
574 		{
575 			/* Status */
576 			bool status = true;
577 
578 			/* Get the channel name */
579 			string channelName = cast(string)message.data[1..message.data.length];
580 
581 			/* Get the channel */
582 			DChannel channel = server.getChannelByName(channelName);
583 
584 			/* Encode the status */
585 			reply ~= [channel !is null];
586 
587 			/* If the channel exists */
588 			if(channel)
589 			{
590 				/* Get the list of members in the channel */
591 				DConnection[] members = channel.getMembers();
592 
593 				/* Construct a CSV string of the members */
594 				string memberString;
595 
596 				for(ulong i = 0; i < members.length; i++)
597 				{
598 					if(i == members.length-1)
599 					{
600 						memberString ~= members[i].getUsername();
601 					}
602 					else
603 					{
604 						memberString ~= members[i].getUsername()~",";
605 					}
606 				}
607 
608 				/* Encode the string into the reply */
609 				reply ~= cast(byte[])memberString;
610 			}
611 			/* If the channel does not exist */
612 			else
613 			{
614 				status = false;
615 			}
616 
617 
618 
619 
620 			
621 
622 
623 			
624 		}
625 		/* If `serverinfo` command (requires: authed, !unspec) */
626 		else if(command == Command.SERVER_INFO && hasAuthed && connType != ConnectionType.UNSPEC)
627 		{
628 			/* Status */
629 			bool status = true;
630 
631 			/* Get the server info */
632 			string serverInfo = server.getServerInfo();
633 
634 			/* Encode the reply */
635 			reply ~= [status];
636 			reply ~= serverInfo;
637 		}
638 		/* If `motd` command (requires: _nothing_) */
639 		else if(command == Command.MOTD)
640 		{
641 			/* Status */
642 			bool status = true;
643 
644 			/* Get the message of the day */
645 			string motd = server.getConfig().getGeneral().getMotd();
646 
647 			/* Encode the reply */
648 			reply ~= [status];
649 			reply ~= motd;
650 		}
651 		/* If `memberinfo` command (requires: authed, client) */
652 		else if(command == Command.MEMBER_INFO && hasAuthed && connType == ConnectionType.CLIENT)
653 		{
654 			/* Status */
655 			bool status = true;
656 
657 			/* TODO: Implement me */
658 			string user = cast(string)message.data[1..message.data.length];
659 
660 			/* TODO: fetch longontime, serveron, status */
661 			string logontime;
662 			string serveron;
663 
664 			/* Encode the reply */
665 			reply ~= [status];
666 			reply ~= [cast(byte)logontime.length];
667 			reply ~= logontime;
668 			reply ~= [cast(byte)serveron.length];
669 			reply ~= serveron;
670 			reply ~= server.getStatusMessage(user);
671 		}
672 		/* If `status` command (requires: authed, client) */
673 		else if(command == Command.STATUS && hasAuthed && connType == ConnectionType.CLIENT)
674 		{
675 			/* Get the new status line */
676 			string statusMessage = cast(string)message.data[1..message.data.length];
677 
678 			/* Set the new status message */
679 			setStatusMessage(statusMessage);
680 
681 			/* Encode the reply */
682 			reply ~= [true];
683 		}
684 
685 		/* If `get_user_props` (requires: authed, client) */
686 		else if(command == Command.GET_USER_PROPS && hasAuthed && connType == ConnectionType.CLIENT)
687 		{
688 			/* Get the username */
689 			string username = cast(string)message.data[1..message.data.length];
690 
691 			/* Get the user */
692 			DConnection connection = server.findUser(username);
693 
694 			/* Get all properties of the user */
695 			string[] propertyKeys = connection.getProperties();
696 
697 			/* Encode the status */
698 			reply ~= [true];
699 
700 			/* Encode the keys */
701 			for(ulong i = 0; i < propertyKeys.length; i++)
702 			{
703 				/* The data to add to the reply */
704 				string replyData;
705 
706 				if(i == propertyKeys.length-1)
707 				{
708 					replyData = propertyKeys[i];
709 				}
710 				else
711 				{
712 					replyData = propertyKeys[i]~",";
713 				}
714 
715 				/* Encode the `replyData` */
716 				reply ~= replyData;
717 			}
718 
719 			/* Encode the number of keys (TODO: FOr now you cannot have more than 255 keys) */
720 			// reply ~= [cast(byte)propertyKeys.length];
721 		}
722 		/* If `get_user_prop` (requires: authed, client) */
723 		else if(command == Command.GET_USER_PROP && hasAuthed && connType == ConnectionType.CLIENT)
724 		{
725 			/* Get the <user>,<propertyName> */
726 			string[] dataLine = split(cast(string)message.data[1..message.data.length],",");
727 
728 			/* Get the username */
729 			string username = dataLine[0];
730 
731 			/* Get the proerty */
732 			string propertyName = dataLine[1];
733 
734 			/* Get the user */
735 			DConnection connection = server.findUser(username);
736 
737 			/* Determine if it is a valid property */
738 			bool status = connection.isProperty(propertyName);
739 
740 			/* Encode the status */
741 			reply ~= [status];
742 
743 			/* Encode the property value if one exists */
744 			if(status)
745 			{
746 				/* Get the property value */
747 				string propertyValue = server.getProperty(username, propertyName);
748 
749 				/* Encode the property value */
750 				reply ~= propertyValue;
751 			}
752 		}
753 		/* If `set_user_prop` (requires: authed, client) */
754 		else if(command == Command.SET_USER_PROP && hasAuthed && connType == ConnectionType.CLIENT)
755 		{
756 			/* Set the <propertyName>,<propertyValue> */
757 			string[] dataLine = split(cast(string)message.data[1..message.data.length],",");
758 
759 			/* Get the property */
760 			string propertyName = dataLine[0];
761 
762 			/* Get the property value */
763 			string propertyValue = dataLine[1];
764 
765 			/* Encode the status */
766 			reply ~= [true];
767 
768 			/* Set the property value */
769 			setProperty(propertyName, propertyValue);
770 		}
771 		/* If `delete_user_prop` (requires: authed, client) */
772 		else if(command == Command.DELETE_USER_PROP && hasAuthed && connType == ConnectionType.CLIENT)
773 		{
774 			/* Get the property */
775 			string property = cast(string)message.data[1..message.data.length];
776 
777 			/* Check if the key exists */
778 			bool keyExists = isProperty(property);
779 
780 			/* If the property exists */
781 			if(keyExists)
782 			{
783 				/* Delete the property */
784 				deleteProperty(property);
785 			}
786 
787 			reply ~= [keyExists];
788 		}
789 		/* If `is_user_prop` (requires: authed, client) */
790 		else if(command == Command.IS_USER_PROP && hasAuthed && connType == ConnectionType.CLIENT)
791 		{
792 			/* Get the <user>,<propertyName> */
793 			string[] dataLine = split(cast(string)message.data[1..message.data.length],",");
794 
795 			/* Get the username */
796 			string username = dataLine[0];
797 
798 			/* Get the proerty */
799 			string propertyName = dataLine[1];
800 
801 			/* Get the user */
802 			DConnection connection = server.findUser(username);
803 
804 			/* Determine if it is a valid property */
805 			bool status = connection.isProperty(propertyName);
806 
807 			/* Encode the status */
808 			reply ~= [true];
809 			reply ~= [status];
810 		}
811 		
812 
813 
814 
815 
816 		/* TODO: `CHAN_PROP`, `SET_PROP` */
817 		/* If no matching built-in command was found */
818 		else
819 		{
820 			/* TODO: Check plugins */
821 			bool isPlugin = false;
822 
823 			/* A matching plugin was found */
824 			if(isPlugin)
825 			{
826 				/* TODO: Implement me */
827 			}
828 			/* The command was invalid */
829 			else
830 			{
831 				/* Write error message */
832 				reply = [2];
833 			}
834 		}
835 
836 		/* Write the response */
837 		writeSocket(tag, reply);
838 	}
839 
840 	/**
841 	* Authenticate
842 	*
843 	* Login as a user with the given credentials
844 	*/
845 	private bool authenticate(string username, string password)
846 	{
847 		/* TODO: Check username and password */
848 		/* TODO: Multi-client/session support */
849 
850 		/* TODO: Implement me */
851 		this.username = username;
852 
853 		/* TODO (Sessions): Generate a session ID for this connection */
854 
855 
856 		return true;
857 	}
858 
859 	private uint generateSessionID()
860 	{
861 		/* TODO: Basically find a number that isn't taken by matching usernames */
862 		return 1;
863 	}
864 
865 	private uint getMySessionID()
866 	{
867 		/* TODO: */
868 		return 1;
869 	}
870 
871 	/**
872 	* Get member count
873 	*
874 	* Gets the member count of a given channel
875 	*/
876 	private bool getMemberCount(string channelName, ref long count)
877 	{
878 		/* Status of operation */
879 		bool status;
880 
881 		/* The channel */
882 		DChannel channel = server.getChannelByName(channelName);
883 		
884 		/* Check if the channel exists */
885 		if(channel)
886 		{
887 			/* Get the channel count */
888 			count = channel.getMemberCount();
889 			
890 		
891 			status = true;
892 		}
893 		/* If the channel does not exist */
894 		else
895 		{
896 			status = false;
897 		}
898 
899 		return status;
900 	}
901 
902 	/**
903 	* Send user a message
904 	*
905 	* Sends the provided user the specified message
906 	*/
907 	private bool sendUserMessage(string username, string message)
908 	{
909 		/* Find the user to send to */
910 		DConnection user = server.findUser(username); /*TODO: Ins erver not just use it directly */
911 
912 		gprintln("sendUserMessage(): "~to!(string)(user));
913 
914 		/* If the user was found */
915 		if(user)
916 		{
917 			/* The protocol data to send */
918 			byte[] protocolData;
919 
920 			/* Set the sub-type (ntype=0 / channel/directmessage) */
921 			protocolData ~= [0];
922 
923 			/* Set to user message (direct message, sub-sub-type) */
924 			protocolData ~= [1];
925 
926 			/* Encode the recipients's length */
927 			protocolData ~= [cast(byte)username.length];
928 
929 			/* Encode the username */
930 			protocolData ~= cast(byte[])username;
931 
932 			/* Encode the sender's length */
933 			protocolData ~= [cast(byte)this.username.length];
934 
935 			/* Encode the sender */
936 			protocolData ~= cast(byte[])this.username;
937 
938 			/* Encode the message */
939 			protocolData ~= cast(byte[])message;
940 
941 			/* Send the messge */
942 			bool sendStatus = user.writeSocket(0, protocolData);
943 
944 			return sendStatus;
945 		}
946 		/* If the user was not found */
947 		else
948 		{
949 			return false;
950 		}
951 	}
952 
953 	public string getUsername()
954 	{
955 		return username;
956 	}
957 
958 	/**
959 	* Set the status message
960 	*/
961 	private void setStatusMessage(string statusMessage)
962 	{
963 		/* Lock the status message mutex */
964 		statusMessageLock.lock();
965 
966 		/* Set the status message */
967 		currentStatus = statusMessage;
968 
969 		/* Unlock the statue message mutex */
970 		statusMessageLock.unlock();
971 	}
972 
973 	/**
974 	* Returns the current status message
975 	*/
976 	public string getStatusMessage(string username)
977 	{
978 		/* The status message */
979 		string statusMessage;
980 
981 		gprintln("hfsjkhfjsdkhfdskj");
982 
983 		/* Lock the status message mutex */
984 		statusMessageLock.lock();
985 
986 		/* Get the status message */
987 		statusMessage = currentStatus;
988 
989 		/* Unlock the statue message mutex */
990 		statusMessageLock.unlock();
991 
992 		return statusMessage;
993 	}
994 
995 	/**
996 	* Sets a property for this user
997 	*/
998 	public void setProperty(string propertyName, string propertyValue)
999 	{
1000 		/* Lock the properties store */
1001 		propertiesLock.lock();
1002 
1003 		/* Set the property's value */
1004 		properties[propertyName] = propertyValue;
1005 
1006 		/* Unlock the properties store */
1007 		propertiesLock.unlock();
1008 	}
1009 
1010 	/**
1011 	* Determines whether or not the given property
1012 	* exists
1013 	*/
1014 	public bool isProperty(string propertyName)
1015 	{
1016 		/**
1017 		* Whether or not the given property is a property
1018 		* of this user
1019 		*/
1020 		bool status;
1021 
1022 		/* Lock the properties store */
1023 		propertiesLock.lock();
1024 
1025 		/* Check for the property's existence */
1026 		status = cast(bool)(propertyName in properties);
1027 
1028 		/* Unlock the properties store */
1029 		propertiesLock.unlock();
1030 
1031 		return status;
1032 	}
1033 
1034 	/**
1035 	* Returns a property
1036 	*/
1037 	public string getProperty(string propertyName)
1038 	{
1039 		/* The fetched property */
1040 		string propertyValue;
1041 
1042 		/* Lock the properties store */
1043 		propertiesLock.lock();
1044 
1045 		/* Check for the property's existence */
1046 		propertyValue = properties[propertyName];
1047 
1048 		/* Unlock the properties store */
1049 		propertiesLock.unlock();
1050 
1051 		/* TODO: Error handling */
1052 		return propertyValue;
1053 	}
1054 
1055 	/**
1056 	* Returns a list of proerties
1057 	*/
1058 	public string[] getProperties()
1059 	{
1060 		/* The list of keys */
1061 		string[] propertiesString;
1062 
1063 		/* Lock the properties store */
1064 		propertiesLock.lock();
1065 
1066 		/* Check for the property's existence */
1067 		propertiesString = properties.keys();
1068 
1069 		/* Unlock the properties store */
1070 		propertiesLock.unlock();
1071 
1072 		return propertiesString;
1073 	}
1074 
1075 	/**
1076 	* Deletes a given proerty
1077 	*/
1078 	public void deleteProperty(string propertyName)
1079 	{
1080 		/* Lock the properties store */
1081 		propertiesLock.lock();
1082 
1083 		/* Remove the property */
1084 		properties.remove(propertyName);
1085 
1086 		/* Unlock the properties store */
1087 		propertiesLock.unlock();
1088 	}
1089 
1090 	public ConnectionType getConnectionType()
1091 	{
1092 		return connType;
1093 	}
1094 
1095 	public override string toString()
1096 	{
1097 		string toStr = "["~to!(string)(connType)~" (";
1098 		toStr ~= socket.remoteAddress.toString();
1099 
1100 	
1101 		toStr ~= ")]: ";
1102 		
1103 		
1104 		if(connType == ConnectionType.CLIENT)
1105 		{
1106 			toStr = toStr ~ getUsername();
1107 		}
1108 		else
1109 		{
1110 			/* TODO Implement me */
1111 		}
1112 
1113 		return toStr;
1114 	}
1115 }