From db01d503253fe10f6107cc145e5fc183a826f989 Mon Sep 17 00:00:00 2001
Message-Id: <db01d503253fe10f6107cc145e5fc183a826f989.1231161643.git.alex@barton.de>
From: Alexander Barton <alex@barton.de>
Date: Thu, 1 Jan 2009 22:26:13 +0100
Subject: [PATCH 1/2] Support individual channel keys for pre-defined channels.

This patch introduces the new configuration variable "KeyFile" for
[Channel] sections in ngircd.conf. Here a file can be configured for each
pre-defined channel which contains individual channel keys for different
users. This file is line-based and must have the following syntax:

  <user>:<nick>:<key>

<user> and <nick> can contain the wildcard character "*".

Please not that these channel keys are only in effect, when the channel
has a regular key set using channel mode "k"!
---
 doc/sample-ngircd.conf   |    4 ++
 man/ngircd.conf.5.tmpl   |   45 +++++++++++++++++++++++-
 src/ngircd/channel.c     |   88 +++++++++++++++++++++++++++++++++++++++++++--
 src/ngircd/channel.h     |    4 ++
 src/ngircd/conf.c        |   10 +++++-
 src/ngircd/conf.h        |    1 +
 src/ngircd/irc-channel.c |    7 ++--
 7 files changed, 149 insertions(+), 10 deletions(-)

diff --git a/doc/sample-ngircd.conf b/doc/sample-ngircd.conf
index 526e880..0d0061b 100644
--- a/doc/sample-ngircd.conf
+++ b/doc/sample-ngircd.conf
@@ -245,6 +245,10 @@
 	# initial channel password (mode k)
 	;Key = Secret
 
+	# Key file, syntax for each line: "<user>:<nick>:<key>".
+	# Default: none.
+	;KeyFile = /etc/ngircd/#chan.key
+
 	# maximum users per channel (mode l)
 	;MaxUsers = 23
 
diff --git a/man/ngircd.conf.5.tmpl b/man/ngircd.conf.5.tmpl
index b8aa7bd..df15b77 100644
--- a/man/ngircd.conf.5.tmpl
+++ b/man/ngircd.conf.5.tmpl
@@ -319,7 +319,50 @@ Topic for this channel.
 Initial channel modes.
 .TP
 \fBKey\fR
-Sets initial channel key (only relevant if mode k is set).
+Sets initial channel key (only relevant if channel mode "k" is set).
+.TP
+\fBKeyFile\fR
+Path and file name of a "key file" containing individual channel keys for
+different users. The file consists of plain text lines with the following
+syntax (without spaces!):
+.PP
+.RS
+.RS
+.I user
+:
+.I nick
+:
+.I key
+.RE
+.PP
+.I user
+and
+.I nick
+can contain the wildcard character "*".
+.br
+.I key
+is an arbitrary password.
+.PP
+Valid examples are:
+.PP
+.RS
+*:*:KeY
+.br
+*:nick:123
+.br
+~user:*:xyz
+.RE
+.PP
+The key file is read on each JOIN command when this channel has a key
+(channel mode +k). Access is granted, if a) the channel key set using the
+MODE +k command or b) one of the lines in the key file match.
+.PP
+.B Please note:
+.br
+The file is not reopened on each access, so you can modify and overwrite it
+without problems, but moving or deleting the file will have not effect until
+the daemon re-reads its configuration!
+.RE
 .TP
 \fBMaxUsers\fR
 Set maximum user limit for this channel (only relevant if channel mode "l"
diff --git a/src/ngircd/channel.c b/src/ngircd/channel.c
index 8caa81a..3a673a2 100644
--- a/src/ngircd/channel.c
+++ b/src/ngircd/channel.c
@@ -22,6 +22,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
+#include <stdio.h>
 #include <strings.h>
 
 #include "defines.h"
@@ -39,6 +40,7 @@
 #include "lists.h"
 #include "log.h"
 #include "messages.h"
+#include "match.h"
 
 #include "exp.h"
 
@@ -59,6 +61,9 @@ static CL2CHAN *Get_First_Cl2Chan PARAMS(( CLIENT *Client, CHANNEL *Chan ));
 static CL2CHAN *Get_Next_Cl2Chan PARAMS(( CL2CHAN *Start, CLIENT *Client, CHANNEL *Chan ));
 static void Delete_Channel PARAMS(( CHANNEL *Chan ));
 static void Free_Channel PARAMS(( CHANNEL *Chan ));
+static void Update_Predefined PARAMS((CHANNEL *Chan,
+				      const struct Conf_Channel *Conf_Chan));
+static void Set_Key_File PARAMS((CHANNEL *Chan, FILE *KeyFile));
 
 
 GLOBAL void
@@ -116,8 +121,10 @@ Channel_InitPredefined( void )
 
 		new_chan = Channel_Search(conf_chan->name);
 		if (new_chan) {
-			Log(LOG_INFO, "Can't create pre-defined channel \"%s\": name already in use.",
-										conf_chan->name);
+			Log(LOG_INFO,
+			    "Can't create pre-defined channel \"%s\": name already in use.",
+			    conf_chan->name);
+			Update_Predefined(new_chan, conf_chan);
 			continue;
 		}
 
@@ -127,6 +134,8 @@ Channel_InitPredefined( void )
 							conf_chan->name);
 			continue;
 		}
+		Log(LOG_INFO, "Created pre-defined channel \"%s\"",
+						conf_chan->name);
 
 		Channel_ModeAdd(new_chan, 'P');
 
@@ -139,8 +148,7 @@ Channel_InitPredefined( void )
 
 		Channel_SetKey(new_chan, conf_chan->key);
 		Channel_SetMaxUsers(new_chan, conf_chan->maxusers);
-		Log(LOG_INFO, "Created pre-defined channel \"%s\"",
-						conf_chan->name);
+		Update_Predefined(new_chan, conf_chan);
 	}
 	if (channel_count)
 		array_free(&Conf_Channels);
@@ -153,6 +161,8 @@ Free_Channel(CHANNEL *chan)
 	array_free(&chan->topic);
 	Lists_Free(&chan->list_bans);
 	Lists_Free(&chan->list_invites);
+	if (Chan->keyfile)
+		fclose(Chan->keyfile);
 
 	free(chan);
 }
@@ -1051,6 +1061,44 @@ Channel_LogServer(char *msg)
 } /* Channel_LogServer */
 
 
+GLOBAL bool
+Channel_CheckKey(CHANNEL *Chan, CLIENT *Client, const char *Key)
+{
+	char line[COMMAND_LEN], *nick, *pass;
+
+	assert(Chan != NULL);
+	assert(Client != NULL);
+	assert(Key != NULL);
+
+	if (!strchr(Chan->modes, 'k'))
+		return true;
+	if (strcmp(Chan->key, Key) == 0)
+		return true;
+	if (!Chan->keyfile)
+		return false;
+
+	Chan->keyfile = freopen(NULL, "r", Chan->keyfile);
+	while (fgets(line, sizeof(line), Chan->keyfile) != NULL) {
+		ngt_TrimStr(line);
+		if (! (nick = strchr(line, ':')))
+			continue;
+		*nick++ = '\0';
+		if (!Match(line, Client_User(Client)))
+			continue;
+		if (! (pass = strchr(nick, ':')))
+			continue;
+		*pass++ = '\0';
+		if (!Match(nick, Client_ID(Client)))
+			continue;
+		if (strcmp(Key, pass) != 0)
+			continue;
+
+		return true;
+	}
+	return false;
+} /* Channel_CheckKey */
+
+
 static CL2CHAN *
 Get_First_Cl2Chan( CLIENT *Client, CHANNEL *Chan )
 {
@@ -1108,4 +1156,36 @@ Delete_Channel(CHANNEL *Chan)
 } /* Delete_Channel */
 
 
+static void
+Update_Predefined(CHANNEL *Chan, const struct Conf_Channel *Conf_Chan)
+{
+	FILE *fd;
+
+	if (! Conf_Chan->keyfile || ! *Conf_Chan->keyfile)
+		return;
+
+	fd = fopen(Conf_Chan->keyfile, "r");
+	if (! fd)
+		Log(LOG_ERR,
+		    "Can't open channel key file for \"%s\", \"%s\": %s",
+		    Conf_Chan->name, Conf_Chan->keyfile,
+		    strerror(errno));
+	else
+		Set_Key_File(Chan, fd);
+} /* Update_Predefined */
+
+
+static void
+Set_Key_File(CHANNEL *Chan, FILE *KeyFile)
+{
+	assert(Chan != NULL);
+
+	if (Chan->keyfile)
+		fclose(Chan->keyfile);
+	Chan->keyfile = KeyFile;
+	Log(LOG_INFO|LOG_snotice,
+	    "New local channel key file for \"%s\" activated.", Chan->name);
+} /* Set_Key_File */
+
+
 /* -eof- */
diff --git a/src/ngircd/channel.h b/src/ngircd/channel.h
index 56b1240..3aa1853 100644
--- a/src/ngircd/channel.h
+++ b/src/ngircd/channel.h
@@ -37,6 +37,7 @@ typedef struct _CHANNEL
 	unsigned long maxusers;		/* Maximum number of members (mode "l") */
 	struct list_head list_bans;	/* list head of banned users */
 	struct list_head list_invites;	/* list head of invited users */
+	FILE *keyfile;			/* handle of the channel key file */
 } CHANNEL;
 
 typedef struct _CLIENT2CHAN
@@ -127,6 +128,9 @@ GLOBAL bool Channel_ShowInvites PARAMS((CLIENT *client, CHANNEL *c));
 
 GLOBAL void Channel_LogServer PARAMS((char *msg));
 
+GLOBAL bool Channel_CheckKey PARAMS((CHANNEL *Chan, CLIENT *Client,
+				     const char *Key));
+
 #define Channel_IsLocal(c) (Channel_Name(c)[0] == '&')
 
 
diff --git a/src/ngircd/conf.c b/src/ngircd/conf.c
index fc12cd9..4a8b628 100644
--- a/src/ngircd/conf.c
+++ b/src/ngircd/conf.c
@@ -313,7 +313,8 @@ Conf_Test( void )
 		printf("  Modes = %s\n", predef_chan->modes);
 		printf("  Key = %s\n", predef_chan->key);
 		printf("  MaxUsers = %lu\n", predef_chan->maxusers);
-		printf("  Topic = %s\n\n", predef_chan->topic);
+		printf("  Topic = %s\n", predef_chan->topic);
+		printf("  KeyFile = %s\n\n", predef_chan->keyfile);
 	}
 
 	return (config_valid ? 0 : 1);
@@ -1232,6 +1233,13 @@ Handle_CHANNEL(int Line, char *Var, char *Arg)
 			Config_Error_NaN(Line, Var);
 		return;
 	}
+	if (strcasecmp(Var, "KeyFile") == 0) {
+		/* channel keys */
+		len = strlcpy(chan->keyfile, Arg, sizeof(chan->keyfile));
+		if (len >= sizeof(chan->keyfile))
+			Config_Error_TooLong(Line, Var);
+		return;
+	}
 
 	Config_Error( LOG_ERR, "%s, line %d (section \"Channel\"): Unknown variable \"%s\"!",
 								NGIRCd_ConfFile, Line, Var );
diff --git a/src/ngircd/conf.h b/src/ngircd/conf.h
index cd9cb95..4695b25 100644
--- a/src/ngircd/conf.h
+++ b/src/ngircd/conf.h
@@ -72,6 +72,7 @@ struct Conf_Channel {
 	char modes[CHANNEL_MODE_LEN];	/* Initial channel modes */
 	char key[CLIENT_PASS_LEN];      /* Channel key ("password", mode "k" ) */
 	char topic[COMMAND_LEN];	/* Initial topic */
+	char keyfile[512];		/* Path and name of channel key file */
 	unsigned long maxusers;		/* maximum usercount for this channel, mode "l" */
 };
 
diff --git a/src/ngircd/irc-channel.c b/src/ngircd/irc-channel.c
index 27414d3..6c478c8 100644
--- a/src/ngircd/irc-channel.c
+++ b/src/ngircd/irc-channel.c
@@ -89,10 +89,9 @@ join_allowed(CLIENT *Client, CLIENT *target, CHANNEL *chan,
 	}
 
 	/* Is the channel protected by a key? */
-	if (strchr(channel_modes, 'k') &&
-		strcmp(Channel_Key(chan), key ? key : ""))
-	{
-		IRC_WriteStrClient(Client, ERR_BADCHANNELKEY_MSG, Client_ID(Client), channame);
+	if (!Channel_CheckKey(chan, target, key ? key : "")) {
+		IRC_WriteStrClient(Client, ERR_BADCHANNELKEY_MSG,
+				   Client_ID(Client), channame);
 		return false;
 	}
 	/* Are there already too many members? */
-- 
1.6.1


From b7438fe357fff191527615c902119e7ecd3aad18 Mon Sep 17 00:00:00 2001
Message-Id: <b7438fe357fff191527615c902119e7ecd3aad18.1231161643.git.alex@barton.de>
In-Reply-To: <db01d503253fe10f6107cc145e5fc183a826f989.1231161643.git.alex@barton.de>
References: <db01d503253fe10f6107cc145e5fc183a826f989.1231161643.git.alex@barton.de>
From: Alexander Barton <alex@barton.de>
Date: Sun, 4 Jan 2009 17:26:50 +0100
Subject: [PATCH 2/2] Channel key file: store file name and open on each access.

Store the file name of channel key files and reopen them on each access
(on each JOIN command) insted of just storing the file handles.

This eliminates the special requirements (no delete) and makes sure
that always the actual file contents are used in all circumstances.
---
 src/ngircd/channel.c |   75 ++++++++++++++++++++++++++-----------------------
 src/ngircd/channel.h |    2 +-
 2 files changed, 41 insertions(+), 36 deletions(-)

diff --git a/src/ngircd/channel.c b/src/ngircd/channel.c
index 3a673a2..32bf281 100644
--- a/src/ngircd/channel.c
+++ b/src/ngircd/channel.c
@@ -61,9 +61,7 @@ static CL2CHAN *Get_First_Cl2Chan PARAMS(( CLIENT *Client, CHANNEL *Chan ));
 static CL2CHAN *Get_Next_Cl2Chan PARAMS(( CL2CHAN *Start, CLIENT *Client, CHANNEL *Chan ));
 static void Delete_Channel PARAMS(( CHANNEL *Chan ));
 static void Free_Channel PARAMS(( CHANNEL *Chan ));
-static void Update_Predefined PARAMS((CHANNEL *Chan,
-				      const struct Conf_Channel *Conf_Chan));
-static void Set_Key_File PARAMS((CHANNEL *Chan, FILE *KeyFile));
+static void Set_KeyFile PARAMS((CHANNEL *Chan, const char *KeyFile));
 
 
 GLOBAL void
@@ -124,7 +122,7 @@ Channel_InitPredefined( void )
 			Log(LOG_INFO,
 			    "Can't create pre-defined channel \"%s\": name already in use.",
 			    conf_chan->name);
-			Update_Predefined(new_chan, conf_chan);
+			Set_KeyFile(new_chan, conf_chan->keyfile);
 			continue;
 		}
 
@@ -148,7 +146,7 @@ Channel_InitPredefined( void )
 
 		Channel_SetKey(new_chan, conf_chan->key);
 		Channel_SetMaxUsers(new_chan, conf_chan->maxusers);
-		Update_Predefined(new_chan, conf_chan);
+		Set_KeyFile(new_chan, conf_chan->keyfile);
 	}
 	if (channel_count)
 		array_free(&Conf_Channels);
@@ -159,10 +157,9 @@ static void
 Free_Channel(CHANNEL *chan)
 {
 	array_free(&chan->topic);
+	array_free(&chan->keyfile);
 	Lists_Free(&chan->list_bans);
 	Lists_Free(&chan->list_invites);
-	if (Chan->keyfile)
-		fclose(Chan->keyfile);
 
 	free(chan);
 }
@@ -1064,7 +1061,8 @@ Channel_LogServer(char *msg)
 GLOBAL bool
 Channel_CheckKey(CHANNEL *Chan, CLIENT *Client, const char *Key)
 {
-	char line[COMMAND_LEN], *nick, *pass;
+	char *file_name, line[COMMAND_LEN], *nick, *pass;
+	FILE *fd;
 
 	assert(Chan != NULL);
 	assert(Client != NULL);
@@ -1074,11 +1072,20 @@ Channel_CheckKey(CHANNEL *Chan, CLIENT *Client, const char *Key)
 		return true;
 	if (strcmp(Chan->key, Key) == 0)
 		return true;
-	if (!Chan->keyfile)
+	if (*Key == '\0')
+		return false;
+
+	file_name = array_start(&Chan->keyfile);
+	if (!file_name)
 		return false;
+	fd = fopen(file_name, "r");
+	if (!fd) {
+		Log(LOG_ERR, "Can't open channek key file \"%s\" for %s: %s",
+		    file_name, Chan->name, strerror(errno));
+		return false;
+	}
 
-	Chan->keyfile = freopen(NULL, "r", Chan->keyfile);
-	while (fgets(line, sizeof(line), Chan->keyfile) != NULL) {
+	while (fgets(line, sizeof(line), fd) != NULL) {
 		ngt_TrimStr(line);
 		if (! (nick = strchr(line, ':')))
 			continue;
@@ -1093,8 +1100,10 @@ Channel_CheckKey(CHANNEL *Chan, CLIENT *Client, const char *Key)
 		if (strcmp(Key, pass) != 0)
 			continue;
 
+		fclose(fd);
 		return true;
 	}
+	fclose(fd);
 	return false;
 } /* Channel_CheckKey */
 
@@ -1157,35 +1166,31 @@ Delete_Channel(CHANNEL *Chan)
 
 
 static void
-Update_Predefined(CHANNEL *Chan, const struct Conf_Channel *Conf_Chan)
+Set_KeyFile(CHANNEL *Chan, const char *KeyFile)
 {
-	FILE *fd;
-
-	if (! Conf_Chan->keyfile || ! *Conf_Chan->keyfile)
-		return;
+	size_t len;
 
-	fd = fopen(Conf_Chan->keyfile, "r");
-	if (! fd)
-		Log(LOG_ERR,
-		    "Can't open channel key file for \"%s\", \"%s\": %s",
-		    Conf_Chan->name, Conf_Chan->keyfile,
-		    strerror(errno));
-	else
-		Set_Key_File(Chan, fd);
-} /* Update_Predefined */
+	assert(Chan != NULL);
+	assert(KeyFile != NULL);
 
+	len = strlen(KeyFile);
+	if (len < array_bytes(&Chan->keyfile)) {
+		Log(LOG_INFO, "Channel key file of %s removed.", Chan->name);
+		array_free(&Chan->keyfile);
+	}
 
-static void
-Set_Key_File(CHANNEL *Chan, FILE *KeyFile)
-{
-	assert(Chan != NULL);
+	if (len < 1)
+		return;
 
-	if (Chan->keyfile)
-		fclose(Chan->keyfile);
-	Chan->keyfile = KeyFile;
-	Log(LOG_INFO|LOG_snotice,
-	    "New local channel key file for \"%s\" activated.", Chan->name);
-} /* Set_Key_File */
+	if (!array_copyb(&Chan->keyfile, KeyFile, len+1))
+		Log(LOG_WARNING,
+		    "Could not set new channel key file \"%s\" for %s: %s",
+		    KeyFile, Chan->name, strerror(errno));
+	else
+		Log(LOG_INFO|LOG_snotice,
+		    "New local channel key file \"%s\" for %s activated.",
+		    KeyFile, Chan->name);
+} /* Set_KeyFile */
 
 
 /* -eof- */
diff --git a/src/ngircd/channel.h b/src/ngircd/channel.h
index 3aa1853..411c345 100644
--- a/src/ngircd/channel.h
+++ b/src/ngircd/channel.h
@@ -37,7 +37,7 @@ typedef struct _CHANNEL
 	unsigned long maxusers;		/* Maximum number of members (mode "l") */
 	struct list_head list_bans;	/* list head of banned users */
 	struct list_head list_invites;	/* list head of invited users */
-	FILE *keyfile;			/* handle of the channel key file */
+	array keyfile;			/* Name of the channel key file */
 } CHANNEL;
 
 typedef struct _CLIENT2CHAN
-- 
1.6.1

