Commit Diff


commit - f38a9035e5439cb395b2de6b9bdfa36102bfe80c
commit + f37600ee01f6cfd86e8fa80f77ee26ebaf3012b2
blob - 0035855c90e6f21dee2a6fb55c9009c5d8873b22
blob + a7b6a22ca342633e30fe0be9b83ea9ad975f0476
--- doc/.gitignore
+++ doc/.gitignore
@@ -1 +1,2 @@
 sample-ngircd.conf
+Add_Modes.txt
blob - c2c533f2a67e1784561869cfee1ac49c5bf0fd90
blob + dec80ce819756e3576ee6d70dfe39e21fe676379
--- doc/Modes.txt
+++ doc/Modes.txt
@@ -68,7 +68,12 @@ channel of which he is a member.
 
   mode	since	description
 
+  q	20?	User is channel owner can only be set by a service, other
+		owner and irc op. Can promote other users to q, a, o, h, v.
+  a	20?	User is channel admin and can promote other users to v, h, o
   o	0.2.0	User is channel operator and can op/kick/... other members.
+  h	20?	User is half op and can set channel modes imntvIbek and kick
+		voiced and normal users.
   v	0.2.0	User is "voiced" and can speak even if channel is moderated.
 
 
blob - f0a9525d348a5d992596f453fd9ba709cbd47c9a
blob + 8d001a825c013e8c77e339f5915a54eac792b6fa
--- src/ngircd/channel.c
+++ src/ngircd/channel.c
@@ -299,6 +299,8 @@ Channel_Kick(CLIENT *Peer, CLIENT *Target, CLIENT *Ori
 	     const char *Reason )
 {
 	CHANNEL *chan;
+	char *ptr, *target_modes;
+	bool can_kick = false;
 
 	assert(Peer != NULL);
 	assert(Target != NULL);
@@ -319,14 +321,51 @@ Channel_Kick(CLIENT *Peer, CLIENT *Target, CLIENT *Ori
 		/* Check that user is on the specified channel */
 		if (!Channel_IsMemberOf(chan, Origin)) {
 			IRC_WriteStrClient( Origin, ERR_NOTONCHANNEL_MSG,
-					   Client_ID(Origin), Name);
+					    Client_ID(Origin), Name);
 			return;
 		}
+	}
 
-		/* Check if user has operator status */
-		if (!strchr(Channel_UserModes(chan, Origin), 'o')) {
-			IRC_WriteStrClient(Origin, ERR_CHANOPRIVSNEEDED_MSG,
-					   Client_ID(Origin), Name);
+	if(Client_Type(Peer) == CLIENT_USER) {
+		/* Check if client has the rights to kick target */
+		ptr = Channel_UserModes(chan, Peer);
+		target_modes = Channel_UserModes(chan, Target);
+		while(*ptr) {
+			/* Owner can kick everyone */
+			if ( *ptr == 'q') {
+				can_kick = true;
+				break;
+			}
+			/* Admin can't kick owner */
+			if ( *ptr == 'a' ) {
+				if (!strchr(target_modes, 'q')) {
+					can_kick = true;
+					break;
+				}
+			}
+			/* Op can't kick owner | admin */
+			if ( *ptr == 'o' ) {
+				if (!strchr(target_modes, 'q') &&
+				    !strchr(target_modes, 'a')) {
+					can_kick = true;
+					break;
+				}
+			}
+			/* Half Op can't kick owner | admin | op */ 
+			if ( *ptr == 'h' ) {
+				if (!strchr(target_modes, 'q') &&
+				    !strchr(target_modes, 'a') &&
+				    !strchr(target_modes, 'o')) {
+					can_kick = true;
+					break;
+				}
+			}
+			ptr++;
+		}
+		
+		if(!can_kick) {
+			IRC_WriteStrClient(Origin, ERR_CHANOPPRIVTOLOW_MSG,
+				Client_ID(Origin), Name);
 			return;
 		}
 	}
@@ -812,9 +851,9 @@ Channel_SetMaxUsers(CHANNEL *Chan, unsigned long Count
 static bool
 Can_Send_To_Channel(CHANNEL *Chan, CLIENT *From)
 {
-	bool is_member, has_voice, is_op;
+	bool is_member, has_voice, is_halfop, is_op, is_chanadmin, is_owner;
 
-	is_member = has_voice = is_op = false;
+	is_member = has_voice = is_halfop = is_op = is_chanadmin = is_owner = false;
 
 	/* The server itself always can send messages :-) */
 	if (Client_ThisServer() == From)
@@ -824,8 +863,14 @@ Can_Send_To_Channel(CHANNEL *Chan, CLIENT *From)
 		is_member = true;
 		if (strchr(Channel_UserModes(Chan, From), 'v'))
 			has_voice = true;
+		if (strchr(Channel_UserModes(Chan, From), 'h'))
+			is_halfop = true;
 		if (strchr(Channel_UserModes(Chan, From), 'o'))
 			is_op = true;
+		if (strchr(Channel_UserModes(Chan, From), 'a'))
+			is_chanadmin = true;
+		if (strchr(Channel_UserModes(Chan, From), 'q'))
+			is_owner = true;
 	}
 
 	/*
@@ -841,7 +886,7 @@ Can_Send_To_Channel(CHANNEL *Chan, CLIENT *From)
 	    && !Client_HasMode(From, 'o'))
 		return false;
 
-	if (is_op || has_voice)
+	if (has_voice || is_halfop || is_op || is_chanadmin || is_owner)
 		return true;
 
 	if (strchr(Channel_Modes(Chan), 'm'))
@@ -1196,64 +1241,6 @@ Channel_CheckKey(CHANNEL *Chan, CLIENT *Client, const 
 } /* Channel_CheckKey */
 
 
-/**
- * Check wether a client is allowed to administer a channel or not.
- *
- * @param Chan		The channel to test.
- * @param Client	The client from which the command has been received.
- * @param Origin	The originator of the command (or NULL).
- * @param OnChannel	Set to true if the originator is member of the channel.
- * @param AdminOk	Set to true if the client is allowed to do
- *			administrative tasks on this channel.
- * @param UseServerMode	Set to true if ngIRCd should emulate "server mode",
- *			that is send commands as if originating from a server
- *			and not the originator of the command.
- */
-GLOBAL void
-Channel_CheckAdminRights(CHANNEL *Chan, CLIENT *Client, CLIENT *Origin,
-			 bool *OnChannel, bool *AdminOk, bool *UseServerMode)
-{
-	assert (Chan != NULL);
-	assert (Client != NULL);
-	assert (OnChannel != NULL);
-	assert (AdminOk != NULL);
-	assert (UseServerMode != NULL);
-
-	/* Use the client as origin, if no origin has been given (no prefix?) */
-	if (!Origin)
-		Origin = Client;
-
-	*OnChannel = false;
-	*AdminOk = false;
-	*UseServerMode = false;
-
-	if (Client_Type(Client) != CLIENT_USER
-	    && Client_Type(Client) != CLIENT_SERVER
-	    && Client_Type(Client) != CLIENT_SERVICE)
-		return;
-
-	/* Allow channel administration if the client is a server or service */
-	if (Client_Type(Client) != CLIENT_USER) {
-		*AdminOk = true;
-		return;
-	}
-
-	*OnChannel = Channel_IsMemberOf(Chan, Origin);
-
-	if (*OnChannel && strchr(Channel_UserModes(Chan, Origin), 'o')) {
-		/* User is a channel operator */
-		*AdminOk = true;
-	} else if (Conf_OperCanMode) {
-		/* IRC operators are allowed to administer channels as well */
-		if (Client_OperByMe(Origin)) {
-			*AdminOk = true;
-			if (Conf_OperServerMode)
-				*UseServerMode = true;
-		}
-	}
-} /* Channel_CheckAdminRights */
-
-
 static CL2CHAN *
 Get_First_Cl2Chan( CLIENT *Client, CHANNEL *Chan )
 {
blob - 82837599aadbb6485f0f044b447739d7b5f922fa
blob + ba7adf17adb204840418b7482b75fef975844138
--- src/ngircd/defines.h
+++ src/ngircd/defines.h
@@ -164,7 +164,7 @@
 #define USERMODES "aBcCiorRswx"
 
 /** Supported channel modes. */
-#define CHANMODES "beiIklmMnoOPrRstvz"
+#define CHANMODES "abehiIklmMnoOPqrRstvz"
 
 /** Away message for users connected to linked servers. */
 #define DEFAULT_AWAY_MSG "Away"
blob - d714b48fcb8e3d28aed8cb2101bc6e452295290b
blob + 9e88e1bd0031de480a33d3ddfb5b447fed81d013
--- src/ngircd/irc-channel.c
+++ src/ngircd/irc-channel.c
@@ -510,7 +510,7 @@ IRC_TOPIC( CLIENT *Client, REQUEST *Req )
 	CHANNEL *chan;
 	CLIENT *from;
 	char *topic;
-	bool onchannel, topicok, use_servermode, r;
+	bool r, is_oper;
 
 	assert( Client != NULL );
 	assert( Req != NULL );
@@ -533,10 +533,9 @@ IRC_TOPIC( CLIENT *Client, REQUEST *Req )
 		return IRC_WriteStrClient(from, ERR_NOSUCHCHANNEL_MSG,
 					  Client_ID(from), Req->argv[0]);
 
-	Channel_CheckAdminRights(chan, Client, from,
-				 &onchannel, &topicok, &use_servermode);
-
-	if (!onchannel && !topicok)
+	/* Only IRC opers and channel members allowed */
+	is_oper = Client_OperByMe(from);
+	if (!Channel_IsMemberOf(chan, from) && !is_oper)
 		return IRC_WriteStrClient(from, ERR_NOTONCHANNEL_MSG,
 					  Client_ID(from), Req->argv[0]);
 
@@ -565,8 +564,12 @@ IRC_TOPIC( CLIENT *Client, REQUEST *Req )
 	}
 
 	if (strchr(Channel_Modes(chan), 't')) {
-		/* Topic Lock. Is the user a channel or IRC operator? */
-		if (!topicok)
+		/* Topic Lock. Is the user a channel op or IRC operator? */
+		if(!strchr(Channel_UserModes(chan, from), 'h') &&
+		   !strchr(Channel_UserModes(chan, from), 'o') &&
+		   !strchr(Channel_UserModes(chan, from), 'a') &&
+		   !strchr(Channel_UserModes(chan, from), 'q') &&
+		   !is_oper)
 			return IRC_WriteStrClient(from, ERR_CHANOPRIVSNEEDED_MSG,
 						  Client_ID(from),
 						  Channel_Name(chan));
@@ -578,7 +581,7 @@ IRC_TOPIC( CLIENT *Client, REQUEST *Req )
 		 Client_TypeText(from), Client_Mask(from), Channel_Name(chan),
 		 Req->argv[1][0] ? Req->argv[1] : "<none>");
 
-	if (use_servermode)
+	if (Conf_OperServerMode)
 		from = Client_ThisServer();
 
 	/* Update channel and forward new topic to other servers */
blob - 6eb8d9421922e8606e0e5cbd83729f1c90c7d4b5
blob + 7a122ef790a0c5ec0148c3224b83956a8de00020
--- src/ngircd/irc-info.c
+++ src/ngircd/irc-info.c
@@ -807,22 +807,38 @@ who_flags_status(const char *client_modes)
 }
 
 
-static const char *
-who_flags_qualifier(CLIENT *Client, const char *chan_user_modes)
+static char *
+who_flags_qualifier(CLIENT *Client, const char *chan_user_modes, char *str, size_t len)
 {
 	assert(Client != NULL);
-
-	if (Client_Cap(Client) & CLIENT_CAP_MULTI_PREFIX) {
-		if (strchr(chan_user_modes, 'o') &&
-		    strchr(chan_user_modes, 'v'))
-			return "@+";
+    
+	if (Client_Cap(Client) & CLIENT_CAP_MULTI_PREFIX) {		
+		if (strchr(chan_user_modes, 'q'))
+			strlcat(str, "~", len);
+		if (strchr(chan_user_modes, 'a'))
+			strlcat(str, "&", len);
+		if (strchr(chan_user_modes, 'o'))
+			strlcat(str, "@", len);
+		if (strchr(chan_user_modes, 'h'))
+			strlcat(str, "&", len);
+		if (strchr(chan_user_modes, 'v'))
+			strlcat(str, "+", len);
+		
+		return str;
 	}
-
-	if (strchr(chan_user_modes, 'o'))
-		return "@";
+	
+	if (strchr(chan_user_modes, 'q'))
+		strlcat(str, "~", len);
+	else if (strchr(chan_user_modes, 'a'))
+		strlcat(str, "&", len);
+	else if (strchr(chan_user_modes, 'o'))
+		strlcat(str, "@", len);
+	else if (strchr(chan_user_modes, 'h'))
+		strlcat(str, "%", len);
 	else if (strchr(chan_user_modes, 'v'))
-		return "+";
-	return "";
+		strlcat(str, "+", len);
+	
+	return str;
 }
 
 
@@ -841,7 +857,7 @@ IRC_WHO_Channel(CLIENT *Client, CHANNEL *Chan, bool On
 	CL2CHAN *cl2chan;
 	const char *client_modes;
 	const char *chan_user_modes;
-	char flags[8];
+	char flags[10];
 	CLIENT *c;
 	int count = 0;
 
@@ -873,9 +889,8 @@ IRC_WHO_Channel(CLIENT *Client, CHANNEL *Chan, bool On
 				strlcat(flags, "*", sizeof(flags));
 
 			chan_user_modes = Channel_UserModes(Chan, c);
-			strlcat(flags, who_flags_qualifier(c, chan_user_modes),
-				sizeof(flags));
-
+			who_flags_qualifier(c, chan_user_modes, flags, sizeof(flags));
+ 
 			if (!write_whoreply(Client, c, Channel_Name(Chan),
 					    flags))
 				return DISCONNECTED;
@@ -1090,8 +1105,7 @@ IRC_WHOIS_SendReply(CLIENT *Client, CLIENT *from, CLIE
 		if (str[strlen(str) - 1] != ':')
 			strlcat(str, " ", sizeof(str));
 
-		strlcat(str, who_flags_qualifier(c, Channel_UserModes(chan, c)),
-						 sizeof(str));
+		who_flags_qualifier(c, Channel_UserModes(chan, c), str, sizeof(str));
 		strlcat(str, Channel_Name(chan), sizeof(str));
 
 		if (strlen(str) > (LINE_LEN - CHANNEL_NAME_LEN - 4)) {
@@ -1595,16 +1609,8 @@ IRC_Send_NAMES(CLIENT * Client, CHANNEL * Chan)
 		if (is_member || is_visible) {
 			if (str[strlen(str) - 1] != ':')
 				strlcat(str, " ", sizeof(str));
-			if (Client_Cap(cl) & CLIENT_CAP_MULTI_PREFIX) {
-				if (strchr(Channel_UserModes(Chan, cl), 'o') &&
-				    strchr(Channel_UserModes(Chan, cl), 'v'))
-					strlcat(str, "@+", sizeof(str));
-			} else {
-				if (strchr(Channel_UserModes(Chan, cl), 'o'))
-					strlcat(str, "@", sizeof(str));
-				else if (strchr(Channel_UserModes(Chan, cl), 'v'))
-					strlcat(str, "+", sizeof(str));
-			}
+			
+			who_flags_qualifier(cl,	Channel_UserModes(Chan, cl), str, sizeof(str));
 			strlcat(str, Client_ID(cl), sizeof(str));
 
 			if (strlen(str) > (LINE_LEN - CLIENT_NICK_LEN - 4)) {
blob - 71557201d8d938f4a5ff65416f50eb487f78333d
blob + f39463433ce5261b0d3736174845f92aadb21e76
--- src/ngircd/irc-mode.c
+++ src/ngircd/irc-mode.c
@@ -418,13 +418,16 @@ static bool
 Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Origin, CHANNEL *Channel)
 {
 	char the_modes[COMMAND_LEN], the_args[COMMAND_LEN], x[2],
-	    argadd[CLIENT_PASS_LEN], *mode_ptr;
-	bool connected, set, skiponce, retval, onchannel, modeok, use_servermode;
+	    argadd[CLIENT_PASS_LEN], *mode_ptr, *o_mode_ptr;
+	bool connected, set, skiponce, retval, use_servermode,
+	     is_halfop, is_op, is_admin, is_owner, is_machine, is_oper;
 	int mode_arg, arg_arg, mode_arg_count = 0;
 	CLIENT *client;
 	long l;
 	size_t len;
 
+	is_halfop = is_op = is_admin = is_owner = is_machine = is_oper = false;
+
 	if (Channel_IsModeless(Channel))
 		return IRC_WriteStrClient(Client, ERR_NOCHANMODES_MSG,
 				Client_ID(Client), Channel_Name(Channel));
@@ -433,10 +436,20 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 	if (Req->argc <= 1)
 		return Channel_Mode_Answer_Request(Origin, Channel);
 
-	Channel_CheckAdminRights(Channel, Client, Origin,
-				 &onchannel, &modeok, &use_servermode);
+	/* Check if origin is oper and opers can use mode */
+	use_servermode = Conf_OperServerMode;
+	if(Client_OperByMe(Client) && Conf_OperCanMode) {
+		is_oper = true;
+	}
+	
+	/* Check if client is a server/service */
+	if(Client_Type(Client) == CLIENT_SERVER ||
+	   Client_Type(Client) == CLIENT_SERVICE) {
+		is_machine = true;
+	}
 
-	if (!onchannel && !modeok)
+	/* Check if client is member of channel or an oper or an server/service */
+	if(!Channel_IsMemberOf(Channel, Client) && !is_oper && !is_machine)
 		return IRC_WriteStrClient(Origin, ERR_NOTONCHANNEL_MSG,
 					  Client_ID(Origin),
 					  Channel_Name(Channel));
@@ -514,6 +527,21 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 		/* Are there arguments left? */
 		if (arg_arg >= Req->argc)
 			arg_arg = -1;
+        
+		if(!is_machine) {
+			o_mode_ptr = Channel_UserModes(Channel, Client);
+			while( *o_mode_ptr ) {
+				if ( *o_mode_ptr == 'q')
+					is_owner = true;
+				if ( *o_mode_ptr == 'a')
+					is_admin = true;
+				if ( *o_mode_ptr == 'o')
+					is_op = true;
+				if ( *o_mode_ptr == 'h')
+					is_halfop = true;
+				o_mode_ptr++;
+			}
+		}
 
 		/* Validate modes */
 		x[0] = '\0';
@@ -521,15 +549,23 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 		client = NULL;
 		switch (*mode_ptr) {
 		/* --- Channel modes --- */
+		case 'R': /* Registered users only */
+		case 's': /* Secret channel */
+		case 'z': /* Secure connections only */
+			if(!is_oper && !is_machine && !is_owner &&
+			   !is_admin && !is_op) {
+				connected = IRC_WriteStrClient(Origin,
+					ERR_CHANOPRIVSNEEDED_MSG,
+					Client_ID(Origin), Channel_Name(Channel));
+				goto chan_exit;
+			}
 		case 'i': /* Invite only */
 		case 'M': /* Only identified nicks can write */
 		case 'm': /* Moderated */
 		case 'n': /* Only members can write */
-		case 'R': /* Registered users only */
-		case 's': /* Secret channel */
 		case 't': /* Topic locked */
-		case 'z': /* Secure connections only */
-			if (modeok)
+			if(is_oper || is_machine || is_owner ||
+			   is_admin || is_op || is_halfop)
 				x[0] = *mode_ptr;
 			else
 				connected = IRC_WriteStrClient(Origin,
@@ -540,7 +576,8 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 			if (Mode_Limit_Reached(Client, mode_arg_count++))
 				goto chan_exit;
 			if (!set) {
-				if (modeok)
+				if (is_oper || is_machine || is_owner ||
+				    is_admin || is_op || is_halfop)
 					x[0] = *mode_ptr;
 				else
 					connected = IRC_WriteStrClient(Origin,
@@ -550,7 +587,8 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 				break;
 			}
 			if (arg_arg > mode_arg) {
-				if (modeok) {
+				if (is_oper || is_machine || is_owner ||
+				    is_admin || is_op || is_halfop) {
 					Channel_ModeDel(Channel, 'k');
 					Channel_SetKey(Channel,
 						       Req->argv[arg_arg]);
@@ -576,7 +614,8 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 			if (Mode_Limit_Reached(Client, mode_arg_count++))
 				goto chan_exit;
 			if (!set) {
-				if (modeok)
+				if (is_oper || is_machine || is_owner ||
+				    is_admin || is_op || is_halfop)
 					x[0] = *mode_ptr;
 				else
 					connected = IRC_WriteStrClient(Origin,
@@ -586,7 +625,8 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 				break;
 			}
 			if (arg_arg > mode_arg) {
-				if (modeok) {
+				if (is_oper || is_machine || is_owner ||
+				    is_admin || is_op || is_halfop) {
 					l = atol(Req->argv[arg_arg]);
 					if (l > 0 && l < 0xFFFF) {
 						Channel_ModeDel(Channel, 'l');
@@ -611,44 +651,47 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 			}
 			break;
 		case 'O': /* IRC operators only */
-			if (modeok) {
+			if (set) {
 				/* Only IRC operators are allowed to
 				 * set the 'O' channel mode! */
-				if (set && !(Client_OperByMe(Client)
-				    || Client_Type(Client) == CLIENT_SERVER))
+				if(is_oper || is_machine)
+					x[0] = 'O';
+				else
 					connected = IRC_WriteStrClient(Origin,
 						ERR_NOPRIVILEGES_MSG,
 						Client_ID(Origin));
-				else
-					x[0] = 'O';
-			} else
+			} else if(is_oper || is_machine || is_owner ||
+				  is_admin || is_op)
+				x[0] = 'O';
+			else
 				connected = IRC_WriteStrClient(Origin,
-						ERR_CHANOPRIVSNEEDED_MSG,
-						Client_ID(Origin),
-						Channel_Name(Channel));
-				break;
+					ERR_CHANOPRIVSNEEDED_MSG,
+					Client_ID(Origin),
+					Channel_Name(Channel));
+			break;
 		case 'P': /* Persistent channel */
-			if (modeok) {
+			if (set) {
 				/* Only IRC operators are allowed to
 				 * set the 'P' channel mode! */
-				if (set && !(Client_OperByMe(Client)
-				    || Client_Type(Client) == CLIENT_SERVER))
+				if(is_oper || is_machine)
+					x[0] = 'P';
+				else
 					connected = IRC_WriteStrClient(Origin,
 						ERR_NOPRIVILEGES_MSG,
 						Client_ID(Origin));
-				else
-					x[0] = 'P';
-			} else
+			} else if(is_oper || is_machine || is_owner ||
+				  is_admin || is_op)
+				x[0] = 'P';
+			else
 				connected = IRC_WriteStrClient(Origin,
 					ERR_CHANOPRIVSNEEDED_MSG,
 					Client_ID(Origin),
 					Channel_Name(Channel));
 			break;
 		/* --- Channel user modes --- */
-		case 'a':
-		case 'h':
-		case 'q':
-			if (Client_Type(Client) != CLIENT_SERVER) {
+		case 'q': /* Owner */
+		case 'a': /* Channel admin */
+			if(!is_oper && !is_machine && !is_owner) {
 				connected = IRC_WriteStrClient(Origin,
 					ERR_CHANOPRIVSNEEDED_MSG,
 					Client_ID(Origin),
@@ -656,16 +699,34 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 				goto chan_exit;
 			}
 		case 'o': /* Channel operator */
+			if(!is_oper && !is_machine && !is_owner &&
+			   !is_admin && !is_op) {
+				connected = IRC_WriteStrClient(Origin,
+					ERR_CHANOPRIVSNEEDED_MSG,
+					Client_ID(Origin),
+					Channel_Name(Channel));
+				goto chan_exit;
+			}
+		case 'h': /* Half Op */
+			if(!is_oper && !is_machine && !is_owner &&
+			   !is_admin && !is_op) {
+				connected = IRC_WriteStrClient(Origin,
+					ERR_CHANOPRIVSNEEDED_MSG,
+					Client_ID(Origin),
+					Channel_Name(Channel));
+				goto chan_exit;
+			}
 		case 'v': /* Voice */
 			if (arg_arg > mode_arg) {
-				if (modeok) {
+				if (is_oper || is_machine || is_owner ||
+				    is_admin || is_op || is_halfop) {
 					client = Client_Search(Req->argv[arg_arg]);
 					if (client)
 						x[0] = *mode_ptr;
 					else
-						connected = IRC_WriteStrClient(Client,
+						connected = IRC_WriteStrClient(Origin,
 							ERR_NOSUCHNICK_MSG,
-							Client_ID(Client),
+							Client_ID(Origin),
 							Req->argv[arg_arg]);
 				} else {
 					connected = IRC_WriteStrClient(Origin,
@@ -690,7 +751,8 @@ Channel_Mode(CLIENT *Client, REQUEST *Req, CLIENT *Ori
 				goto chan_exit;
 			if (arg_arg > mode_arg) {
 				/* modify list */
-				if (modeok) {
+				if (is_oper || is_machine || is_owner ||
+				    is_admin || is_op || is_halfop) {
 					connected = set
 					   ? Add_To_List(*mode_ptr, Origin,
 						Client, Channel,
blob - 5e36b02bef478ce9ac2a0d96e411deaf2e11c4cc
blob + 08495475f60520c7dd899d7cef9e10b4f4852550
--- src/ngircd/irc-op.c
+++ src/ngircd/irc-op.c
@@ -166,8 +166,11 @@ IRC_INVITE(CLIENT *Client, REQUEST *Req)
 
 		/* Is the channel "invite-only"? */
 		if (strchr(Channel_Modes(chan), 'i')) {
-			/* Yes. The user must be channel operator! */
-			if (!strchr(Channel_UserModes(chan, from), 'o'))
+			/* Yes. The user must be channel owner/admin/operator/halfop! */
+			if (!strchr(Channel_UserModes(chan, from), 'q') &&
+			    !strchr(Channel_UserModes(chan, from), 'a') &&
+			    !strchr(Channel_UserModes(chan, from), 'o') &&
+			    !strchr(Channel_UserModes(chan, from), 'h'))
 				return IRC_WriteStrClient(from, ERR_CHANOPRIVSNEEDED_MSG,
 						Client_ID(from), Channel_Name(chan));
 			remember = true;
blob - 8526a573ec361b0851222314343e079b447114f6
blob + f9182d984ca9c2c6f1f743b419a198d89b31ada7
--- src/ngircd/irc-server.c
+++ src/ngircd/irc-server.c
@@ -203,7 +203,7 @@ GLOBAL bool
 IRC_NJOIN( CLIENT *Client, REQUEST *Req )
 {
 	char nick_in[COMMAND_LEN], nick_out[COMMAND_LEN], *channame, *ptr, modes[8];
-	bool is_op, is_voiced;
+	bool is_owner, is_chanadmin, is_op, is_halfop, is_voiced;
 	CHANNEL *chan;
 	CLIENT *c;
 	
@@ -222,9 +222,13 @@ IRC_NJOIN( CLIENT *Client, REQUEST *Req )
 		is_op = is_voiced = false;
 		
 		/* cut off prefixes */
-		while(( *ptr == '@' ) || ( *ptr == '+' ))
-		{
+		while(( *ptr == '~') || ( *ptr == '&' ) || ( *ptr == '@' ) ||
+			( *ptr == '%') || ( *ptr == '+' ))
+		{	
+			if( *ptr == '~' ) is_owner = true;
+			if( *ptr == '&' ) is_chanadmin = true;
 			if( *ptr == '@' ) is_op = true;
+			if( *ptr == 'h' ) is_halfop = true;
 			if( *ptr == '+' ) is_voiced = true;
 			ptr++;
 		}
@@ -236,7 +240,10 @@ IRC_NJOIN( CLIENT *Client, REQUEST *Req )
 			chan = Channel_Search( channame );
 			assert( chan != NULL );
 			
+			if( is_owner ) Channel_UserModeAdd( chan, c, 'q' );
+			if( is_chanadmin ) Channel_UserModeAdd( chan, c, 'a' );
 			if( is_op ) Channel_UserModeAdd( chan, c, 'o' );
+			if( is_halfop ) Channel_UserModeAdd( chan, c, 'h' );
 			if( is_voiced ) Channel_UserModeAdd( chan, c, 'v' );
 
 			/* announce to channel... */
@@ -251,7 +258,10 @@ IRC_NJOIN( CLIENT *Client, REQUEST *Req )
 			}
 
 			if( nick_out[0] != '\0' ) strlcat( nick_out, ",", sizeof( nick_out ));
+			if( is_owner ) strlcat( nick_out, "~", sizeof( nick_out ));
+			if( is_chanadmin ) strlcat( nick_out, "&", sizeof( nick_out ));
 			if( is_op ) strlcat( nick_out, "@", sizeof( nick_out ));
+			if( is_halfop ) strlcat( nick_out, "%", sizeof( nick_out ));
 			if( is_voiced ) strlcat( nick_out, "+", sizeof( nick_out ));
 			strlcat( nick_out, ptr, sizeof( nick_out ));
 		}
blob - 4f3a397b2647eabc6fd6ed21f9aa31d776df69e9
blob + d99930faa5d079995932ba3140d207c08e20e5c9
--- src/ngircd/messages.h
+++ src/ngircd/messages.h
@@ -21,7 +21,7 @@
 #define RPL_YOURHOST_MSG		"002 %s :Your host is %s, running version ngircd-%s (%s/%s/%s)"
 #define RPL_CREATED_MSG			"003 %s :This server has been started %s"
 #define RPL_MYINFO_MSG			"004 %s %s ngircd-%s %s %s"
-#define RPL_ISUPPORT1_MSG		"005 %s RFC2812 IRCD=ngIRCd CASEMAPPING=ascii PREFIX=(ov)@+ CHANTYPES=#&+ CHANMODES=beI,k,l,imMnOPRstz CHANLIMIT=#&+:%d :are supported on this server"
+#define RPL_ISUPPORT1_MSG		"005 %s RFC2812 IRCD=ngIRCd CASEMAPPING=ascii PREFIX=(qaohv)~&@%%+ CHANTYPES=#&+ CHANMODES=beI,k,l,imMnOPRstz CHANLIMIT=#&+:%d :are supported on this server"
 #define RPL_ISUPPORT2_MSG		"005 %s CHANNELLEN=%d NICKLEN=%d TOPICLEN=%d AWAYLEN=%d KICKLEN=%d MODES=%d MAXLIST=beI:%d EXCEPTS=e INVEX=I PENALTY :are supported on this server"
 
 #define RPL_TRACELINK_MSG		"200 %s Link %s-%s %s %s V%s %ld %d %d"
@@ -138,6 +138,7 @@
 #define ERR_LISTFULL_MSG		"478 %s %s %s: Channel list is full (%d)"
 #define ERR_NOPRIVILEGES_MSG		"481 %s :Permission denied"
 #define ERR_CHANOPRIVSNEEDED_MSG	"482 %s %s :You are not channel operator"
+#define ERR_CHANOPPRIVTOLOW_MSG		"482 %s %s :Your privileges are to low"
 #define ERR_CANTKILLSERVER_MSG		"483 %s :You can't kill a server!"
 #define ERR_RESTRICTED_MSG		"484 %s :Your connection is restricted"
 #define ERR_NICKREGISTER_MSG		"484 %s :Cannot modify user mode (+R) -- Use IRC services"
blob - d59a1dc3a9be6895de5f2565337fe8e29a3fbb40
blob + 4bce60fb12a947908f14118b7aa200439f4284df
--- src/ngircd/numeric.c
+++ src/ngircd/numeric.c
@@ -67,10 +67,17 @@ Announce_Channel(CLIENT *Client, CHANNEL *Chan)
 			 * (if user is channel operator or has voice) */
 			if (str[strlen(str) - 1] != ':')
 				strlcat(str, ",", sizeof(str));
-			if (strchr(Channel_UserModes(Chan, cl), 'v'))
-				strlcat(str, "+", sizeof(str));
+			if (strchr(Channel_UserModes(Chan, cl), 'q'))
+				strlcat(str, "~", sizeof(str));
+			if (strchr(Channel_UserModes(Chan, cl), 'a'))
+				strlcat(str, "&", sizeof(str));
 			if (strchr(Channel_UserModes(Chan, cl), 'o'))
 				strlcat(str, "@", sizeof(str));
+			if (strchr(Channel_UserModes(Chan, cl), 'h'))
+				strlcat(str, "%", sizeof(str));
+			if (strchr(Channel_UserModes(Chan, cl), 'v'))
+				strlcat(str, "+", sizeof(str));
+
 			strlcat(str, Client_ID(cl), sizeof(str));
 
 			/* Send the data if the buffer is "full" */
blob - 260cd03c12898ae46c5e9d58b1f5f4fb4bcf74ff
blob + 44b6e5dfe5a3bb3cb05dddba1e08498e1cb58bdd
--- src/testsuite/mode-test.e
+++ src/testsuite/mode-test.e
@@ -31,6 +31,46 @@ expect {
 	"@* MODE nick :-i"
 }
 
+send "join #usermode\r"
+expect {
+	timeout { exit 1 }
+	"@* JOIN :#usermode"
+}
+expect {
+	timeout { exit 1 }
+	"366"
+}
+
+send "mode #usermode +v nick\r"
+expect {
+	timeout { exit 1 }
+	"@* MODE #usermode +v nick\r"
+}
+
+send "mode #usermode +h nick\r"
+expect {
+	timeout { exit 1 }
+	"@* MODE #usermode +h nick\r"
+}
+
+send "mode #usermode +a nick\r"
+expect {
+	timeout { exit 1 }
+	"482 nick"
+}
+
+send "mode #usermode +q nick\r"
+expect {
+	timeout { exit 1 }
+	"482 nick"
+}
+
+send "mode #usermode -vho nick nick nick\r"
+expect {
+	timeout { exit 1 }
+	"@* MODE #usermode -vho nick nick nick"
+}
+
 send "oper TestOp 123\r"
 expect {
 	timeout { exit 1 }
@@ -47,6 +87,34 @@ expect {
 	"221 nick +o"
 }
 
+send "mode #usermode +a nick\r"
+expect {
+	timeout { exit 1 }
+	"@* MODE #usermode +a nick"
+}
+
+send "mode #usermode +q nick\r"
+expect {
+	timeout { exit 1 }
+	"@* MODE #usermode +q nick"
+}
+
+send "names #usermode\r"
+expect {
+	timeout { exit 1 }
+	"353 nick = #usermode :~nick"
+}
+expect {
+	timeout { exit 1 }
+	"366 nick #usermode"
+}
+
+send "part #usermode\r"
+expect {
+	timeout { exit 1 }
+	"@* PART #usermode"
+}
+
 send "join #channel\r"
 expect {
 	timeout { exit 1 }