mirror of
https://github.com/outbackdingo/sysadm.git
synced 2026-01-27 10:20:26 +00:00
Merge branch 'master' of github.com:pcbsd/sysadm
This commit is contained in:
@@ -4,27 +4,29 @@ QT += core network
|
||||
TARGET=sysadm
|
||||
target.path = /usr/local/lib
|
||||
|
||||
DESTDIR= $$_PRO_FILE_PWD_
|
||||
#DESTDIR= $$_PRO_FILE_PWD_
|
||||
|
||||
TEMPLATE = lib
|
||||
LANGUAGE = C++
|
||||
VERSION = 1.0.0
|
||||
|
||||
HEADERS += sysadm-global.h \
|
||||
sysadm-general.h \
|
||||
sysadm-lifepreserver.h \
|
||||
sysadm-general.h \
|
||||
sysadm-lifepreserver.h \
|
||||
sysadm-network.h \
|
||||
sysadm-firewall.h \
|
||||
sysadm-update.h \
|
||||
sysadm-usermanager.h
|
||||
sysadm-firewall.h \
|
||||
sysadm-servicemanager.h\
|
||||
sysadm-update.h \
|
||||
sysadm-usermanager.h
|
||||
|
||||
SOURCES += NetDevice.cpp \
|
||||
sysadm-general.cpp \
|
||||
sysadm-lifepreserver.cpp \
|
||||
sysadm-network.cpp \
|
||||
sysadm-firewall.cpp \
|
||||
sysadm-update.cpp \
|
||||
sysadm-usermanager.cpp
|
||||
sysadm-general.cpp \
|
||||
sysadm-lifepreserver.cpp \
|
||||
sysadm-network.cpp \
|
||||
sysadm-firewall.cpp \
|
||||
sysadm-servicemanager.cpp \
|
||||
sysadm-update.cpp \
|
||||
sysadm-usermanager.cpp
|
||||
|
||||
include.path=/usr/local/include/
|
||||
include.files=sysadm-*.h
|
||||
|
||||
@@ -10,6 +10,20 @@
|
||||
#include <algorithm>
|
||||
|
||||
using namespace sysadm;
|
||||
|
||||
Firewall::Firewall()
|
||||
{
|
||||
readServicesFile();
|
||||
LoadOpenPorts();
|
||||
|
||||
firewallService = serviceManager.GetService("ipfw");
|
||||
}
|
||||
|
||||
Firewall::~Firewall()
|
||||
{
|
||||
delete portStrings;
|
||||
}
|
||||
|
||||
PortInfo Firewall::LookUpPort(int port, QString type)
|
||||
{
|
||||
//Make sure that the port is valid
|
||||
@@ -74,7 +88,7 @@ PortInfo Firewall::LookUpPort(int port, QString type)
|
||||
returnValue.Description = description;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return returnValue;
|
||||
|
||||
}
|
||||
@@ -122,52 +136,30 @@ bool Firewall::IsRunning()
|
||||
|
||||
void Firewall::Start()
|
||||
{
|
||||
Enable();
|
||||
|
||||
QStringList args;
|
||||
args << "start";
|
||||
General::RunCommand("/etc/rc.d/ipfw",args);
|
||||
serviceManager.Enable(firewallService);
|
||||
serviceManager.Start(firewallService);
|
||||
}
|
||||
|
||||
void Firewall::Stop()
|
||||
{
|
||||
Enable();
|
||||
QStringList args;
|
||||
args << "stop";
|
||||
General::RunCommand("/etc/rc.d/ipfw",args);
|
||||
serviceManager.Enable(firewallService);
|
||||
serviceManager.Stop(firewallService);
|
||||
}
|
||||
|
||||
void Firewall::Restart()
|
||||
{
|
||||
Enable();
|
||||
|
||||
QStringList args;
|
||||
args << "restart";
|
||||
General::RunCommand("/etc/rc.d/ipfw",args);
|
||||
serviceManager.Enable(firewallService);
|
||||
serviceManager.Restart(firewallService);
|
||||
}
|
||||
|
||||
void Firewall::Enable()
|
||||
{
|
||||
//check if rc.conf has firewall_enable="YES"
|
||||
QStringList rcConf = General::readTextFile("/etc/rc.conf");
|
||||
if (rcConf.filter("firewall_enabled=\"YES\"").size() == 0)
|
||||
{
|
||||
rcConf.removeAll("firewall_enable=\"NO\"");
|
||||
rcConf.append("firewall_enabled=\"YES\"");
|
||||
General::writeTextFile("/etc/rc.conf",rcConf);
|
||||
}
|
||||
serviceManager.Enable(firewallService);
|
||||
}
|
||||
|
||||
void Firewall::Disable()
|
||||
{
|
||||
//check if rc.conf has firewall_enable="NO"
|
||||
QStringList rcConf = General::readTextFile("/etc/rc.conf");
|
||||
if (rcConf.filter("firewall_enabled=\"NO\"").size() == 0)
|
||||
{
|
||||
rcConf.removeAll("firewall_enable=\"YES\"");
|
||||
rcConf.append("firewall_enabled=\"NO\"");
|
||||
General::writeTextFile("/etc/rc.conf",rcConf);
|
||||
}
|
||||
serviceManager.Disable(firewallService);
|
||||
}
|
||||
|
||||
void Firewall::RestoreDefaults()
|
||||
@@ -201,17 +193,6 @@ void Firewall::RestoreDefaults()
|
||||
LoadOpenPorts();
|
||||
}
|
||||
|
||||
Firewall::Firewall()
|
||||
{
|
||||
readServicesFile();
|
||||
LoadOpenPorts();
|
||||
}
|
||||
|
||||
Firewall::~Firewall()
|
||||
{
|
||||
delete portStrings;
|
||||
}
|
||||
|
||||
void Firewall::readServicesFile()
|
||||
{
|
||||
portStrings = new QStringList();
|
||||
@@ -280,4 +261,4 @@ void Firewall::SaveOpenPorts()
|
||||
General::RunCommand("sh",args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define PORTLOOKUP_H
|
||||
#include <QtCore>
|
||||
#include <tuple>
|
||||
#include "sysadm-servicemanager.h"
|
||||
namespace sysadm
|
||||
{
|
||||
struct PortInfo{
|
||||
@@ -34,6 +35,11 @@ class Firewall
|
||||
{
|
||||
|
||||
public:
|
||||
///#section: ctors dtors
|
||||
Firewall();
|
||||
~Firewall();
|
||||
///#endsection
|
||||
|
||||
///#section: port commands
|
||||
/**
|
||||
* @description Returns a structure containing information about the port
|
||||
@@ -112,11 +118,6 @@ public:
|
||||
void RestoreDefaults();
|
||||
///#endsection
|
||||
|
||||
///#section: ctors dtors
|
||||
Firewall();
|
||||
~Firewall();
|
||||
///#endsection
|
||||
|
||||
private:
|
||||
void readServicesFile();
|
||||
QStringList* portStrings;
|
||||
@@ -125,7 +126,10 @@ private:
|
||||
|
||||
void LoadOpenPorts();
|
||||
void SaveOpenPorts();
|
||||
|
||||
ServiceManager serviceManager;
|
||||
Service firewallService;
|
||||
};
|
||||
}
|
||||
#endif // PORTLOOKUP_H
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ QString General::RunCommand(bool &success, QString command, QStringList argument
|
||||
QProcessEnvironment PE = QProcessEnvironment::systemEnvironment();
|
||||
if(!env.isEmpty()){
|
||||
for(int i=0; i<env.length(); i++){
|
||||
if(!env[i].contains("=")){ continue; }
|
||||
if(!env[i].contains("=")){ continue; }
|
||||
PE.insert(env[i].section("=",0,0), env[i].section("=",1,100));
|
||||
}
|
||||
}
|
||||
@@ -97,6 +97,137 @@ bool General::writeTextFile(QString filepath, QStringList contents, bool overwri
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
QString General::getConfFileValue(QString fileName, QString Key, int occur )
|
||||
{
|
||||
int found = 1;
|
||||
|
||||
QFile file( fileName );
|
||||
if ( ! file.open( QIODevice::ReadOnly ) ) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
QTextStream stream( &file );
|
||||
QString line;
|
||||
while ( !stream.atEnd() )
|
||||
{
|
||||
line = stream.readLine(); // line of text excluding '\n'
|
||||
line = line.section("#",0,0).trimmed(); //remove any comments
|
||||
if(line.isEmpty()){ continue; }
|
||||
int index = line.indexOf(Key, 0);
|
||||
//qDebug() << "Line:" << line << index;
|
||||
// If the KEY is not found at the start of the line, continue processing
|
||||
if(index!=0)
|
||||
continue;
|
||||
|
||||
if ( found == occur) {
|
||||
line.remove(line.indexOf(Key, 0), Key.length());
|
||||
|
||||
// Remove any quotes
|
||||
if ( line.indexOf('"') == 0 )
|
||||
line = line.remove(0, 1);
|
||||
|
||||
if ( line.indexOf('"') != -1 )
|
||||
line.truncate(line.indexOf('"'));
|
||||
|
||||
file.close();
|
||||
|
||||
return line;
|
||||
} else {
|
||||
found++;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return QString();
|
||||
}
|
||||
bool General::setConfFileValue(QString fileName, QString oldKey, QString newKey, int occur )
|
||||
{
|
||||
// Lets the dev save a value into a specified config file.
|
||||
// The occur value tells which occurance of "oldKey" to replace
|
||||
// If occur is set to -1, it will remove any duplicates of "oldKey"
|
||||
|
||||
//copy the original file to create a temporary file for editing
|
||||
QStringList args;
|
||||
QString oFileTmp = fileName + ".tmp";
|
||||
args << fileName << oFileTmp;
|
||||
General::RunCommand("cp",args);
|
||||
|
||||
//Continue evaluating the temporary file
|
||||
QStringList SavedFile;
|
||||
int found = 1;
|
||||
|
||||
// Load the old file, find the oldKey, remove it and replace with newKey
|
||||
QFile file( oFileTmp );
|
||||
if ( ! file.open( QIODevice::ReadOnly ) )
|
||||
return false;
|
||||
|
||||
QTextStream stream( &file );
|
||||
QString line;
|
||||
while ( !stream.atEnd() ) {
|
||||
line = stream.readLine(); // line of text excluding '\n'
|
||||
|
||||
// Key is not found at all
|
||||
if ( line.indexOf(oldKey, 0) == -1 ) {
|
||||
SavedFile << line ;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Found the key, but it is commented out, so don't worry about this line
|
||||
if ( line.trimmed().indexOf("#", 0) == 0 ) {
|
||||
SavedFile << line ;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the KEY is found, and we are just on wrong occurance, save it and continue to search
|
||||
if ( occur != -1 && found != occur ) {
|
||||
SavedFile << line ;
|
||||
found++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the KEY is found in the line and this matches the occurance that must be processed
|
||||
if ( ! newKey.isEmpty() && found == occur )
|
||||
{
|
||||
SavedFile << newKey ;
|
||||
newKey.clear();
|
||||
found++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the KEY is found and we just want one occurance of the key
|
||||
if ( occur == -1 && ! newKey.isEmpty() ) {
|
||||
SavedFile << newKey ;
|
||||
newKey.clear();
|
||||
found++;
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Didn't find the key? Write it!
|
||||
if ( ! newKey.isEmpty() )
|
||||
SavedFile << newKey;
|
||||
|
||||
|
||||
// Save the new file
|
||||
QFile fileout( oFileTmp );
|
||||
if ( ! fileout.open( QIODevice::WriteOnly ) )
|
||||
return false;
|
||||
|
||||
QTextStream streamout( &fileout );
|
||||
for (int i = 0; i < SavedFile.size(); ++i)
|
||||
streamout << SavedFile.at(i) << "\n";
|
||||
|
||||
fileout.close();
|
||||
|
||||
//Have the temporary file with new changes overwrite the original file
|
||||
args.clear();
|
||||
args << oFileTmp << fileName;
|
||||
General::RunCommand("mv",args);
|
||||
return true;
|
||||
}
|
||||
|
||||
//===========================
|
||||
// SYSCTL ACCESS (might require root)
|
||||
|
||||
@@ -13,25 +13,46 @@ namespace sysadm{
|
||||
|
||||
class General{
|
||||
public:
|
||||
//Non-event-blocking versions of QProcess::execute() or system()
|
||||
//Note: environment changes should be listed as such: [<variable>=<value>]
|
||||
// - Both success/log of output
|
||||
static QString RunCommand(bool &success, QString command, QStringList arguments = QStringList(), QString workdir = "", QStringList env = QStringList() );
|
||||
// - Log output only
|
||||
static QString RunCommand(QString command, QStringList arguments = QStringList(), QString workdir = "", QStringList env = QStringList() );
|
||||
// - success output only
|
||||
static bool RunQuickCommand(QString command, QStringList arguments = QStringList(), QString workdir = "", QStringList env = QStringList() );
|
||||
|
||||
//File Access Functions
|
||||
static QStringList readTextFile(QString filename);
|
||||
static bool writeTextFile(QString filename, QStringList contents, bool overwrite = true);
|
||||
|
||||
//Retrieve a text-based sysctl
|
||||
static QString sysctl(QString var);
|
||||
//Retrieve a number-based sysctl
|
||||
static long long sysctlAsInt(QString var);
|
||||
//Non-event-blocking versions of QProcess::execute() or system()
|
||||
//Note: environment changes should be listed as such: [<variable>=<value>]
|
||||
// - Both success/log of output
|
||||
static QString RunCommand(bool &success, QString command, QStringList arguments = QStringList(), QString workdir = "", QStringList env = QStringList() );
|
||||
// - Log output only
|
||||
static QString RunCommand(QString command, QStringList arguments = QStringList(), QString workdir = "", QStringList env = QStringList() );
|
||||
// - success output only
|
||||
static bool RunQuickCommand(QString command, QStringList arguments = QStringList(), QString workdir = "", QStringList env = QStringList() );
|
||||
|
||||
//File Access Functions
|
||||
static QStringList readTextFile(QString filename);
|
||||
static bool writeTextFile(QString filename, QStringList contents, bool overwrite = true);
|
||||
|
||||
/**
|
||||
* @brief getConfFileValue get the value associated with a key in a config file
|
||||
* @param fileName the file to read from
|
||||
* @param Key the key to look for
|
||||
* @param occur which occurance of the key to return, 1 = the first occurance
|
||||
* @return the value associated with the key
|
||||
*/
|
||||
static QString getConfFileValue( QString fileName, QString Key, int occur =1);
|
||||
|
||||
/**
|
||||
* @brief setConfFileValue set a value in a config file
|
||||
* @param fileName the file to write to
|
||||
* @param oldKey the key to look for
|
||||
* @param newKey the new key with it's value
|
||||
* @param occur which occurance in a file to write the key to, if set to -1 it removes all duplicates
|
||||
* @return success or failure to write the key
|
||||
*/
|
||||
static bool setConfFileValue( QString fileName, QString oldKey, QString newKey, int occur = -1 );
|
||||
|
||||
|
||||
//Retrieve a text-based sysctl
|
||||
static QString sysctl(QString var);
|
||||
//Retrieve a number-based sysctl
|
||||
static long long sysctlAsInt(QString var);
|
||||
|
||||
};
|
||||
|
||||
|
||||
} //end of pcbsd namespace
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
172
src/library/sysadm-servicemanager.cpp
Normal file
172
src/library/sysadm-servicemanager.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "sysadm-servicemanager.h"
|
||||
#include "sysadm-general.h"
|
||||
using namespace sysadm;
|
||||
ServiceManager::ServiceManager(QString chroot, QString ip)
|
||||
{
|
||||
this->chroot = chroot;
|
||||
this->ip = ip;
|
||||
loadServices();
|
||||
}
|
||||
|
||||
Service ServiceManager::GetService(QString serviceName)
|
||||
{
|
||||
for(Service service : services)
|
||||
{
|
||||
if(service.Name == serviceName)
|
||||
return service;
|
||||
}
|
||||
return Service();
|
||||
}
|
||||
|
||||
QVector<Service> ServiceManager::GetServices()
|
||||
{
|
||||
return services;
|
||||
}
|
||||
|
||||
void ServiceManager::Start(Service service)
|
||||
{
|
||||
// Start the process
|
||||
QString prog;
|
||||
QStringList args;
|
||||
|
||||
if ( chroot.isEmpty() ) {
|
||||
prog = "service";
|
||||
args << service.Directory;
|
||||
args << "start";
|
||||
} else {
|
||||
prog = "warden";
|
||||
args << "chroot" << ip << "service" << service.Directory << "start";
|
||||
}
|
||||
General::RunCommand(prog,args);
|
||||
}
|
||||
|
||||
void ServiceManager::Stop(Service service)
|
||||
{
|
||||
// Start the process
|
||||
QString prog;
|
||||
QStringList args;
|
||||
|
||||
if ( chroot.isEmpty() ) {
|
||||
prog = "service";
|
||||
args << service.Directory;
|
||||
args << "stop";
|
||||
} else {
|
||||
prog = "warden";
|
||||
args << "chroot" << ip << "service" << service.Directory << "stop";
|
||||
}
|
||||
General::RunCommand(prog,args);
|
||||
}
|
||||
|
||||
void ServiceManager::Restart(Service service)
|
||||
{
|
||||
QString prog;
|
||||
QStringList args;
|
||||
|
||||
if ( chroot.isEmpty() ) {
|
||||
prog = "service";
|
||||
args << service.Directory;
|
||||
args << "restart";
|
||||
} else {
|
||||
prog = "warden";
|
||||
args << "chroot" << ip << "service" << service.Directory << "restart";
|
||||
}
|
||||
General::RunCommand(prog,args);
|
||||
}
|
||||
|
||||
void ServiceManager::Enable(Service service)
|
||||
{
|
||||
General::setConfFileValue( chroot + "/etc/rc.conf", service.Tag, service.Tag + "=\"YES\"", -1);
|
||||
}
|
||||
|
||||
void ServiceManager::Disable(Service service)
|
||||
{
|
||||
General::setConfFileValue( chroot + "/etc/rc.conf", service.Tag, service.Tag + "=\"NO\"", -1);
|
||||
}
|
||||
|
||||
void ServiceManager::loadServices()
|
||||
{
|
||||
QString tmp;
|
||||
bool valid;
|
||||
Service service;
|
||||
|
||||
QStringList stringDirs;
|
||||
stringDirs << chroot + "/etc/rc.d" << chroot + "/usr/local/etc/rc.d";
|
||||
|
||||
for ( QString dir: stringDirs)
|
||||
{
|
||||
|
||||
QDir directory( dir );
|
||||
|
||||
directory.setFilter( QDir::Files );
|
||||
directory.setSorting( QDir::Name );
|
||||
|
||||
if ( directory.count() == 0 )
|
||||
return;
|
||||
|
||||
for (unsigned int i = 0; i < directory.count(); i++ )
|
||||
{
|
||||
service = Service();
|
||||
|
||||
QFile file( dir + "/" + directory[i] );
|
||||
if ( file.open( QIODevice::ReadOnly ) )
|
||||
{
|
||||
valid=false;
|
||||
service.Directory=directory[i];
|
||||
QTextStream stream( &file );
|
||||
stream.setCodec("UTF-8");
|
||||
QString line;
|
||||
while ( !stream.atEnd() )
|
||||
{
|
||||
line = stream.readLine(); // line of text excluding '\n'
|
||||
|
||||
if ( line.indexOf("name=") == 0)
|
||||
{
|
||||
valid=true;
|
||||
tmp = line.replace("name=", "");
|
||||
service.Name = tmp.replace('"', "");
|
||||
}
|
||||
if ( line.indexOf("rcvar=") == 0)
|
||||
{
|
||||
if ( tmp.isEmpty() )
|
||||
continue;
|
||||
|
||||
tmp = line.replace("rcvar=", "");
|
||||
tmp = tmp.replace('"', "");
|
||||
tmp = tmp.replace("'", "");
|
||||
tmp = tmp.replace("`", "");
|
||||
tmp = tmp.replace("$(set_rcvar)", "");
|
||||
tmp = tmp.replace("$set_rcvar", "");
|
||||
tmp = tmp.replace("set_rcvar", "");
|
||||
tmp = tmp.replace("${name}", "");
|
||||
tmp = tmp.replace("_enable", "");
|
||||
tmp = tmp.replace(" ", "");
|
||||
|
||||
if (tmp.isEmpty())
|
||||
service.Tag = service.Name + "_enable";
|
||||
else
|
||||
service.Tag = tmp;
|
||||
|
||||
if ( service.Tag.indexOf("_enable") == -1 )
|
||||
service.Tag=service.Tag + "_enable";
|
||||
break;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
|
||||
if ( !valid || service.Tag.isEmpty() )
|
||||
continue;
|
||||
|
||||
QString cDir = dir;
|
||||
if ( ! chroot.isEmpty() )
|
||||
cDir.replace(chroot, "");
|
||||
if ( service.Tag.indexOf("$") == 0 )
|
||||
service.Tag = service.Directory + "_enable";
|
||||
if ( service.Name.indexOf("$") == 0 )
|
||||
service.Name = service.Directory;
|
||||
|
||||
services << service;
|
||||
//qDebug() << "Added Service:" << cDir << service.Directory << service.Name << service.Tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/library/sysadm-servicemanager.h
Normal file
73
src/library/sysadm-servicemanager.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#ifndef SERVICEMANAGER_H
|
||||
#define SERVICEMANAGER_H
|
||||
#include <QtCore>
|
||||
namespace sysadm{
|
||||
struct Service{
|
||||
Service()
|
||||
{
|
||||
Name = "";
|
||||
Tag = "";
|
||||
Directory = "";
|
||||
}
|
||||
|
||||
QString Name;
|
||||
QString Tag;
|
||||
QString Directory;
|
||||
};
|
||||
|
||||
class ServiceManager
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief ServiceManager constructor for service manager
|
||||
* @param chroot if we're using a chroot, the chroot to use
|
||||
* @param ip if we're in a jail the ip to use
|
||||
*/
|
||||
ServiceManager(QString chroot = "", QString ip = "");
|
||||
|
||||
/**
|
||||
* @brief GetService gets a service by it's name
|
||||
* @param serviceName the name of the service to get, use the name that would be stored in Service.Name
|
||||
* @return the service with the service name, if the service is not found it returns a blank Service
|
||||
*/
|
||||
Service GetService(QString serviceName);
|
||||
/**
|
||||
* @brief GetServices getter for the vector of services
|
||||
* @return returns the vector of services on the system
|
||||
*/
|
||||
QVector<Service> GetServices();
|
||||
|
||||
/**
|
||||
* @brief Start starts a service
|
||||
* @param service the service to start
|
||||
*/
|
||||
void Start(Service service);
|
||||
/**
|
||||
* @brief Stop stops a service
|
||||
* @param service the service to stop
|
||||
*/
|
||||
void Stop(Service service);
|
||||
/**
|
||||
* @brief Restart restarts a service
|
||||
* @param service the service to restart
|
||||
*/
|
||||
void Restart(Service service);
|
||||
|
||||
/**
|
||||
* @brief Enable enable a service
|
||||
* @param service the service to enable
|
||||
*/
|
||||
void Enable(Service service);
|
||||
/**
|
||||
* @brief Disable disable a service
|
||||
* @param service the service to disable
|
||||
*/
|
||||
void Disable(Service service);
|
||||
private:
|
||||
QVector<Service> services;
|
||||
void loadServices();
|
||||
QString chroot;
|
||||
QString ip;
|
||||
};
|
||||
}
|
||||
#endif // SERVICEMANAGER_H
|
||||
Reference in New Issue
Block a user