simple RADIUS server example works
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2010 Andreas Steffen
|
* Copyright (C) 2012 Andreas Steffen
|
||||||
* HSR Hochschule fuer Technik Rapperswil
|
* HSR Hochschule fuer Technik Rapperswil
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify it
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <debug.h>
|
#include <debug.h>
|
||||||
#include <threading/thread.h>
|
#include <threading/thread.h>
|
||||||
#include <processing/jobs/callback_job.h>
|
#include <processing/jobs/callback_job.h>
|
||||||
|
#include <sa/authenticators/eap/eap_method.h>
|
||||||
|
|
||||||
typedef struct private_tnc_pdp_t private_tnc_pdp_t;
|
typedef struct private_tnc_pdp_t private_tnc_pdp_t;
|
||||||
|
|
||||||
@@ -57,6 +58,25 @@ struct private_tnc_pdp_t {
|
|||||||
*/
|
*/
|
||||||
callback_job_t *job;
|
callback_job_t *job;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RADIUS shared secret
|
||||||
|
*/
|
||||||
|
chunk_t secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MD5 hasher
|
||||||
|
*/
|
||||||
|
hasher_t *hasher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HMAC MD5 signer, with secret set
|
||||||
|
*/
|
||||||
|
signer_t *signer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EAP method
|
||||||
|
*/
|
||||||
|
eap_method_t *method;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -102,12 +122,12 @@ static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
|
|||||||
skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
|
||||||
if (skt < 0)
|
if (skt < 0)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "opening RADIUS socket failed: %s", strerror(errno));
|
DBG1(DBG_CFG, "opening RADIUS socket failed: %s", strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
|
if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
|
DBG1(DBG_CFG, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
|
||||||
close(skt);
|
close(skt);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -115,7 +135,7 @@ static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
|
|||||||
/* bind the socket */
|
/* bind the socket */
|
||||||
if (bind(skt, (struct sockaddr *)&addr, addrlen) < 0)
|
if (bind(skt, (struct sockaddr *)&addr, addrlen) < 0)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "unable to bind RADIUS socket: %s", strerror(errno));
|
DBG1(DBG_CFG, "unable to bind RADIUS socket: %s", strerror(errno));
|
||||||
close(skt);
|
close(skt);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -123,6 +143,127 @@ static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
|
|||||||
return skt;
|
return skt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a RADIUS message to client
|
||||||
|
*/
|
||||||
|
static void send_message(private_tnc_pdp_t *this, radius_message_t *message,
|
||||||
|
host_t *client)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
chunk_t data;
|
||||||
|
|
||||||
|
fd = (client->get_family(client) == AF_INET) ? this->ipv4 : this->ipv6;
|
||||||
|
data = message->get_encoding(message);
|
||||||
|
|
||||||
|
DBG2(DBG_CFG, "sending RADIUS packet to %#H", client);
|
||||||
|
DBG3(DBG_CFG, "%B", &data);
|
||||||
|
|
||||||
|
if (sendto(fd, data.ptr, data.len, 0, client->get_sockaddr(client),
|
||||||
|
*client->get_sockaddr_len(client)) != data.len)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "sending RADIUS message failed: %s", strerror(errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a RADIUS response for a request
|
||||||
|
*/
|
||||||
|
static void send_response(private_tnc_pdp_t *this,
|
||||||
|
radius_message_t *request, radius_message_code_t code,
|
||||||
|
eap_payload_t *eap, host_t *client)
|
||||||
|
{
|
||||||
|
radius_message_t *response;
|
||||||
|
chunk_t data;
|
||||||
|
|
||||||
|
response = radius_message_create(code);
|
||||||
|
if (eap)
|
||||||
|
{
|
||||||
|
data = eap->get_data(eap);
|
||||||
|
response->add(response, RAT_EAP_MESSAGE, data);
|
||||||
|
}
|
||||||
|
response->set_identifier(response, request->get_identifier(request));
|
||||||
|
response->sign(response, request->get_authenticator(request),
|
||||||
|
this->secret, this->hasher, this->signer, NULL);
|
||||||
|
|
||||||
|
DBG1(DBG_CFG, "sending RADIUS %N to client '%H'", radius_message_code_names,
|
||||||
|
code, client);
|
||||||
|
send_message(this, response, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process EAP message
|
||||||
|
*/
|
||||||
|
static void process_eap(private_tnc_pdp_t *this, radius_message_t *request,
|
||||||
|
host_t *source)
|
||||||
|
{
|
||||||
|
enumerator_t *enumerator;
|
||||||
|
eap_payload_t *in, *out = NULL;
|
||||||
|
eap_type_t eap_type;
|
||||||
|
chunk_t data, message = chunk_empty;
|
||||||
|
radius_message_code_t code = RMC_ACCESS_CHALLENGE;
|
||||||
|
u_int32_t eap_vendor;
|
||||||
|
int type;
|
||||||
|
|
||||||
|
enumerator = request->create_enumerator(request);
|
||||||
|
while (enumerator->enumerate(enumerator, &type, &data))
|
||||||
|
{
|
||||||
|
if (type == RAT_EAP_MESSAGE && data.len)
|
||||||
|
{
|
||||||
|
message = chunk_cat("mc", message, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enumerator->destroy(enumerator);
|
||||||
|
|
||||||
|
if (message.len)
|
||||||
|
{
|
||||||
|
in = eap_payload_create_data(message);
|
||||||
|
|
||||||
|
/* apply EAP method selected by RADIUS server */
|
||||||
|
eap_type = in->get_type(in, &eap_vendor);
|
||||||
|
|
||||||
|
DBG3(DBG_CFG, "%N payload %B", eap_type_names, eap_type, &message);
|
||||||
|
free(message.ptr);
|
||||||
|
|
||||||
|
if (eap_type == EAP_IDENTITY)
|
||||||
|
{
|
||||||
|
identification_t *server, *peer;
|
||||||
|
|
||||||
|
peer = identification_create_from_string("[email protected]");
|
||||||
|
server = identification_create_from_string("server");
|
||||||
|
this->method = charon->eap->create_instance(charon->eap, EAP_MD5, 0,
|
||||||
|
EAP_SERVER, server, peer);
|
||||||
|
if (!this->method)
|
||||||
|
{
|
||||||
|
peer->destroy(peer);
|
||||||
|
server->destroy(server);
|
||||||
|
in->destroy(in);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->method->initiate(this->method, &out);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (this->method->process(this->method, in, &out))
|
||||||
|
{
|
||||||
|
case NEED_MORE:
|
||||||
|
code = RMC_ACCESS_CHALLENGE;
|
||||||
|
break;
|
||||||
|
case SUCCESS:
|
||||||
|
code = RMC_ACCESS_ACCEPT;
|
||||||
|
break;
|
||||||
|
case FAILED:
|
||||||
|
default:
|
||||||
|
code = RMC_ACCESS_REJECT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send_response(this, request, code, out, source);
|
||||||
|
|
||||||
|
in->destroy(in);
|
||||||
|
DESTROY_IF(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process packets received on the RADIUS socket
|
* Process packets received on the RADIUS socket
|
||||||
*/
|
*/
|
||||||
@@ -155,7 +296,7 @@ static job_requeue_t receive(private_tnc_pdp_t *this)
|
|||||||
}
|
}
|
||||||
max_fd = max(this->ipv4, this->ipv6);
|
max_fd = max(this->ipv4, this->ipv6);
|
||||||
|
|
||||||
DBG2(DBG_NET, "waiting for data on RADIUS sockets");
|
DBG2(DBG_CFG, "waiting for data on RADIUS sockets");
|
||||||
oldstate = thread_cancelability(TRUE);
|
oldstate = thread_cancelability(TRUE);
|
||||||
if (select(max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
|
if (select(max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
|
||||||
{
|
{
|
||||||
@@ -190,26 +331,34 @@ static job_requeue_t receive(private_tnc_pdp_t *this)
|
|||||||
bytes_read = recvmsg(selected, &msg, 0);
|
bytes_read = recvmsg(selected, &msg, 0);
|
||||||
if (bytes_read < 0)
|
if (bytes_read < 0)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "error reading RADIUS socket: %s", strerror(errno));
|
DBG1(DBG_CFG, "error reading RADIUS socket: %s", strerror(errno));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (msg.msg_flags & MSG_TRUNC)
|
if (msg.msg_flags & MSG_TRUNC)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "receive buffer too small, RADIUS packet discarded");
|
DBG1(DBG_CFG, "receive buffer too small, RADIUS packet discarded");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
source = host_create_from_sockaddr((sockaddr_t*)&src);
|
source = host_create_from_sockaddr((sockaddr_t*)&src);
|
||||||
DBG2(DBG_NET, "received RADIUS packet from %#H", source);
|
DBG2(DBG_CFG, "received RADIUS packet from %#H", source);
|
||||||
DBG3(DBG_NET, "%b", buffer, bytes_read);
|
DBG3(DBG_CFG, "%b", buffer, bytes_read);
|
||||||
request = radius_message_parse_response(chunk_create(buffer, bytes_read));
|
request = radius_message_parse(chunk_create(buffer, bytes_read));
|
||||||
if (request)
|
if (request)
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "received RADIUS %N from client '%H'",
|
DBG1(DBG_CFG, "received RADIUS %N from client '%H'",
|
||||||
radius_message_code_names, request->get_code(request), source);
|
radius_message_code_names, request->get_code(request), source);
|
||||||
|
|
||||||
|
if (request->verify(request, NULL, this->secret, this->hasher,
|
||||||
|
this->signer))
|
||||||
|
{
|
||||||
|
process_eap(this, request, source);
|
||||||
|
}
|
||||||
|
request->destroy(request);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DBG1(DBG_NET, "received invalid RADIUS message, ignored");
|
DBG1(DBG_CFG, "received invalid RADIUS message, ignored");
|
||||||
}
|
}
|
||||||
source->destroy(source);
|
source->destroy(source);
|
||||||
}
|
}
|
||||||
@@ -219,7 +368,10 @@ static job_requeue_t receive(private_tnc_pdp_t *this)
|
|||||||
METHOD(tnc_pdp_t, destroy, void,
|
METHOD(tnc_pdp_t, destroy, void,
|
||||||
private_tnc_pdp_t *this)
|
private_tnc_pdp_t *this)
|
||||||
{
|
{
|
||||||
this->job->cancel(this->job);
|
if (this->job)
|
||||||
|
{
|
||||||
|
this->job->cancel(this->job);
|
||||||
|
}
|
||||||
if (this->ipv4)
|
if (this->ipv4)
|
||||||
{
|
{
|
||||||
close(this->ipv4);
|
close(this->ipv4);
|
||||||
@@ -228,6 +380,9 @@ METHOD(tnc_pdp_t, destroy, void,
|
|||||||
{
|
{
|
||||||
close(this->ipv6);
|
close(this->ipv6);
|
||||||
}
|
}
|
||||||
|
DESTROY_IF(this->signer);
|
||||||
|
DESTROY_IF(this->hasher);
|
||||||
|
DESTROY_IF(this->method);
|
||||||
free(this);
|
free(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +392,7 @@ METHOD(tnc_pdp_t, destroy, void,
|
|||||||
tnc_pdp_t *tnc_pdp_create(u_int16_t port)
|
tnc_pdp_t *tnc_pdp_create(u_int16_t port)
|
||||||
{
|
{
|
||||||
private_tnc_pdp_t *this;
|
private_tnc_pdp_t *this;
|
||||||
|
char *secret;
|
||||||
|
|
||||||
INIT(this,
|
INIT(this,
|
||||||
.public = {
|
.public = {
|
||||||
@@ -244,6 +400,8 @@ tnc_pdp_t *tnc_pdp_create(u_int16_t port)
|
|||||||
},
|
},
|
||||||
.ipv4 = open_socket(this, AF_INET, port),
|
.ipv4 = open_socket(this, AF_INET, port),
|
||||||
.ipv6 = open_socket(this, AF_INET6, port),
|
.ipv6 = open_socket(this, AF_INET6, port),
|
||||||
|
.hasher = lib->crypto->create_hasher(lib->crypto, HASH_MD5),
|
||||||
|
.signer = lib->crypto->create_signer(lib->crypto, AUTH_HMAC_MD5_128),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!this->ipv4 && !this->ipv6)
|
if (!this->ipv4 && !this->ipv6)
|
||||||
@@ -260,6 +418,21 @@ tnc_pdp_t *tnc_pdp_create(u_int16_t port)
|
|||||||
{
|
{
|
||||||
DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
|
DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
|
||||||
}
|
}
|
||||||
|
if (!this->hasher || !this->signer)
|
||||||
|
{
|
||||||
|
destroy(this);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
secret = lib->settings->get_str(lib->settings,
|
||||||
|
"charon.plugins.tnc-pdp.secret", NULL);
|
||||||
|
if (!secret)
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "missing RADIUS secret, PDP disabled");
|
||||||
|
destroy(this);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
this->secret = chunk_create(secret, strlen(secret));
|
||||||
|
this->signer->set_key(this->signer, this->secret);
|
||||||
|
|
||||||
this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
|
this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
|
||||||
this, NULL, NULL, JOB_PRIO_CRITICAL);
|
this, NULL, NULL, JOB_PRIO_CRITICAL);
|
||||||
|
|||||||
@@ -282,10 +282,14 @@ METHOD(radius_message_t, sign, void,
|
|||||||
private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
|
private_radius_message_t *this, u_int8_t *req_auth, chunk_t secret,
|
||||||
hasher_t *hasher, signer_t *signer, rng_t *rng)
|
hasher_t *hasher, signer_t *signer, rng_t *rng)
|
||||||
{
|
{
|
||||||
if (rng == NULL)
|
if (rng)
|
||||||
{
|
{
|
||||||
chunk_t msg;
|
/* build Request-Authenticator */
|
||||||
|
rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* build Response-Authenticator */
|
||||||
if (req_auth)
|
if (req_auth)
|
||||||
{
|
{
|
||||||
memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
|
memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
|
||||||
@@ -294,17 +298,14 @@ METHOD(radius_message_t, sign, void,
|
|||||||
{
|
{
|
||||||
memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator));
|
memset(this->msg->authenticator, 0, sizeof(this->msg->authenticator));
|
||||||
}
|
}
|
||||||
msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
|
|
||||||
hasher->get_hash(hasher, msg, NULL);
|
|
||||||
hasher->get_hash(hasher, secret, this->msg->authenticator);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (rng || this->msg->code == RMC_ACCESS_CHALLENGE
|
||||||
|
|| this->msg->code == RMC_ACCESS_ACCEPT
|
||||||
|
|| this->msg->code == RMC_ACCESS_REJECT)
|
||||||
{
|
{
|
||||||
char buf[HASH_SIZE_MD5];
|
char buf[HASH_SIZE_MD5];
|
||||||
|
|
||||||
/* build Request-Authenticator */
|
|
||||||
rng->get_bytes(rng, HASH_SIZE_MD5, this->msg->authenticator);
|
|
||||||
|
|
||||||
/* build Message-Authenticator attribute, using 16 null bytes */
|
/* build Message-Authenticator attribute, using 16 null bytes */
|
||||||
memset(buf, 0, sizeof(buf));
|
memset(buf, 0, sizeof(buf));
|
||||||
add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf)));
|
add(this, RAT_MESSAGE_AUTHENTICATOR, chunk_create(buf, sizeof(buf)));
|
||||||
@@ -312,6 +313,15 @@ METHOD(radius_message_t, sign, void,
|
|||||||
chunk_create((u_char*)this->msg, ntohs(this->msg->length)),
|
chunk_create((u_char*)this->msg, ntohs(this->msg->length)),
|
||||||
((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5);
|
((u_char*)this->msg) + ntohs(this->msg->length) - HASH_SIZE_MD5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!rng)
|
||||||
|
{
|
||||||
|
chunk_t msg;
|
||||||
|
|
||||||
|
msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
|
||||||
|
hasher->get_hash(hasher, msg, NULL);
|
||||||
|
hasher->get_hash(hasher, secret, this->msg->authenticator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
METHOD(radius_message_t, verify, bool,
|
METHOD(radius_message_t, verify, bool,
|
||||||
@@ -324,25 +334,29 @@ METHOD(radius_message_t, verify, bool,
|
|||||||
chunk_t data, msg;
|
chunk_t data, msg;
|
||||||
bool has_eap = FALSE, has_auth = FALSE;
|
bool has_eap = FALSE, has_auth = FALSE;
|
||||||
|
|
||||||
/* replace Response by Request Authenticator for verification */
|
|
||||||
memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
|
|
||||||
if (req_auth)
|
|
||||||
{
|
|
||||||
memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
|
|
||||||
}
|
|
||||||
msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
|
msg = chunk_create((u_char*)this->msg, ntohs(this->msg->length));
|
||||||
|
|
||||||
/* verify Response-Authenticator */
|
if (this->msg->code != RMC_ACCESS_REQUEST)
|
||||||
hasher->get_hash(hasher, msg, NULL);
|
|
||||||
hasher->get_hash(hasher, secret, buf);
|
|
||||||
if (!memeq(buf, res_auth, HASH_SIZE_MD5))
|
|
||||||
{
|
{
|
||||||
DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
|
/* replace Response by Request Authenticator for verification */
|
||||||
return FALSE;
|
memcpy(res_auth, this->msg->authenticator, HASH_SIZE_MD5);
|
||||||
|
if (req_auth)
|
||||||
|
{
|
||||||
|
memcpy(this->msg->authenticator, req_auth, HASH_SIZE_MD5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memset(this->msg->authenticator, 0, HASH_SIZE_MD5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* verify Response-Authenticator */
|
||||||
|
hasher->get_hash(hasher, msg, NULL);
|
||||||
|
hasher->get_hash(hasher, secret, buf);
|
||||||
|
if (!memeq(buf, res_auth, HASH_SIZE_MD5))
|
||||||
|
{
|
||||||
|
DBG1(DBG_CFG, "RADIUS Response-Authenticator verification failed");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* verify Message-Authenticator attribute */
|
/* verify Message-Authenticator attribute */
|
||||||
@@ -380,8 +394,12 @@ METHOD(radius_message_t, verify, bool,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
enumerator->destroy(enumerator);
|
enumerator->destroy(enumerator);
|
||||||
/* restore Response-Authenticator */
|
|
||||||
memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
|
if (this->msg->code != RMC_ACCESS_REQUEST)
|
||||||
|
{
|
||||||
|
/* restore Response-Authenticator */
|
||||||
|
memcpy(this->msg->authenticator, res_auth, HASH_SIZE_MD5);
|
||||||
|
}
|
||||||
|
|
||||||
if (has_eap && !has_auth)
|
if (has_eap && !has_auth)
|
||||||
{ /* Message-Authenticator is required if we have an EAP-Message */
|
{ /* Message-Authenticator is required if we have an EAP-Message */
|
||||||
|
|||||||
Reference in New Issue
Block a user