Commit message: Fix AUTHENTICATE bug
CVE: CVE-2016-7144
CWE: CWE-287
Vulnerability description: The m_authenticate function in modules/m_sasl.c in UnrealIRCd before 3.2.10.7 and 4.x before 4.0.6 allows remote attackers to spoof certificate fingerprints and consequently log in as another user via a crafted AUTHENTICATE parameter.

Project: unrealircd

CMD_FUNC(m_svslogin)
{
	if (!SASL_SERVER || MyClient(sptr) || (parc < 3) || !parv[3])
		return 0;

	if (!stricmp(parv[1], me.name))
	{
		aClient *target_p;

		/* is the PUID valid? */
		if ((target_p = decode_puid(parv[2])) == NULL)
			return 0;

		if (target_p->user == NULL)
			make_user(target_p);

		strlcpy(target_p->user->svid, parv[3], sizeof(target_p->user->svid));

		sendto_one(target_p, err_str(RPL_LOGGEDIN), me.name,
			   BadPtr(target_p->name) ? "*" : target_p->name,
			   BadPtr(target_p->name) ? "*" : target_p->name,
			   BadPtr(target_p->user->username) ? "*" : target_p->user->username,
			   BadPtr(target_p->user->realhost) ? "*" : target_p->user->realhost,
			   target_p->user->svid, target_p->user->svid);

		return 0;
	}

	/* not for us; propagate. */
	sendto_server(cptr, 0, 0, ":%s SVSLOGIN %s %s %s",
	    sptr->name, parv[1], parv[2], parv[3]);

	return 0;
}

Project: unrealircd

CMD_FUNC(m_sasl)
{
	if (!SASL_SERVER || MyClient(sptr) || (parc < 4) || !parv[4])
		return 0;

	if (!stricmp(parv[1], me.name))
	{
		aClient *target_p;

		/* is the PUID valid? */
		if ((target_p = decode_puid(parv[2])) == NULL)
			return 0;

		if (target_p->user == NULL)
			make_user(target_p);

		/* reject if another SASL agent is answering */
		if (*target_p->local->sasl_agent && stricmp(sptr->name, target_p->local->sasl_agent))
			return 0;
		else
			strlcpy(target_p->local->sasl_agent, sptr->name, sizeof(target_p->local->sasl_agent));

		if (*parv[3] == 'C')
			sendto_one(target_p, "AUTHENTICATE %s", parv[4]);
		else if (*parv[3] == 'D')
		{
			if (*parv[4] == 'F')
				sendto_one(target_p, err_str(ERR_SASLFAIL), me.name, BadPtr(target_p->name) ? "*" : target_p->name);
			else if (*parv[4] == 'S')
			{
				target_p->local->sasl_complete++;
				sendto_one(target_p, err_str(RPL_SASLSUCCESS), me.name, BadPtr(target_p->name) ? "*" : target_p->name);
			}

			*target_p->local->sasl_agent = '\0';
		}
		else if (*parv[3] == 'M')
			sendto_one(target_p, err_str(RPL_SASLMECHS), me.name, BadPtr(target_p->name) ? "*" : target_p->name, parv[4]);

		return 0;
	}

	/* not for us; propagate. */
	sendto_server(cptr, 0, 0, ":%s SASL %s %s %c %s %s",
	    sptr->name, parv[1], parv[2], *parv[3], parv[4], parc > 5 ? parv[5] : "");

	return 0;
}

Project: unrealircd

MOD_LOAD(m_sasl)
{
	return MOD_SUCCESS;
}

Project: unrealircd

MOD_UNLOAD(m_sasl)
{
	return MOD_SUCCESS;
}

Project: unrealircd

static int abort_sasl(aClient *cptr)
{
	if (cptr->local->sasl_out == 0 || cptr->local->sasl_complete)
		return 0;

	cptr->local->sasl_out = cptr->local->sasl_complete = 0;
	sendto_one(cptr, err_str(ERR_SASLABORTED), me.name, BadPtr(cptr->name) ? "*" : cptr->name);

	if (*cptr->local->sasl_agent)
	{
		aClient *agent_p = find_client(cptr->local->sasl_agent, NULL);

		if (agent_p != NULL)
		{
			sendto_server(NULL, 0, 0, ":%s SASL %s %s D A",
			    me.name, AGENT_SID(agent_p), encode_puid(cptr));
			return 0;
		}
	}

	sendto_server(NULL, 0, 0, ":%s SASL * %s D A", me.name, encode_puid(cptr));
	return 0;
}

Project: unrealircd

static aClient *decode_puid(char *puid)
{
	aClient *cptr;
	char *it, *it2;
	int cookie = 0;

	if ((it = strrchr(puid, '!')) == NULL)
		return NULL;

	*it++ = '\0';

	if ((it2 = strrchr(it, '.')) != NULL)
	{
		*it2++ = '\0';
		cookie = atoi(it2);
	}

	if (stricmp(me.name, puid))
		return NULL;

	list_for_each_entry(cptr, &unknown_list, lclient_node)
		if (cptr->local->sasl_cookie == cookie)
			return cptr;

	return NULL;
}

Project: unrealircd

static const char *encode_puid(aClient *client)
{
	static char buf[HOSTLEN + 20];

	/* create a cookie if necessary (and in case getrandom16 returns 0, then run again) */
	while (!client->local->sasl_cookie)
		client->local->sasl_cookie = getrandom16();

	snprintf(buf, sizeof buf, "%s!0.%d", me.name, client->local->sasl_cookie);

	return buf;
}

Project: unrealircd

int sasl_capability_visible(void)
{
	if (!SASL_SERVER || !find_server(SASL_SERVER, NULL))
		return 0;
	
	return 1;
}

Project: unrealircd

int sasl_connect(aClient *sptr)
{
	return abort_sasl(sptr);
}