Index: doc/user-guide/commands.xml
===================================================================
--- doc/user-guide/commands.xml	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ doc/user-guide/commands.xml	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -1620,4 +1620,18 @@
 	</bitlbee-setting>
 
+	<bitlbee-setting name="utf8_nicks" type="boolean" scope="global">
+		<default>false</default>
+
+		<description>
+			<para>
+				Officially, IRC nicknames are restricted to ASCII. Recently some clients and servers started supporting Unicode nicknames though. To enable UTF-8 nickname support (contacts only) in BitlBee, enable this setting.
+			</para>
+			
+			<para>
+				To avoid confusing old clients, this setting is disabled by default. Be careful when you try it, and be prepared to be locked out of your BitlBee in case your client interacts poorly with UTF-8 nicknames.
+			</para>
+		</description>
+	</bitlbee-setting>
+
 	<bitlbee-setting name="web_aware" type="string" scope="account">
 		<default>false</default>
Index: ipc.c
===================================================================
--- ipc.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ ipc.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -152,5 +152,5 @@
 		old = l->data;
 		if( child != old &&
-		    old->nick && nick_cmp( old->nick, child->nick ) == 0 &&
+		    old->nick && nick_cmp( NULL, old->nick, child->nick ) == 0 &&
 		    old->password && strcmp( old->password, child->password ) == 0 )
 			break;
@@ -298,5 +298,5 @@
 		return;
 	
-	if( nick_cmp( cmd[1], irc->user->nick ) != 0 )
+	if( nick_cmp( NULL, cmd[1], irc->user->nick ) != 0 )
 		return;		/* It's not for us. */
 	
Index: irc.c
===================================================================
--- irc.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ irc.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -35,4 +35,5 @@
 static char *set_eval_password( set_t *set, char *value );
 static char *set_eval_bw_compat( set_t *set, char *value );
+static char *set_eval_utf8_nicks( set_t *set, char *value );
 
 irc_t *irc_new( int fd )
@@ -134,4 +135,5 @@
 	s = set_add( &b->set, "to_char", ": ", set_eval_to_char, irc );
 	s = set_add( &b->set, "typing_notice", "false", set_eval_bool, irc );
+	s = set_add( &b->set, "utf8_nicks", "false", set_eval_utf8_nicks, irc );
 
 	irc->root = iu = irc_user_new( irc, ROOT_NICK );
@@ -962,4 +964,21 @@
 }
 
+static char *set_eval_utf8_nicks( set_t *set, char *value )
+{
+	irc_t *irc = set->data;
+	gboolean val = bool2int( value );
+	
+	/* Do *NOT* unset this flag in the middle of a session. There will
+	   be UTF-8 nicks around already so if we suddenly disable support
+	   for them, various functions might behave strangely. */
+	if( val )
+		irc->status |= IRC_UTF8_NICKS;
+	else if( irc->status & IRC_UTF8_NICKS )
+		irc_rootmsg( irc, "You need to reconnect to BitlBee for this "
+		                  "change to take effect." );
+	
+	return set_eval_bool( set, value );
+}
+
 void register_irc_plugin( const struct irc_plugin *p )
 {
Index: irc.h
===================================================================
--- irc.h	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ irc.h	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -62,4 +62,6 @@
 	OPER_HACK_ACCOUNT_ADD = 0x400,
 	OPER_HACK_ANY = 0x3700,	/* To check for them all at once. */
+	
+	IRC_UTF8_NICKS = 0x10000, /* Disable ASCII restrictions on buddy nicks. */
 } irc_status_t;
 
Index: irc_commands.c
===================================================================
--- irc_commands.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ irc_commands.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -80,5 +80,5 @@
 		irc_send_num( irc, 433, "%s :This nick is already in use", cmd[1] );
 	}
-	else if( !nick_ok( cmd[1] ) )
+	else if( !nick_ok( NULL, cmd[1] ) )
 	{
 		/* [SH] Invalid characters. */
@@ -291,5 +291,5 @@
 	else
 	{
-		if( nick_cmp( cmd[1], irc->user->nick ) == 0 )
+		if( nick_cmp( NULL, cmd[1], irc->user->nick ) == 0 )
 		{
 			if( cmd[2] )
@@ -392,5 +392,5 @@
 	/* At least for now just echo. IIRC some IRC clients use self-notices
 	   for lag checks, so try to support that. */
-	if( nick_cmp( cmd[1], irc->user->nick ) == 0 )
+	if( nick_cmp( NULL, cmd[1], irc->user->nick ) == 0 )
 		irc_send_msg( irc->user, "NOTICE", irc->user->nick, cmd[2], NULL );
 	else if( ( iu = irc_user_by_name( irc, cmd[1] ) ) )
@@ -593,5 +593,5 @@
 		
 		nick = g_strdup( cmd[i] + 1 );
-		nick_lc( nick );
+		nick_lc( irc, nick );
 		
 		iu = irc_user_by_name( irc, nick );
Index: irc_im.c
===================================================================
--- irc_im.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ irc_im.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -697,5 +697,5 @@
 	irc_channel_name_strip( stripped );
 	if( set_getbool( &bee->set, "lcnicks" ) )
-		nick_lc( stripped );
+		nick_lc( irc, stripped );
 	
 	if( stripped[0] == '\0' )
Index: irc_user.c
===================================================================
--- irc_user.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ irc_user.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -36,5 +36,5 @@
 	
 	iu->key = g_strdup( nick );
-	nick_lc( iu->key );
+	nick_lc( irc, iu->key );
 	/* Using the hash table for speed and irc->users for easy iteration
 	   through the list (since the GLib API doesn't have anything sane
@@ -107,5 +107,5 @@
 	
 	strcpy( key, nick );
-	if( nick_lc( key ) )
+	if( nick_lc( irc, key ) )
 		return g_hash_table_lookup( irc->nick_user_hash, key );
 	else
@@ -121,5 +121,5 @@
 	
 	strcpy( key, new );
-	if( iu == NULL || !nick_lc( key ) ||
+	if( iu == NULL || !nick_lc( irc, key ) ||
 	    ( ( new_iu = irc_user_by_name( irc, new ) ) && new_iu != iu ) )
 		return 0;
Index: nick.c
===================================================================
--- nick.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ nick.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -51,9 +51,10 @@
 {
 	char *store_handle, *store_nick = g_malloc( MAX_NICK_LENGTH + 1 );
+	irc_t *irc = (irc_t *) acc->bee->ui_data;
 	
 	store_handle = clean_handle( handle );
 	store_nick[MAX_NICK_LENGTH] = '\0';
 	strncpy( store_nick, nick, MAX_NICK_LENGTH );
-	nick_strip( store_nick );
+	nick_strip( irc, store_nick );
 	
 	g_hash_table_replace( acc->nicks, store_handle, store_nick );
@@ -69,4 +70,5 @@
 	static char nick[MAX_NICK_LENGTH+1];
 	char *store_handle, *found_nick;
+	irc_t *irc = (irc_t *) bu->bee->ui_data;
 	
 	memset( nick, 0, MAX_NICK_LENGTH + 1 );
@@ -94,7 +96,7 @@
 				*(s++) = 0;
 		
-		nick_strip( nick );
+		nick_strip( irc, nick );
 		if( set_getbool( &bu->bee->set, "lcnicks" ) )
-			nick_lc( nick );
+			nick_lc( irc, nick );
 	}
 	g_free( store_handle );
@@ -110,5 +112,7 @@
 {
 	gboolean ok = FALSE; /* Set to true once the nick contains something unique. */
-	GString *ret = g_string_new( "" );
+	GString *ret = g_string_sized_new( MAX_NICK_LENGTH + 1 );
+	char *rets;
+	irc_t *irc = (irc_t *) bu->bee->ui_data;
 	char *fmt = set_getstr( &bu->ic->acc->set, "nick_format" ) ? :
 	            set_getstr( &bu->bee->set, "nick_format" );
@@ -117,5 +121,4 @@
 	{
 		char *part = NULL, chop = '\0', *asc = NULL;
-		int len = MAX_NICK_LENGTH;
 		
 		if( *fmt != '%' )
@@ -140,11 +143,4 @@
 				fmt += 2;
 			}
-			else if( isdigit( *fmt ) )
-			{
-				len = 0;
-				/* Grab a number. */
-				while( isdigit( *fmt ) )
-					len = len * 10 + ( *(fmt++) - '0' );
-			}
 			else if( g_strncasecmp( fmt, "nick", 4 ) == 0 )
 			{
@@ -195,29 +191,29 @@
 		}
 		
+		if( !part )
+			continue;
+		
 		/* Credits to Josay_ in #bitlbee for this idea. //TRANSLIT
 		   should do lossy/approximate conversions, so letters with
 		   accents don't just get stripped. Note that it depends on
 		   LC_CTYPE being set to something other than C/POSIX. */
-		if( part )
+		if( !( irc && irc->status & IRC_UTF8_NICKS ) )
 			part = asc = g_convert_with_fallback( part, -1, "ASCII//TRANSLIT",
 			                                      "UTF-8", "", NULL, NULL, NULL );
 		
-		if( ret->len == 0 && part && isdigit( *part ) )
-			g_string_append_c( ret, '_' );
-		
-		while( part && *part && *part != chop && len > 0 )
-		{
-			if( strchr( nick_lc_chars, *part ) ||
-			    strchr( nick_uc_chars, *part ) )
-				g_string_append_c( ret, *part );
-			
-			part ++;
-			len --;
-		}
+		if( part )
+			g_string_append( ret, part );
 		g_free( asc );
 	}
 	
-	/* This returns NULL if the nick is empty or otherwise not ok. */
-	return g_string_free( ret, ret->len == 0 || !ok );
+	rets = g_string_free( ret, FALSE );
+	if( ok && rets && *rets )
+	{
+		nick_strip( irc, rets );
+		rets[MAX_NICK_LENGTH] = '\0';
+		return rets;
+	}
+	g_free( rets );
+	return NULL;
 }
 
@@ -230,5 +226,5 @@
 	/* Now, find out if the nick is already in use at the moment, and make
 	   subtle changes to make it unique. */
-	while( !nick_ok( nick ) ||
+	while( !nick_ok( irc, nick ) ||
 	       ( ( iu = irc_user_by_name( irc, nick ) ) && iu->bu != bu ) )
 	{
@@ -245,21 +241,12 @@
 		if( inf_protection-- == 0 )
 		{
-			int i;
+			g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() );
 			
-			irc_rootmsg( irc, "Warning: Almost had an infinite loop in nick_get()! "
-			                  "This used to be a fatal BitlBee bug, but we tried to fix it. "
-			                  "This message should *never* appear anymore. "
-			                  "If it does, please *do* send us a bug report! "
-			                  "Please send all the following lines in your report:" );
-			
-			irc_rootmsg( irc, "Trying to get a sane nick for handle %s", bu->handle );
-			for( i = 0; i < MAX_NICK_LENGTH; i ++ )
-				irc_rootmsg( irc, "Char %d: %c/%d", i, nick[i], nick[i] );
-			
-			irc_rootmsg( irc, "FAILED. Returning an insane nick now. Things might break. "
-			                  "Good luck, and please don't forget to paste the lines up here "
-			                  "in #bitlbee on OFTC or in a mail to wilmer@gaast.net" );
-			
-			g_snprintf( nick, MAX_NICK_LENGTH + 1, "xx%x", rand() );
+			irc_rootmsg( irc, "Warning: Something went wrong while trying "
+			                  "to generate a nickname for contact %s on %s.",
+			                  bu->handle, bu->ic->acc->tag );
+			irc_rootmsg( irc, "This might be a bug in BitlBee, or the result "
+			                  "of a faulty nick_format setting. Will use %s "
+			                  "instead.", nick );
 			
 			break;
@@ -287,15 +274,43 @@
 
 
-void nick_strip( char *nick )
-{
-	int i, j;
-	
-	for( i = j = 0; nick[i] && j < MAX_NICK_LENGTH; i++ )
-	{
-		if( strchr( nick_lc_chars, nick[i] ) || 
-		    strchr( nick_uc_chars, nick[i] ) )
-		{
-			nick[j] = nick[i];
-			j++;
+void nick_strip( irc_t *irc, char *nick )
+{
+	int len = 0;
+	
+	if( irc && ( irc->status & IRC_UTF8_NICKS ) )
+	{
+		gunichar c;
+		char *p = nick, *n, tmp[strlen(nick)+1];
+		
+		while( p && *p )
+		{
+			c = g_utf8_get_char_validated( p, -1 );
+			n = g_utf8_find_next_char( p, NULL );
+			
+			if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
+			                     strchr( nick_uc_chars, c ) ) ) ||
+			    !g_unichar_isgraph( c ) )
+			{
+				strcpy( tmp, n );
+				strcpy( p, tmp );
+			}
+			else
+				p = n;
+		}
+		if( p )
+			len = p - nick;
+	}
+	else
+	{
+		int i;
+		
+		for( i = len = 0; nick[i] && len < MAX_NICK_LENGTH; i++ )
+		{
+			if( strchr( nick_lc_chars, nick[i] ) || 
+			    strchr( nick_uc_chars, nick[i] ) )
+			{
+				nick[len] = nick[i];
+				len++;
+			}
 		}
 	}
@@ -304,14 +319,16 @@
 		char *orig;
 		
+		/* First character of a nick can't be a digit, so insert an
+		   underscore if necessary. */
 		orig = g_strdup( nick );
 		g_snprintf( nick, MAX_NICK_LENGTH, "_%s", orig );
 		g_free( orig );
-		j ++;
-	}
-	while( j <= MAX_NICK_LENGTH )
-		nick[j++] = '\0';
-}
-
-int nick_ok( const char *nick )
+		len ++;
+	}
+	while( len <= MAX_NICK_LENGTH )
+		nick[len++] = '\0';
+}
+
+gboolean nick_ok( irc_t *irc, const char *nick )
 {
 	const char *s;
@@ -319,14 +336,36 @@
 	/* Empty/long nicks are not allowed, nor numbers at [0] */
 	if( !*nick || isdigit( nick[0] ) || strlen( nick ) > MAX_NICK_LENGTH )
-		return( 0 );
-	
-	for( s = nick; *s; s ++ )
-		if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) )
-			return( 0 );
-	
-	return( 1 );
-}
-
-int nick_lc( char *nick )
+		return 0;
+	
+	if( irc && ( irc->status & IRC_UTF8_NICKS ) )
+	{
+		gunichar c;
+		const char *p = nick, *n;
+		
+		while( p && *p )
+		{
+			c = g_utf8_get_char_validated( p, -1 );
+			n = g_utf8_find_next_char( p, NULL );
+			
+			if( ( c < 0x7f && !( strchr( nick_lc_chars, c ) ||
+			                     strchr( nick_uc_chars, c ) ) ) ||
+			    !g_unichar_isgraph( c ) )
+			{
+				return FALSE;
+			}
+			p = n;
+		}
+	}
+	else
+	{
+		for( s = nick; *s; s ++ )
+			if( !strchr( nick_lc_chars, *s ) && !strchr( nick_uc_chars, *s ) )
+				return FALSE;
+	}
+	
+	return TRUE;
+}
+
+int nick_lc( irc_t *irc, char *nick )
 {
 	static char tab[128] = { 0 };
@@ -340,39 +379,24 @@
 		}
 	
+	if( irc && ( irc->status & IRC_UTF8_NICKS ) )
+	{
+		gchar *down = g_utf8_strdown( nick, -1 );
+		if( strlen( down ) > strlen( nick ) )
+		{
+			/* Well crap. Corrupt it if we have to. */
+			down[strlen(nick)] = '\0';
+		}
+		strcpy( nick, down );
+		g_free( down );
+	}
+	
 	for( i = 0; nick[i]; i ++ )
-	{
-		if( !tab[(int)nick[i]] )
-			return( 0 );
-		
-		nick[i] = tab[(int)nick[i]];
-	}
-	
-	return( 1 );
-}
-
-int nick_uc( char *nick )
-{
-	static char tab[128] = { 0 };
-	int i;
-	
-	if( tab['A'] == 0 )
-		for( i = 0; nick_lc_chars[i]; i ++ )
-		{
-			tab[(int)nick_uc_chars[i]] = nick_uc_chars[i];
-			tab[(int)nick_lc_chars[i]] = nick_uc_chars[i];
-		}
-	
-	for( i = 0; nick[i]; i ++ )
-	{
-		if( !tab[(int)nick[i]] )
-			return( 0 );
-		
-		nick[i] = tab[(int)nick[i]];
-	}
-	
-	return( 1 );
-}
-
-int nick_cmp( const char *a, const char *b )
+		if( nick[i] < 0x7f )
+			nick[i] = tab[(int)nick[i]];
+	
+	return nick_ok( irc, nick );
+}
+
+int nick_cmp( irc_t *irc, const char *a, const char *b )
 {
 	char aa[1024] = "", bb[1024] = "";
@@ -380,5 +404,5 @@
 	strncpy( aa, a, sizeof( aa ) - 1 );
 	strncpy( bb, b, sizeof( bb ) - 1 );
-	if( nick_lc( aa ) && nick_lc( bb ) )
+	if( nick_lc( irc, aa ) && nick_lc( irc, bb ) )
 	{
 		return( strcmp( aa, bb ) );
@@ -389,7 +413,2 @@
 	}
 }
-
-char *nick_dup( const char *nick )
-{
-	return g_strndup( nick, MAX_NICK_LENGTH );
-}
Index: nick.h
===================================================================
--- nick.h	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ nick.h	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -31,9 +31,9 @@
 int nick_saved( bee_user_t *bu );
 void nick_del( bee_user_t *bu );
-void nick_strip( char *nick );
 
-int nick_ok( const char *nick );
-int nick_lc( char *nick );
-int nick_uc( char *nick );
-int nick_cmp( const char *a, const char *b );
+void nick_strip( irc_t *irc, char *nick );
+gboolean nick_ok( irc_t *irc, const char *nick );
+int nick_lc( irc_t *irc, char *nick );
+int nick_uc( irc_t *irc, char *nick );
+int nick_cmp( irc_t *irc, const char *a, const char *b );
 char *nick_dup( const char *nick );
Index: root_commands.c
===================================================================
--- root_commands.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ root_commands.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -702,5 +702,5 @@
 	if( cmd[3] )
 	{
-		if( !nick_ok( cmd[3] ) )
+		if( !nick_ok( irc, cmd[3] ) )
 		{
 			irc_rootmsg( irc, "The requested nick `%s' is invalid", cmd[3] );
@@ -844,5 +844,5 @@
 		irc_rootmsg( irc, "Use /nick to change your own nickname" );
 	}
-	else if( !nick_ok( cmd[2] ) )
+	else if( !nick_ok( irc, cmd[2] ) )
 	{
 		irc_rootmsg( irc, "Nick `%s' is invalid", cmd[2] );
Index: storage_xml.c
===================================================================
--- storage_xml.c	(revision ab19567e25a35beb23f922303d1f60ed13228356)
+++ storage_xml.c	(revision 1a2c1c0c413ea1124544cdc9a24a0d2faa5dbb8f)
@@ -181,5 +181,5 @@
 	strncpy( xd->given_nick, my_nick, MAX_NICK_LENGTH );
 	xd->given_nick[MAX_NICK_LENGTH] = '\0';
-	nick_lc( xd->given_nick );
+	nick_lc( NULL, xd->given_nick );
 	xd->given_pass = (char*) password;
 	
@@ -368,5 +368,5 @@
 	
 	path2 = g_strdup( irc->user->nick );
-	nick_lc( path2 );
+	nick_lc( NULL, path2 );
 	g_snprintf( path, sizeof( path ) - 20, "%s%s%s", global.conf->configdir, path2, ".xml" );
 	g_free( path2 );
@@ -424,5 +424,5 @@
 
 	lc = g_strdup( nick );
-	nick_lc( lc );
+	nick_lc( NULL, lc );
 	g_snprintf( s, 511, "%s%s%s", global.conf->configdir, lc, ".xml" );
 	g_free( lc );
