diff --git a/src/server/WebBackend.cpp b/src/server/WebBackend.cpp index 45c71b9..76bd0f4 100644 --- a/src/server/WebBackend.cpp +++ b/src/server/WebBackend.cpp @@ -9,15 +9,15 @@ #include //sysadm library interface classes -#include "sysadm-general.h" -#include "sysadm-iocage.h" -#include "sysadm-lifepreserver.h" -#include "sysadm-network.h" -#include "sysadm-systeminfo.h" -#include "sysadm-update.h" +#include "library/sysadm-general.h" +#include "library/sysadm-iocage.h" +#include "library/sysadm-lifepreserver.h" +#include "library/sysadm-network.h" +#include "library/sysadm-systeminfo.h" +#include "library/sysadm-update.h" #include "syscache-client.h" -#include "dispatcher-client.h" +//#include "dispatcher-client.h" #define DEBUG 0 #define SCLISTDELIM QString("::::") //SysCache List Delimiter @@ -83,7 +83,7 @@ RestOutputStruct::ExitCode WebSocket::EvaluateBackendRequest(const RestInputStru //Go through and forward this request to the appropriate sub-system if(namesp=="rpc" && name=="dispatcher"){ - return EvaluateDispatcherRequest(IN.args, out); + return EvaluateDispatcherRequest(IN.fullaccess, IN.args, out); }else if(namesp=="sysadm" && name=="iocage"){ return EvaluateSysadmIocageRequest(IN.args, out); }else if(namesp=="sysadm" && name=="lifepreserver"){ @@ -136,15 +136,21 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSyscacheRequest(const QJsonValue i } //==== DISPATCHER ==== -RestOutputStruct::ExitCode WebSocket::EvaluateDispatcherRequest(const QJsonValue in_args, QJsonObject *out){ +RestOutputStruct::ExitCode WebSocket::EvaluateDispatcherRequest(bool allaccess, const QJsonValue in_args, QJsonObject *out){ //dispatcher only needs a list of sub-commands at the moment (might change later) - if(!AUTHSYSTEM->hasFullAccess(SockAuthToken)){ + if(!in_args.isObject() || !in_args.toObject().contains("action") ){ return RestOutputStruct::BADREQUEST; } + QString act = in_args.toObject().value("action").toString().toLower(); + + //Determing the type of action to perform + if(act=="run"){ } + if(!allaccess){ return RestOutputStruct::FORBIDDEN; //this user does not have permission to queue jobs } - QStringList in_req; + + //Parse the input arguments structure - if(in_args.isArray()){ in_req = JsonArrayToStringList(in_args.toArray()); } + /*if(in_args.isArray()){ in_req = JsonArrayToStringList(in_args.toArray()); } else if(in_args.isObject()){ QStringList keys = in_args.toObject().keys(); for(int i=0; iinsert(in_req[i],values[i]); } + for(int i=0; iinsert(in_req[i],values[i]); }*/ //Return Success return RestOutputStruct::OK; } diff --git a/src/server/WebSocket.h b/src/server/WebSocket.h index e1ec81c..4fb28a2 100644 --- a/src/server/WebSocket.h +++ b/src/server/WebSocket.h @@ -45,7 +45,7 @@ private: RestOutputStruct::ExitCode EvaluateBackendRequest(const RestInputStruct&, QJsonObject *out); // -- Individual subsystems RestOutputStruct::ExitCode EvaluateSyscacheRequest(const QJsonValue in_args, QJsonObject *out); - RestOutputStruct::ExitCode EvaluateDispatcherRequest(const QJsonValue in_args, QJsonObject *out); + RestOutputStruct::ExitCode EvaluateDispatcherRequest(bool allaccess, const QJsonValue in_args, QJsonObject *out); // -- sysadm iocage API RestOutputStruct::ExitCode EvaluateSysadmIocageRequest(const QJsonValue in_args, QJsonObject *out); // -- sysadm Network API diff --git a/src/server/library/NetDevice.cpp b/src/server/library/NetDevice.cpp new file mode 100644 index 0000000..f82ee0d --- /dev/null +++ b/src/server/library/NetDevice.cpp @@ -0,0 +1,325 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +// Note: This was almost entirely written by Tim McCormick in 2009 for +// the first PC-BSD library, and copied here by Ken Moore in 2015 +//=========================================== +#include "sysadm-network.h" +#include "sysadm-general.h" + +#include "sysadm-global.h" + +using namespace sysadm; + +//==================== +// STATIC LISTING FUNCTION +//==================== +QStringList NetDevice::listNetDevices(){ + QStringList result; + struct ifaddrs *ifap; + getifaddrs(&ifap); + char ifName[IFNAMSIZ]; + //Probe the system for each device (one at a time) + while (ifap != NULL){ + strncpy(ifName, ifap->ifa_name, IFNAMSIZ); + if (result.contains(ifName) == 0) result += ifName; + ifap = ifap->ifa_next; + } + //Close the + freeifaddrs(ifap); + return result; +} + +//===================== +// CLASS FUNCTIONS +//===================== +//Get the name of the device (taking off any numbers) +QString NetDevice::devName(){ + uint pos = name.indexOf(QRegExp("[0-9]+$")); + QString result = name; + result.truncate(pos); + return result; +} + +//Return just the number of the device (removing the name) +uint NetDevice::devNum(){ + uint pos = name.indexOf(QRegExp("[0-9]+$")); + return name.mid(pos).toInt(); +} + +//Fetch the IP and return it as a QString +QString NetDevice::ipAsString(){ + struct ifreq ifr; + memset(&ifr, 0, sizeof(struct ifreq)); + + strncpy(ifr.ifr_name, name.toLocal8Bit(), IFNAMSIZ); + int s = socket(PF_INET, SOCK_DGRAM, 0); + + ioctl(s, SIOCGIFADDR, &ifr); + struct in_addr in = ((sockaddr_in *) &ifr.ifr_addr)->sin_addr; + + return QString(inet_ntoa(in)); +} + +//Fetch the IPv6 and return it as a QString +QString NetDevice::ipv6AsString(){ + //Get the sockaddr for the device + struct sockaddr *sadd = 0; + struct ifaddrs *addrs; + if( 0!=getifaddrs( &addrs ) ){ qDebug() << "Could not get addrs"; return ""; } + while(sadd==0 && addrs!=0){ + if( QString(addrs->ifa_name)==name && addrs->ifa_addr->sa_family==AF_INET6){ + //Found device (with IPv6 address) + sadd = addrs->ifa_addr; + break; + }else{ + //Move to the next device + addrs = addrs->ifa_next; + } + } + free(addrs); + if(sadd==0){ qDebug() << "No socket address found"; return ""; } + //Now get the IPv6 address in string form + char straddr[INET6_ADDRSTRLEN]; + int err = getnameinfo(sadd, sadd->sa_len, straddr, sizeof(straddr),NULL, 0, NI_NUMERICHOST); + if(err!=0){ + qDebug() << "getnameinfo error:" << gai_strerror(err); + return ""; + }else{ + return QString(straddr); + } +} + + +//Fetch the netmask and return it as a QString +QString NetDevice::netmaskAsString(){ + struct ifreq ifr; + memset(&ifr, 0, sizeof(struct ifreq)); + + strncpy(ifr.ifr_name, name.toLocal8Bit(), IFNAMSIZ); + int s = socket(PF_INET, SOCK_DGRAM, 0); + + ioctl(s, SIOCGIFNETMASK, &ifr); + struct in_addr in = ((sockaddr_in *) &ifr.ifr_addr)->sin_addr; + + return QString(inet_ntoa(in)); +} + +//Returns the description string for the device +QString NetDevice::desc(){ + return General::sysctl("dev." + devName() + "." + QString::number(devNum()) + ".%desc"); +} + +//Fetch the mac address as a QString +QString NetDevice::macAsString(){ + int mib[6]; + size_t len; + char *buf; + struct sockaddr_dl *sdl; + char *ptr; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_LINK; + mib[4] = NET_RT_IFLIST; + mib[5] = if_nametoindex(name.toLocal8Bit()); + + //First find the size of the return, so we can adjust buf accordingly + sysctl(mib, 6, NULL, &len, NULL, 0); + buf = (char *) malloc(len); + sysctl(mib, 6, buf, &len, NULL, 0); + + sdl = (sockaddr_dl *)(((if_msghdr *)buf)+1); + ptr = (char *) LLADDR(sdl); + + QString mac; + for (uint i=0; i < 6; i++){ + mac += QString::number(*(ptr+i), 16).right(2).rightJustified(2, '0'); + if(i<5){ mac += ":"; } + } + return mac; +} + +//Get the status of the device (active, associated, etc...) +QString NetDevice::mediaStatusAsString(){ + struct ifmediareq ifm; + memset(&ifm, 0, sizeof(struct ifmediareq)); + + strncpy(ifm.ifm_name, name.toLocal8Bit(), IFNAMSIZ); + int s = socket(AF_INET, SOCK_DGRAM, 0); + + ioctl(s, SIOCGIFMEDIA, &ifm); + QString status; + + switch (IFM_TYPE(ifm.ifm_active)){ + case IFM_FDDI: + case IFM_TOKEN: + if (ifm.ifm_status & IFM_ACTIVE) status = "inserted"; + else status = "no ring"; + break; + case IFM_IEEE80211: + if (ifm.ifm_status & IFM_ACTIVE) status = "associated"; + else status = "no carrier"; + break; + default: + if (ifm.ifm_status & IFM_ACTIVE) status = "active"; + else status = "no carrier"; + } + return status; +} + +QString NetDevice::gatewayAsString(){ + QString info = General::RunCommand("nice netstat -n -r").split("\n").filter(name).filter("default").join("\n"); + if(info.isEmpty()){ return ""; } + //Pull the gateway out of the first line ( ) + info = info.replace("\t"," ").section("\n",0,0).simplified(); //ensure proper parsing + return info.section(" ",1,1); +} + +//Check if a device is wireless or not +bool NetDevice::isWireless(){ + struct ifmediareq ifm; + memset(&ifm, 0, sizeof(struct ifmediareq)); + + strncpy(ifm.ifm_name, name.toLocal8Bit(), IFNAMSIZ); + int s = socket(AF_INET, SOCK_DGRAM, 0); + + ioctl(s, SIOCGIFMEDIA, &ifm); + + return IFM_TYPE(ifm.ifm_active) == IFM_IEEE80211; +} + +//Get the parent device (if this is a wireless wlan) +QString NetDevice::getWifiParent(){ + if(!name.contains("wlan")){ return ""; } + return General::sysctl("net.wlan." + QString::number(this->devNum()) + ".%parent"); +} + +//See if the device is setup to use DHCP +bool NetDevice::usesDHCP(){ + //The system does not keep track of how the device's address was assigned + // so the closest we can get to this is to see if the system is setup to use + // DHCP on startup (in /etc/rc.conf) (Ken Moore - 6/24/15) + return !Network::readRcConf().filter(name).filter("DHCP").isEmpty(); +} + +//See if the device is currently in use +bool NetDevice::isUp(){ + struct ifreq ifr; + memset(&ifr, 0, sizeof(struct ifreq)); + + strncpy(ifr.ifr_name, name.toLocal8Bit(), IFNAMSIZ); + int s = socket(AF_INET, SOCK_DGRAM, 0); + + ioctl(s, SIOCGIFFLAGS, &ifr); + + return (ifr.ifr_flags & IFF_UP) ? 1 : 0; +} + +//Determine the number of packets received by the device +long NetDevice::packetsRx(){ + int mib[6]; + size_t len; + char *buf; + struct if_msghdr *ifm; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_LINK; + mib[4] = NET_RT_IFLIST; + mib[5] = if_nametoindex(name.toLocal8Bit()); + + //First find the size of the return, so we can adjust buf accordingly + sysctl(mib, 6, NULL, &len, NULL, 0); + buf = (char *) malloc(len); + sysctl(mib, 6, buf, &len, NULL, 0); + + ifm = (if_msghdr *) buf; + + return ifm->ifm_data.ifi_ipackets; +} + +//Determine the number of packets transmitted by the device +long NetDevice::packetsTx(){ + int mib[6]; + size_t len; + char *buf; + struct if_msghdr *ifm; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_LINK; + mib[4] = NET_RT_IFLIST; + mib[5] = if_nametoindex(name.toLocal8Bit()); + + //First find the size of the return, so we can adjust buf accordingly + sysctl(mib, 6, NULL, &len, NULL, 0); + buf = (char *) malloc(len); + sysctl(mib, 6, buf, &len, NULL, 0); + + ifm = (if_msghdr *) buf; + + return ifm->ifm_data.ifi_opackets; +} + +//Determine the number of errors received +long NetDevice::errorsRx(){ + int mib[6]; + size_t len; + char *buf; + struct if_msghdr *ifm; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_LINK; + mib[4] = NET_RT_IFLIST; + mib[5] = if_nametoindex(name.toLocal8Bit()); + + //First find the size of the return, so we can adjust buf accordingly + sysctl(mib, 6, NULL, &len, NULL, 0); + buf = (char *) malloc(len); + sysctl(mib, 6, buf, &len, NULL, 0); + + ifm = (if_msghdr *) buf; + + return ifm->ifm_data.ifi_ierrors; +} + +//Determine the number of errors transmitted +long NetDevice::errorsTx(){ + int mib[6]; + size_t len; + char *buf; + struct if_msghdr *ifm; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = AF_LINK; + mib[4] = NET_RT_IFLIST; + mib[5] = if_nametoindex(name.toLocal8Bit()); + + //First find the size of the return, so we can adjust buf accordingly + sysctl(mib, 6, NULL, &len, NULL, 0); + buf = (char *) malloc(len); + sysctl(mib, 6, buf, &len, NULL, 0); + + ifm = (if_msghdr *) buf; + + return ifm->ifm_data.ifi_oerrors; +} + +//========================= +// SETTING FUNCTIONS (requires root) +//========================= +void NetDevice::setUp(bool up){ + //This only sets it up/down for the current session - does not change usage on next boot + General::RunCommand("ifconfig "+name+" "+ (up ? "up": "down") ); +} diff --git a/src/server/library/library.pri b/src/server/library/library.pri new file mode 100644 index 0000000..4874d46 --- /dev/null +++ b/src/server/library/library.pri @@ -0,0 +1,25 @@ + +CONFIG += c++11 + +HEADERS += $${PWD}/sysadm-global.h \ + $${PWD}/sysadm-general.h \ + $${PWD}/sysadm-iocage.h \ + $${PWD}/sysadm-lifepreserver.h \ + $${PWD}/sysadm-network.h \ + $${PWD}/sysadm-firewall.h \ + $${PWD}/sysadm-servicemanager.h\ + $${PWD}/sysadm-systeminfo.h\ + $${PWD}/sysadm-update.h \ + $${PWD}/sysadm-usermanager.h + +SOURCES += $${PWD}/NetDevice.cpp \ + $${PWD}/sysadm-general.cpp \ + $${PWD}/sysadm-iocage.cpp \ + $${PWD}/sysadm-lifepreserver.cpp \ + $${PWD}/sysadm-network.cpp \ + $${PWD}/sysadm-firewall.cpp \ + $${PWD}/sysadm-servicemanager.cpp \ + $${PWD}/sysadm-systeminfo.cpp \ + $${PWD}/sysadm-update.cpp \ + $${PWD}/sysadm-usermanager.cpp + diff --git a/src/server/library/sysadm-firewall.cpp b/src/server/library/sysadm-firewall.cpp new file mode 100644 index 0000000..837aa6a --- /dev/null +++ b/src/server/library/sysadm-firewall.cpp @@ -0,0 +1,264 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details + +#include "sysadm-firewall.h" +#include "sysadm-general.h" +#include +#include + +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 + if (port < 0 || port > 65535) + { + PortInfo returnValue; + returnValue.Port = -1; + returnValue.Description = "Port out of bounds"; + return returnValue; + } + + //Check to see if things have been initialized + if(portStrings == NULL) + readServicesFile(); + + + PortInfo returnValue; + //the port number is valid so set it + returnValue.Port = port; + + //make sure that the portType is cased in lower to match the service file and + //then store it in the returnValue, since there isn't a huge point in checking + //the validitiy of the type since /etc/services lists more than udp/tcp + type = type.toLower(); + returnValue.Type = type; + + //Check to see if it's a recommended port + returnValue.Recommended = false; + for(int recommendedPort : recommendedPorts) + { + if (port == recommendedPort) + { + returnValue.Recommended = true; + } + } + + //Check to see if the port number is listed. The format in the file + // is portname/portType. ex.: 22/tcp + + QStringList portList = portStrings->filter(QString::number(port) + "/" + type); + if(portList.size() > 0) + { + //grab the first one, there may be duplicates due to colliding ports in the /etc/services file + //but those are listed after the declaration for what the port officially should be used for + QString line = portList.at(0); + + //Split across spaces since it's whitespace delimited + QStringList lineList = line.split(' '); + + //the keyword associated with the port is the first element in a line + returnValue.Keyword = lineList.at(0); + + //if the size of the list is less than 3 then there is no description + if(lineList.size() > 2) + { + QString description = lineList.at(2); + //String the description back together from the end of the list + for(int i = 3; i < lineList.size(); i++) + { + description += " " + lineList.at(i); + } + returnValue.Description = description; + } + } + + return returnValue; + +} + +void Firewall::OpenPort(int port, QString type) +{ + openports.append(LookUpPort(port,type)); + SaveOpenPorts(); +} + +void Firewall::OpenPort(QVector ports) +{ + for(PortInfo port : ports) + { + openports.append(port); + } + SaveOpenPorts(); +} + +void Firewall::ClosePort(int port, QString type) +{ + openports.removeAll(LookUpPort(port,type)); + SaveOpenPorts(); +} + +void Firewall::ClosePort(QVector ports) +{ + for(PortInfo port : ports) + { + openports.removeAll(port); + } + SaveOpenPorts(); +} + +QVector Firewall::OpenPorts() +{ + return openports; + +} + +bool Firewall::IsRunning() +{ + return General::sysctlAsInt("net.inet.ip.fw.enable") == 1; +} + +void Firewall::Start() +{ + serviceManager.Enable(firewallService); + serviceManager.Start(firewallService); +} + +void Firewall::Stop() +{ + serviceManager.Enable(firewallService); + serviceManager.Stop(firewallService); +} + +void Firewall::Restart() +{ + serviceManager.Enable(firewallService); + serviceManager.Restart(firewallService); +} + +void Firewall::Enable() +{ + serviceManager.Enable(firewallService); +} + +void Firewall::Disable() +{ + serviceManager.Disable(firewallService); +} + +void Firewall::RestoreDefaults() +{ + const QString ipfwrules = "/etc/ipfw.rules"; + const QString ipfwrulesprevious = ipfwrules + ".previous"; + + const QString ipfwopenports = "/etc/ipfw.openports"; + const QString ipfwopenportsprevious = ipfwopenports + ".previous"; + + //QFile has a rename command that acts like move, however it won't + //clobber a file if it already exists, so we have to check if there + //already is a .previous and if so delete it before moving the file + //to .previous + + //move the files out of the way + if(QFile::exists(ipfwrulesprevious)) + QFile::remove(ipfwrulesprevious); + QFile::rename(ipfwrules,ipfwrulesprevious); + + if(QFile::exists(ipfwopenportsprevious)) + QFile::remove(ipfwopenportsprevious); + QFile::rename(ipfwopenports,ipfwopenportsprevious); + + + //refresh/restart the rules files + QStringList args; + args << "/usr/local/share/pcbsd/scripts/reset-firewall"; + General::RunCommand("sh",args); + + LoadOpenPorts(); +} + +void Firewall::readServicesFile() +{ + portStrings = new QStringList(); + + // /etc/services contains a file that lists the various port numbers + // and their descriptions + QFile* services = new QFile("/etc/services"); + services->open(QFile::ReadOnly); + while(!services->atEnd()) + { + QString line = services->readLine(); + //jump down past the comments + if(line[0] == '#' || line.simplified().isEmpty()) + continue; + + //remove all of the extraneous whitespace in the line + line = line.simplified(); + + portStrings->append(line); + } + services->close(); + delete services; +} + +void Firewall::LoadOpenPorts() +{ + openports.clear(); + QFile file("/etc/ipfw.openports"); + if( file.open(QIODevice::ReadOnly) ){ + QTextStream in(&file); + while( !in.atEnd() ){ + QString line = in.readLine(); + if(line.startsWith("#") || line.simplified().isEmpty()){ continue; } + //File format: " " (nice and simple) + openports.append(LookUpPort(line.section(" ",1,1).toInt(),line.section(" ",0,0))); + } + file.close(); + } + //order them in ascending order by port then port type + std::sort(openports.begin(),openports.end()); +} + +void Firewall::SaveOpenPorts() +{ + //Convert to file format + std::sort(openports.begin(), openports.end()); //make sure they are still sorted by port + QStringList fileout; + for(PortInfo port : openports){ + fileout.append("#" + port.Keyword + ": " + port.Description + "\n" + + port.Type +" "+QString::number(port.Port)); + } + //Always make sure that the file always ends with a newline + if(!fileout.isEmpty()){ fileout << ""; } + //Save to file + QFile file("/etc/ipfw.openports"); + if( file.open(QIODevice::WriteOnly | QIODevice::Truncate) ){ + QTextStream out(&file); + out << fileout.join("\n"); + file.close(); + } + //Re-load/start rules (just in case - it is a smart script) + if(IsRunning()) + { + QStringList args; + args << "/usr/local/share/pcbsd/scripts/reset-firewall"; + General::RunCommand("sh",args); + } +} + diff --git a/src/server/library/sysadm-firewall.h b/src/server/library/sysadm-firewall.h new file mode 100644 index 0000000..d0360e8 --- /dev/null +++ b/src/server/library/sysadm-firewall.h @@ -0,0 +1,135 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details + +#ifndef PORTLOOKUP_H +#define PORTLOOKUP_H +#include +#include +#include "sysadm-servicemanager.h" +namespace sysadm +{ +struct PortInfo{ + int Port; + QString Type; + QString Keyword; + QString Description; + bool Recommended; + friend bool operator<(const PortInfo lhs, const PortInfo rhs){ + return std::tie(lhs.Port,lhs.Type) < std::tie(rhs.Port,rhs.Type); + } + friend bool operator>(const PortInfo lhs, const PortInfo rhs) + { return rhs < lhs;} + friend bool operator==(const PortInfo lhs, const PortInfo rhs) + { + return lhs.Port == rhs.Port && lhs.Type == rhs.Type; + } + friend bool operator !=(const PortInfo lhs, const PortInfo rhs) + { return !(lhs == rhs);} +}; + +const static QVector recommendedPorts = {22, 80}; +class Firewall +{ + +public: + ///#section: ctors dtors + Firewall(); + ~Firewall(); + ///#endsection + + ///#section: port commands + /** + * @description Returns a structure containing information about the port + * including its port type, keyword, description, and whether it's a + * recommended port + * + * @param number a port number between 0 and 2^16 - 1 + * @param type specify whether the port is tdp, udp, etc + * + * @ErrorConditions Port Number is set to -1 and a description of the error is stored in the description variable + */ + PortInfo LookUpPort(int number, QString type); + /** + * @brief Opens a port + * @param number a port number between 0 and 2^16 -1 + * @param type specify whether the port is tdp, udp, etc + */ + void OpenPort(int number, QString type); + + /** + * @brief Opens a set of ports + * @param ports a vector of ports to open + */ + void OpenPort(QVector ports); + + /** + * @brief ClosePort closes a port + * @param number a port number between 0 and 2^16 -1 + * @param type specify whether the port is tdp, udp, etc + */ + void ClosePort(int number, QString type); + + /** + * @brief ClosePort closes a set of ports + * @param ports a vector of ports to close + */ + void ClosePort(QVector ports); + + /** + * @brief finds a list of ports that are open gets the info about them + * and returns them + * @return a QVector of the open ports + */ + QVector OpenPorts(); + ///#endsection + + ///#section: firewall commands + /** + * @brief Checks to see if the firewall is running + * @return true if the firewall is running, false if not + */ + bool IsRunning(); + /** + * @brief Starts the firewall + */ + void Start(); + /** + * @brief Stops the firewall + */ + void Stop(); + /** + * @brief Restarts the firewall + */ + void Restart(); + /** + * @brief Enables the firewall in rc.conf, Start() will automatically enable the firewall + */ + void Enable(); + /** + * @brief Disables the firewall in rc.conf use after Stop() to completely disable + */ + void Disable(); + /** + * @brief Restores the Default Configuration + */ + void RestoreDefaults(); + ///#endsection + +private: + void readServicesFile(); + QStringList* portStrings; + + QVector openports; + + void LoadOpenPorts(); + void SaveOpenPorts(); + + ServiceManager serviceManager; + Service firewallService; +}; +} +#endif // PORTLOOKUP_H + diff --git a/src/server/library/sysadm-general.cpp b/src/server/library/sysadm-general.cpp new file mode 100644 index 0000000..3a2b812 --- /dev/null +++ b/src/server/library/sysadm-general.cpp @@ -0,0 +1,250 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-general.h" +//PLEASE: Keep the functions in the same order as listed in pcbsd-general.h + +#include "sysadm-global.h" + +using namespace sysadm; +//================= +// RunCommand() variations +//================= +//Note: environment changes should be listed as such: = + +//Return CLI output (and success/failure) +QString General::RunCommand(bool &success, QString command, QStringList arguments, QString workdir, QStringList env){ + QProcess proc; + proc.setProcessChannelMode(QProcess::MergedChannels); //need output + //First setup the process environment as necessary + QProcessEnvironment PE = QProcessEnvironment::systemEnvironment(); + if(!env.isEmpty()){ + for(int i=0; i=] + // - 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 diff --git a/src/server/library/sysadm-global.h b/src/server/library/sysadm-global.h new file mode 100644 index 0000000..a9290dd --- /dev/null +++ b/src/server/library/sysadm-global.h @@ -0,0 +1,39 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef __PCBSD_LIB_UTILS_GENERAL_INCLUDES_H +#define __PCBSD_LIB_UTILS_GENERAL_INCLUDES_H + +//Qt Includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//FreeBSD Includes +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include //from "man getnetent" (network entries) + +#endif diff --git a/src/server/library/sysadm-iocage.cpp b/src/server/library/sysadm-iocage.cpp new file mode 100644 index 0000000..393282b --- /dev/null +++ b/src/server/library/sysadm-iocage.cpp @@ -0,0 +1,101 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-general.h" +#include "sysadm-iocage.h" +#include "sysadm-global.h" + +using namespace sysadm; + +//PLEASE: Keep the functions in the same order as listed in pcbsd-general.h +// Return all the default iocage settings +QJsonObject Iocage::getDefaultSettings() { + QJsonObject retObject; + + QStringList output = General::RunCommand("iocage defaults").split("\n"); + + QJsonObject vals; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("JID") != -1 ) + continue; + + if ( output.at(i).isEmpty() ) + break; + + QString key = output.at(i).simplified().section("=", 0, 0); + QString value = output.at(i).simplified().section("=", 1, 1); + + vals.insert(key, value); + } + + retObject.insert("defaults", vals); + return retObject; +} + +// List the jails on the box +QJsonObject Iocage::getJailSettings(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("jail") ) { + retObject.insert("error", "Missing required keys"); + return retObject; + } + + // Get the key values + QString jail = jsin.value("jail").toString(); + + QStringList output = General::RunCommand("iocage get all " + jail).split("\n"); + + QJsonObject vals; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("JID") != -1 ) + continue; + + if ( output.at(i).isEmpty() ) + break; + + QString key = output.at(i).simplified().section(":", 0, 0); + QString value = output.at(i).simplified().section(":", 1, 1); + + vals.insert(key, value); + } + + retObject.insert(jail, vals); + return retObject; +} + +// List the jails on the box +QJsonObject Iocage::listJails() { + QJsonObject retObject; + + QStringList output = General::RunCommand("iocage list").split("\n"); + + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("JID") != -1 ) + continue; + + if ( output.at(i).isEmpty() ) + break; + + QJsonObject jail; + QString line = output.at(i).simplified(); + QString uuid = line.section(" ", 1, 1); + + jail.insert("jid", line.section(" ", 0, 0)); + jail.insert("boot", line.section(" ", 2, 2)); + jail.insert("state", line.section(" ", 3, 3)); + jail.insert("tag", line.section(" ", 4, 4)); + jail.insert("type", line.section(" ", 5, 5)); + + retObject.insert(uuid, jail); + } + + return retObject; +} diff --git a/src/server/library/sysadm-iocage.h b/src/server/library/sysadm-iocage.h new file mode 100644 index 0000000..3a1f9f7 --- /dev/null +++ b/src/server/library/sysadm-iocage.h @@ -0,0 +1,24 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef __PCBSD_LIB_UTILS_IOCAGE_H +#define __PCBSD_LIB_UTILS_IOCAGE_H + +#include +#include "sysadm-global.h" + +namespace sysadm{ + +class Iocage{ +public: + static QJsonObject getDefaultSettings(); + static QJsonObject getJailSettings(QJsonObject); + static QJsonObject listJails(); +}; + +} //end of pcbsd namespace + +#endif diff --git a/src/server/library/sysadm-lifepreserver.cpp b/src/server/library/sysadm-lifepreserver.cpp new file mode 100644 index 0000000..c3fe76d --- /dev/null +++ b/src/server/library/sysadm-lifepreserver.cpp @@ -0,0 +1,665 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-general.h" +#include "sysadm-lifepreserver.h" +#include "sysadm-global.h" + +using namespace sysadm; + +//PLEASE: Keep the functions in the same order as listed in pcbsd-general.h + +// Add a new replication target +QJsonObject LifePreserver::addReplication(QJsonObject jsin) { + QJsonObject retObject; + QString host, user, port, password, ldataset, rdataset, frequency; + + QStringList keys = jsin.keys(); + if (! keys.contains("host") + || ! keys.contains("user") + || ! keys.contains("port") + || ! keys.contains("password") + || ! keys.contains("dataset") + || ! keys.contains("remotedataset") + || ! keys.contains("frequency") ) { + retObject.insert("error", "Missing required keys"); + return retObject; + } + + // Get the key values + host = jsin.value("host").toString(); + user = jsin.value("user").toString(); + port = jsin.value("port").toString(); + password = jsin.value("password").toString(); + ldataset = jsin.value("dataset").toString(); + rdataset = jsin.value("remotedataset").toString(); + frequency = jsin.value("frequency").toString(); + + // Make sure we have the dataset / snap key(s) + if ( host.isEmpty() + || user.isEmpty() + || port.isEmpty() + || password.isEmpty() + || ldataset.isEmpty() + || rdataset.isEmpty() + || frequency.isEmpty() ) { + retObject.insert("error", "Empty dataset or snap keys "); + return retObject; + } + + // Run the command with the SSHPASS env variable set + QStringList output; + output = General::RunCommand("lpreserver", QStringList() << "replicate" << "add" + << host + << user + << port + << ldataset + << rdataset + << frequency, "", QStringList() << "SSHPASS=" + password ).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("host", host); + values.insert("user", user); + values.insert("port", port); + values.insert("ldataset", ldataset); + values.insert("rdataset", rdataset); + values.insert("frequency", frequency); + + return values; +} + +// Re-init the LP replication target +QJsonObject LifePreserver::initReplication(QJsonObject jsin) { + QJsonObject retObject; + QString dset, rhost; + + QStringList keys = jsin.keys(); + if(! keys.contains("dataset") || ! keys.contains("host")){ + retObject.insert("error", "Missing dataset or host key"); + return retObject; + } + + // Check which pool we are looking at + dset = jsin.value("dataset").toString(); + rhost = jsin.value("host").toString(); + + // Make sure we have the pool key + if ( dset.isEmpty() || rhost.isEmpty()) { + retObject.insert("error", "Missing dataset or host key"); + return retObject; + } + + // TODO - This command can take a LONG TIME. Find a way to queue / background it and return an event + // via websockets later, or block here and return when finished if this is REST + QStringList output = General::RunCommand("lpreserver replicate init " + dset + " " + rhost).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + QJsonObject values; + values.insert("dataset", dset); + values.insert("host", rhost); + return values; +} + +// Build list of scheduled cron snapshot jobs +QJsonObject LifePreserver::listCron() { + QJsonObject retObject; + + QStringList output = General::RunCommand("lpreserver listcron").split("\n"); + QList snaps; + + // Parse the output + bool inSnapSection = false; + bool inScrubSection = false; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("-----------------") != -1 ) { + inSnapSection = true; + continue; + } + + if (!inSnapSection) + continue; + + if ( output.at(i).indexOf("Pools scheduled for scrub") != -1 ) { + inScrubSection = true; + continue; + } + + if ( output.at(i).isEmpty() || output.at(i).indexOf("-----------------") != -1 ) + continue; + + if ( inSnapSection && ! inScrubSection ) { + // Breakdown this cron job + QString pool = output.at(i).section("-", 0, 0).simplified(); + QString freq = output.at(i).section("-", 1, 1).simplified(); + QString keep = output.at(i).section("-", 2, 2).simplified().replace("total: ", ""); + + QJsonObject values; + values.insert("schedule", freq); + values.insert("keep", keep); + retObject.insert(pool, values); + } else if (inSnapSection && inScrubSection ) { + // Add a cron scrub + QString pool = output.at(i).section("-", 0, 0).simplified(); + QString freq = output.at(i).section("-", 1, 1).simplified().replace(" ", ""); + qDebug() << "Found scrub:" << pool << freq; + + QJsonObject values; + QStringList keys = retObject.keys(); + if( keys.contains(pool)){ + // We have an existing pool object, add it + values = retObject[pool].toObject(); + retObject.remove(pool); + values.insert("scrub", freq); + retObject.insert(pool, values); + } else { + // Add a new pool object with the scrub + values.insert("scrub", freq); + retObject.insert(pool, values); + } + } + } + + return retObject; +} + +// Return a list of replication targets +QJsonObject LifePreserver::listReplication() { + QJsonObject retObject; + + QStringList output = General::RunCommand("lpreserver replicate list").split("\n"); + QStringList setitems; + QString tmpkey; + QRegExp sep("\\s+"); + + // Parse the output + bool inSection = false; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("-----------------") != -1 ) { + inSection = true; + continue; + } + + if (!inSection) + continue; + + if ( output.at(i).isEmpty() || output.at(i).indexOf("-----------------") != -1 ) + break; + + // Breakdown the settings + QJsonObject values; + tmpkey = ""; + QString dset, rdset, user, host, port, parseline, time; + dset = output.at(i).section(sep, 0, 0).simplified(); + parseline = output.at(i).section(sep, 2, 2).simplified(); + user = parseline.section("@", 0, 0); + host = parseline.section("@", 1, 1).section("[", 0, 0); + port = parseline.section("@", 1, 1).section("[", 1, 1).section("]", 0, 0); + rdset = parseline.section(":", 1, 1); + time = output.at(i).section(sep, 4, 4).simplified(); + + values.insert("dataset", dset); + values.insert("user", user); + values.insert("port", port); + values.insert("host", host); + values.insert("rdset", rdset); + values.insert("frequency", time); + retObject.insert(dset + "->" + host, values); + } + + return retObject; +} + +// Return a list of snapshots on a particular pool / dataset +QJsonObject LifePreserver::listSnap(QJsonObject jsin) { + QJsonObject retObject; + QString pool; + + QStringList keys = jsin.keys(); + if(! keys.contains("pool")){ + retObject.insert("error", "Missing pool key"); + return retObject; + } + + // Check which pool we are looking at + pool = jsin.value("pool").toString(); + + // Make sure we have the pool key + if ( pool.isEmpty() ) { + retObject.insert("error", "Missing pool key"); + return retObject; + } + + QStringList output = General::RunCommand("lpreserver listsnap " + pool ).split("\n"); + QList snaps; + QStringList snapitems; + QRegExp sep("\\s+"); + + // Parse the output + bool inSection = false; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("-----------------") != -1 ) { + inSection = true; + continue; + } + + if (!inSection) + continue; + + if ( output.at(i).isEmpty() || output.at(i).indexOf("-----------------") != -1 ) + break; + + // Breakdown this snapshot + snapitems.clear(); + snapitems << output.at(i).section(sep, 0, 0).simplified(); + snapitems << output.at(i).section(sep, 1, -1).simplified(); + + QJsonObject values; + values.insert("comment", snapitems.at(1)); + retObject.insert(snapitems.at(0), values); + } + + return retObject; +} + +// Remove a replication task +QJsonObject LifePreserver::removeReplication(QJsonObject jsin) { + QJsonObject retObject; + QString dataset, host; + + QStringList keys = jsin.keys(); + if(! keys.contains("dataset") || ! keys.contains("host")){ + retObject.insert("error", "Requires dataset and host keys"); + return retObject; + } + + // Get the dataset / host + dataset = jsin.value("dataset").toString(); + host = jsin.value("host").toString(); + + // Make sure we have the dataset / host key(s) + if ( dataset.isEmpty() || host.isEmpty() ) { + retObject.insert("error", "Empty dataset or host keys "); + return retObject; + } + + QStringList output; + output = General::RunCommand("lpreserver replicate remove " + dataset + " " + host).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("dataset", dataset); + values.insert("host", host); + + return values; +} + +// Remove a snapshot +QJsonObject LifePreserver::removeSnapshot(QJsonObject jsin) { + QJsonObject retObject; + QString dataset, snap; + + QStringList keys = jsin.keys(); + if(! keys.contains("dataset") || ! keys.contains("snap")){ + retObject.insert("error", "Requires dataset and snap keys"); + return retObject; + } + + // Get the dataset / snap + dataset = jsin.value("dataset").toString(); + snap = jsin.value("snap").toString(); + + // Make sure we have the dataset / snap key(s) + if ( dataset.isEmpty() || snap.isEmpty() ) { + retObject.insert("error", "Empty dataset or snap keys "); + return retObject; + } + + QStringList output; + output = General::RunCommand("lpreserver rmsnap " + dataset + " " + snap).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("dataset", dataset); + values.insert("snap", snap); + + return values; +} + +// Run a replication task +QJsonObject LifePreserver::runReplication(QJsonObject jsin) { + QJsonObject retObject; + QString dataset, host; + + QStringList keys = jsin.keys(); + if(! keys.contains("dataset") || ! keys.contains("host")){ + retObject.insert("error", "Requires dataset and host keys"); + return retObject; + } + + // Get the dataset / host + dataset = jsin.value("dataset").toString(); + host = jsin.value("host").toString(); + + // Make sure we have the dataset / host key(s) + if ( dataset.isEmpty() || host.isEmpty() ) { + retObject.insert("error", "Empty dataset or host keys "); + return retObject; + } + + QStringList output; + output = General::RunCommand("lpreserver replicate run " + dataset + " " + host).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("dataset", dataset); + values.insert("host", host); + + return values; +} + +// Revert to a snapshot +QJsonObject LifePreserver::revertSnapshot(QJsonObject jsin) { + QJsonObject retObject; + QString dataset, snap; + + QStringList keys = jsin.keys(); + if(! keys.contains("dataset") || ! keys.contains("snap")){ + retObject.insert("error", "Requires dataset and snap keys"); + return retObject; + } + + // Get the dataset / snap + dataset = jsin.value("dataset").toString(); + snap = jsin.value("snap").toString(); + + // Make sure we have the dataset / snap key(s) + if ( dataset.isEmpty() || snap.isEmpty() ) { + retObject.insert("error", "Empty dataset or snap keys "); + return retObject; + } + + QStringList output; + output = General::RunCommand("lpreserver revertsnap " + dataset + " " + snap).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("dataset", dataset); + values.insert("snap", snap); + + return values; +} + +// Save system-wide settings +QJsonObject LifePreserver::saveSettings(QJsonObject jsin) { + QJsonObject retObject; + QString warn, email, emailopts, recursive; + + QStringList keys = jsin.keys(); + if(! keys.contains("duwarn") && ! keys.contains("email") && ! keys.contains("emailopts") && !keys.contains("recursive")){ + retObject.insert("error", "Requires duwarn, email, emailopts or recursive to be set!"); + return retObject; + } + + QJsonObject values; + QStringList output; + + // Get the settings + if ( keys.contains("duwarn") ) { + warn = jsin.value("duwarn").toString(); + output = General::RunCommand("lpreserver set duwarn " + warn).split("\n"); + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + values.insert("duwarn", warn); + } + if ( keys.contains("email") ) { + email = jsin.value("email").toString(); + output = General::RunCommand("lpreserver set email " + email).split("\n"); + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + values.insert("email", email); + } + if ( keys.contains("emailopts") ) { + emailopts = jsin.value("emailopts").toString(); + output = General::RunCommand("lpreserver set emailopts " + emailopts.toUpper()).split("\n"); + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + values.insert("emailopts", emailopts); + } + if ( keys.contains("recursive") ) { + recursive = jsin.value("recursive").toString(); + QString recset = "ON"; + if ( recursive.toUpper() == "FALSE" ) + recset = "OFF"; + output = General::RunCommand("lpreserver set recursive " + recset).split("\n"); + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + values.insert("recursive", recursive); + } + + return values; +} + + +// Schedule a new scrub routine +QJsonObject LifePreserver::scheduleScrub(QJsonObject jsin) { + QJsonObject retObject; + QString pool, frequency; + + QStringList keys = jsin.keys(); + if(! keys.contains("pool") || ! keys.contains("frequency")){ + retObject.insert("error", "Requires pool and frequency keys"); + return retObject; + } + + // Check which pool we are looking at + pool = jsin.value("pool").toString(); + frequency = jsin.value("frequency").toString(); + + // Make sure we have the pool / frequency / keep key(s) + if ( pool.isEmpty() || frequency.isEmpty() ) { + retObject.insert("error", "Empty pool or frequency keys "); + return retObject; + } + + QStringList output; + if ( frequency == "none" ) + output = General::RunCommand("lpreserver cronscrub " + pool + " stop " + frequency).split("\n"); + else + output = General::RunCommand("lpreserver cronscrub " + pool + " start " + frequency).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("pool", pool); + values.insert("frequency", frequency); + + return values; +} + + +// Schedule a new snapshot routine +QJsonObject LifePreserver::scheduleSnapshot(QJsonObject jsin) { + QJsonObject retObject; + QString pool, frequency, keep; + + QStringList keys = jsin.keys(); + if(! keys.contains("pool") || ! keys.contains("frequency") || ! keys.contains("keep")){ + retObject.insert("error", "Requires pool, frequency and keep keys"); + return retObject; + } + + // Check which pool we are looking at + pool = jsin.value("pool").toString(); + frequency = jsin.value("frequency").toString(); + keep = jsin.value("keep").toString(); + + // Make sure we have the pool / frequency / keep key(s) + if ( pool.isEmpty() || frequency.isEmpty() || keep.isEmpty() ) { + retObject.insert("error", "Empty pool, frequency and keep keys "); + return retObject; + } + + QStringList output; + if ( frequency == "none" ) + output = General::RunCommand("lpreserver cronsnap " + pool + " stop " ).split("\n"); + else + output = General::RunCommand("lpreserver cronsnap " + pool + " start " + frequency + " " + keep ).split("\n"); + + // Check for any errors + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("ERROR:") != -1 ) { + retObject.insert("error", output.at(i)); + return retObject; + } + } + + // Got to the end, return the good json + QJsonObject values; + values.insert("pool", pool); + values.insert("frequency", frequency); + values.insert("keep", keep); + + return values; +} + + +// Return a list of settings for life-preserver +QJsonObject LifePreserver::settings() { + QJsonObject retObject; + + QStringList output = General::RunCommand("lpreserver get").split("\n"); + QStringList setitems; + QString tmpkey; + QRegExp sep("\\s+"); + + // Parse the output + bool inSection = false; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("-----------------") != -1 ) { + inSection = true; + continue; + } + + if (!inSection) + continue; + + if ( output.at(i).isEmpty() || output.at(i).indexOf("-----------------") != -1 ) + break; + + // Breakdown the setting we got + tmpkey = ""; + setitems.clear(); + setitems << output.at(i).section(":", 0, 0).simplified(); + setitems << output.at(i).section(":", 1, 1).simplified(); + if ( setitems.at(0) == "Recursive mode" ) + tmpkey = "recursive"; + if ( setitems.at(0) == "E-mail notifications" ) + tmpkey = "email"; + if ( setitems.at(0) == "E-mail addresses" ) + tmpkey = "emailaddress"; + if ( setitems.at(0) == "Disk space warn at" ) + tmpkey = "diskwarn"; + + // Unknown key we dont support? + if ( tmpkey.isEmpty() ) + continue; + + retObject.insert(tmpkey, setitems.at(1)); + } + + return retObject; +} diff --git a/src/server/library/sysadm-lifepreserver.h b/src/server/library/sysadm-lifepreserver.h new file mode 100644 index 0000000..4f9c0ba --- /dev/null +++ b/src/server/library/sysadm-lifepreserver.h @@ -0,0 +1,34 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef __PCBSD_LIB_UTILS_LIFEPRESERVER_H +#define __PCBSD_LIB_UTILS_LIFEPRESERVER_H + +#include +#include "sysadm-global.h" + +namespace sysadm{ + +class LifePreserver{ +public: + static QJsonObject addReplication(QJsonObject jsin); + static QJsonObject initReplication(QJsonObject jsin); + static QJsonObject listCron(); + static QJsonObject listReplication(); + static QJsonObject listSnap(QJsonObject jsin); + static QJsonObject removeReplication(QJsonObject jsin); + static QJsonObject removeSnapshot(QJsonObject jsin); + static QJsonObject revertSnapshot(QJsonObject jsin); + static QJsonObject runReplication(QJsonObject jsin); + static QJsonObject saveSettings(QJsonObject jsin); + static QJsonObject scheduleSnapshot(QJsonObject jsin); + static QJsonObject scheduleScrub(QJsonObject jsin); + static QJsonObject settings(); +}; + +} //end of pcbsd namespace + +#endif diff --git a/src/server/library/sysadm-network.cpp b/src/server/library/sysadm-network.cpp new file mode 100644 index 0000000..4a088f8 --- /dev/null +++ b/src/server/library/sysadm-network.cpp @@ -0,0 +1,129 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-network.h" +//PLEASE: Keep the functions in the same order as listed in pcbsd-network.h +#include "sysadm-general.h" + +using namespace sysadm; +//===================== +// NETWORK FUNCTIONS +//===================== +/*QList Network::listNetworkEntries(){ + QList out; + netent *entry = getnetent(); + while(entry!=0){ + //Copy over this data into the output structure + NetworkEntry tmp; + tmp.name = QString::fromLocal8Bit(entry->n_name); + for(int i=0; entry->n_aliases[i] != 0; i++){ + tmp.aliases << QString::fromLocal8Bit(entry->n_aliases[i]); + } + tmp.netnum = entry->n_net; + out << tmp; + //Now load the next entry + entry = getnetent(); + } + endnetent(); //make sure to close the file since we are finished reading it + return out; +}*/ + +//--------------------------------------- +QStringList Network::readRcConf(){ + static QStringList contents = QStringList(); + static QDateTime lastread; + if(!lastread.isValid() || contents.isEmpty() || (QFileInfo("/etc/rc.conf").lastModified()> lastread) ){ + lastread = QDateTime::currentDateTime(); + contents = General::readTextFile("/etc/rc.conf"); + } + return contents; +} + +//--------------------------------------- +NetDevSettings Network::deviceRCSettings(QString dev){ + QStringList info = Network::readRcConf().filter(dev); + //Setup the default structure/values + NetDevSettings set; + set.device = dev; + set.wifihost = set.useDHCP = set.wifisecurity = false; + if(info.isEmpty()){ return set; } //no settings + //Now load any info associated with this device + for(int i=0; i wlan device) + //Wifi settings + QString wifiCountry, wifiSSID, wifiBSSID, wifiChannel; + bool wifihost; + bool wifisecurity; //always recommended for wifi settings - otherwise can't connect to secure access points + //Addressing + bool useDHCP; + QString staticIPv4, staticNetmask, staticIPv6; //assumes DHCP if none are set + QString staticGateway; +}; + +//General data class for network devices +// Note: Sources in NetDevice.cpp +class NetDevice{ +private: + QString name; +public: + NetDevice(QString devName){ name = devName; } + QString device(){ return name; } //full device name (wlan0) + QString devName(); //name only (wlan) + uint devNum(); //number only (0) + QString ipAsString(); + QString ipv6AsString(); + QString netmaskAsString(); + QString desc(); + QString macAsString(); + //QString mediaTypeAsString(); + QString mediaStatusAsString(); + QString gatewayAsString(); + bool isWireless(); + QString getWifiParent(); + bool usesDHCP(); + bool isUp(); + long packetsRx(); + long packetsTx(); + long errorsRx(); + long errorsTx(); + + //Setting Functions (to make changes - requires root) + void setUp(bool up); //Turn device on/off (temporary - not saved globally) + + //Generic listing of devices + static QStringList listNetDevices(); //probe the system for all network devices +}; + +//General data structure for wifi access points (local or available) +struct NetWifi{ + NetCrypt encryption; + QString BSSID, SSID; +}; + + +//The general-purpose class that any user/app can utilitize +class Network{ +public: + //static QList listNetworkEntries(); + static QStringList readRcConf(); //use this when reading /etc/rc.conf for network stuff - prevents opening the file repeatedly + static NetDevSettings deviceRCSettings(QString dev); //settings in rc.conf (bootup) + //static NetDevSettings deviceIfconfigSettings(QString dev); //settings currently running +}; + +//The class that requires overarching root permissions (usually for changes to system) +class NetworkRoot{ +public: + //static bool saveNetworkEntry(NetworkEntry); //**Not implemented yet** + static bool saveRCSettings(NetDevSettings); //rc.conf settings (bootup) + static bool setIfconfigSettings(NetDevSettings); //ifconfig settings (temporary session) +}; + +} //end of pcbsd namespace + +#endif \ No newline at end of file diff --git a/src/server/library/sysadm-servicemanager.cpp b/src/server/library/sysadm-servicemanager.cpp new file mode 100644 index 0000000..0a12acd --- /dev/null +++ b/src/server/library/sysadm-servicemanager.cpp @@ -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 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; + } + } + } +} diff --git a/src/server/library/sysadm-servicemanager.h b/src/server/library/sysadm-servicemanager.h new file mode 100644 index 0000000..6b70d06 --- /dev/null +++ b/src/server/library/sysadm-servicemanager.h @@ -0,0 +1,73 @@ +#ifndef SERVICEMANAGER_H +#define SERVICEMANAGER_H +#include +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 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 services; + void loadServices(); + QString chroot; + QString ip; +}; +} +#endif // SERVICEMANAGER_H diff --git a/src/server/library/sysadm-systeminfo.cpp b/src/server/library/sysadm-systeminfo.cpp new file mode 100644 index 0000000..235da49 --- /dev/null +++ b/src/server/library/sysadm-systeminfo.cpp @@ -0,0 +1,251 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-general.h" +#include "sysadm-systeminfo.h" +#include "sysadm-global.h" + +using namespace sysadm; + +//PLEASE: Keep the functions in the same order as listed in pcbsd-general.h + + +//Battery Availability +QJsonObject SysInfo::batteryInfo(){ + QJsonObject retObject; + bool ok; + + int val = General::RunCommand("apm -l").toInt(&ok); + if ( ok && (val >= 0 && val <= 100) ) { + retObject.insert("battery", "true"); + } else { + retObject.insert("battery", "false"); + return retObject; + } + + // We have a battery, return info about it + //Battery Charge Level + QString tmp; + tmp.setNum(val); + retObject.insert("level", tmp); + + //Battery Charging State + int state = General::RunCommand("apm -a").toInt(&ok); + if ( ok && state == 0 ) + retObject.insert("status", "offline"); + else if ( ok && state == 1 ) + retObject.insert("status", "charging"); + else if ( ok && state == 2 ) + retObject.insert("status", "backup"); + else + retObject.insert("status", "unknown"); + + int timeleft = General::RunCommand("apm -t").toInt(&ok); + if ( ok ) { + tmp.setNum(timeleft); + retObject.insert("timeleft", tmp); + } else { + retObject.insert("timeleft", "-1"); + } + + return retObject; +} + +// KPM 1-21-2016 +// This needs to be looked at, I'm not 100% sure it is returning correct busy % +// We probably want to supply more info as well, such as user,nice,system,interrupt,idle +QJsonObject SysInfo::cpuPercentage() { + QJsonObject retObject; + QString tmp; + + //Calculate the percentage based on the kernel information directly - no extra utilities + QStringList result = General::RunCommand("sysctl -n kern.cp_times").split(" "); + static QStringList last = QStringList(); + if(last.isEmpty()){ + //need two ticks before it works properly + sleep(1); + last = result; + result = General::RunCommand("sysctl -n kern.cp_times").split(" "); + } + double tot = 0; + double roundtot; + int cpnum = 0; + for(int i=4; i::::::::] + //Note: = [USB, HDRIVE, DVD, SDCARD, UNKNOWN] + QStringList devs = General::RunCommand("mount").split("\n"); + + //Now check the output + for(int i=0; i +#include "sysadm-global.h" + +namespace sysadm{ + +class SysInfo{ +public: + static QJsonObject batteryInfo(); + static QJsonObject cpuPercentage(); + static QJsonObject cpuTemps(); + static QJsonObject externalDevicePaths(); + static QJsonObject memoryStats(); + static QJsonObject systemInfo(); +}; + +} //end of pcbsd namespace + +#endif diff --git a/src/server/library/sysadm-update.cpp b/src/server/library/sysadm-update.cpp new file mode 100644 index 0000000..27694f3 --- /dev/null +++ b/src/server/library/sysadm-update.cpp @@ -0,0 +1,152 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include +#include "sysadm-general.h" +#include "sysadm-update.h" +#include "sysadm-global.h" +#include "globals.h" + +using namespace sysadm; + +//PLEASE: Keep the functions in the same order as listed in pcbsd-general.h + +// Return a list of updates available +QJsonObject Update::checkUpdates() { + QJsonObject retObject; + + QStringList output = General::RunCommand("pc-updatemanager check").split("\n"); + QString nameval; + + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("Your system is up to date!") != -1 ) + { + retObject.insert("status", "noupdates"); + return retObject; + } + if ( output.at(i).indexOf("NAME: ") != -1 ) + nameval = output.at(i).section(" ", 1, 1); + + if ( output.at(i).indexOf("TYPE: SECURITYUPDATE") != -1 ) { + QJsonObject itemvals; + itemvals.insert("name", nameval); + retObject.insert("security", itemvals); + } + if ( output.at(i).indexOf("TYPE: SYSTEMUPDATE") != -1 ) { + QJsonObject itemvals; + itemvals.insert("name", nameval); + if ( output.size() > ( i + 1) ) + itemvals.insert("tag", output.at(i+1).section(" ", 1, 1)); + if ( output.size() > ( i + 2) ) + itemvals.insert("version", output.at(i+2).section(" ", 1, 1)); + retObject.insert("majorupgrade", itemvals); + } + if ( output.at(i).indexOf("TYPE: PATCH") != -1 ) { + QJsonObject itemvals; + itemvals.insert("name", nameval); + if ( output.size() > ( i + 1) ) + itemvals.insert("tag", output.at(i+1).section(" ", 1, 1)); + if ( output.size() > ( i + 2) ) + itemvals.insert("details", output.at(i+2).section(" ", 1, 1)); + if ( output.size() > ( i + 3) ) + itemvals.insert("date", output.at(i+3).section(" ", 1, 1)); + if ( output.size() > ( i + 4) ) + itemvals.insert("size", output.at(i+4).section(" ", 1, 1)); + retObject.insert("patch", itemvals); + } + if ( output.at(i).indexOf("TYPE: PKGUPDATE") != -1 ) { + QJsonObject itemvals; + itemvals.insert("name", nameval); + retObject.insert("packageupdate", itemvals); + } + } + + // Update status that we have updates + retObject.insert("status", "updatesavailable"); + return retObject; +} + +// List available branches we can switch to +QJsonObject Update::listBranches() { + QJsonObject retObject; + + QStringList output = General::RunCommand("pc-updatemanager branches").split("\n"); + + bool inSection = false; + for ( int i = 0; i < output.size(); i++) + { + if ( output.at(i).indexOf("-----------------") != -1 ) { + inSection = true; + continue; + } + + if (!inSection) + continue; + + if ( output.at(i).isEmpty() ) + break; + + QString tmp = output.at(i).section(" ", 0, 0); + QString tmp2 = output.at(i).section(" ", 1, 1); + if ( tmp2 == "*" ) + retObject.insert(tmp, "active"); + else + retObject.insert(tmp, "available"); + } + + return retObject; +} + +// Kickoff an update process +QJsonObject Update::startUpdate(QJsonObject jsin) { + QJsonObject retObject; + + QStringList keys = jsin.keys(); + if (! keys.contains("target") ) { + retObject.insert("error", "Missing required key 'target'"); + return retObject; + } + // Save the target + QString target; + target = jsin.value("target").toString(); + + QString flags; + if ( target == "chbranch" ) { + if (! keys.contains("branch") ) { + retObject.insert("error", "Missing required key 'branch'"); + return retObject; + } + flags = "chbranch " + jsin.value("branch").toString(); + } else if ( target == "pkgupdate" ) { + flags = "pkgupdate"; + } else if ( target == "fbsdupdate" ) { + flags = "fbsdupdate"; + } else if ( target == "fbsdupdatepkgs" ) { + flags = "fbsdupdatepkgs"; + } else if ( target == "standalone" ) { + if (! keys.contains("tag") ) { + retObject.insert("error", "Missing required key 'tag'"); + return retObject; + } + flags = "install " + jsin.value("tag").toString(); + } else { + // Ruh-roh + retObject.insert("error", "Unknown target key: " + target); + return retObject; + } + + // Create a unique ID for this queued action + QString ID = QUuid::createUuid().toString(); + + DISPATCHER->queueProcess(ID, "pc-updatemanager " + flags); + + // Return some details to user that the action was queued + retObject.insert("command", "pc-updatemanger " + flags); + retObject.insert("comment", "Task Queued"); + retObject.insert("queueid", ID); + return retObject; +} diff --git a/src/server/library/sysadm-update.h b/src/server/library/sysadm-update.h new file mode 100644 index 0000000..1a96ded --- /dev/null +++ b/src/server/library/sysadm-update.h @@ -0,0 +1,24 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#ifndef __PCBSD_LIB_UTILS_UPDATER_H +#define __PCBSD_LIB_UTILS_UPDATER_H + +#include +#include "sysadm-global.h" + +namespace sysadm{ + +class Update{ +public: + static QJsonObject checkUpdates(); + static QJsonObject listBranches(); + static QJsonObject startUpdate(QJsonObject); +}; + +} //end of pcbsd namespace + +#endif diff --git a/src/server/library/sysadm-usermanager.cpp b/src/server/library/sysadm-usermanager.cpp new file mode 100644 index 0000000..71f30ce --- /dev/null +++ b/src/server/library/sysadm-usermanager.cpp @@ -0,0 +1,546 @@ +//=========================================== +// PC-BSD source code +// Copyright (c) 2015, PC-BSD Software/iXsystems +// Available under the 3-clause BSD license +// See the LICENSE file for full details +//=========================================== +#include "sysadm-usermanager.h" +#include "sysadm-general.h" +using namespace sysadm; + +UserManager::UserManager(QString chroot) +{ + this->chroot = chroot; + loadUsers(); + loadGroups(); + loadShells(); +} + +void UserManager::NewUser(QString fullName, QString userName, QString password, QString home, QString shell, int uid, int gid, bool encrypt) +{ + User user; + user.UserName = userName; + user.FullName = fullName; + user.HomeFolder = (home.isEmpty())?"/usr/home/"+userName : home; + user.Shell = shell; + + //Add User + qDebug() << "Adding user " << userName; + // Create the zfs dataset associated with the home directory + if ( chroot.isEmpty() ) + { + QStringList args; + args.append(user.HomeFolder); + General::RunCommand("/usr/local/share/pcbsd/scripts/mkzfsdir.sh",args); + } + + QStringList args; + if ( ! chroot.isEmpty() ) //if chroot is not empty the command starts with chroot instead of pw + args << chroot << "pw"; //and thus we have to add it as an argument + args << "useradd"; //create a user + args << userName; //with this userName + args << "-c"; //sets the comment field + args << "\""+ fullName+"\""; //with the full name of the user + args << "-m"; //create the user's home directory + if(!home.isEmpty()) + { + args << "-d"; //set the home directory to + args << home; //this + } + args << "-s"; //set the user's shell + args << shell; //to this + if(gid != -1) + { + args << "-g"; //set the group id to + args << QString::number(gid); //this + } + if(uid != -1) + { + args << "-u"; //set the user id to + args << QString::number(uid); //this + } + args << "-G"; //additionally add the user to + args << "operator"; //the operator's group + + if ( ! chroot.isEmpty() ) //if we're operating with a chroot call + General::RunCommand("chroot", args); + else //otherwise + General::RunCommand("pw", args); + + ChangeUserPassword(user,password); + + //enable flash for the user + if ( chroot.isEmpty() ) { //if we're not in a chroot + qDebug() << "Enabling Flash Plugin for " << userName; + args.clear(); + args << userName; //run command as this user + args << "-c"; //with the command + args << "\"flashpluginctl on\""; //turn on flashpluginctl + General::RunCommand("su",args); + } + + //if we're going to PersonaCrypt the home directory + if(encrypt) + initPCDevice(user,home,password); + + //reloads the groups and users so that the internal model is consistent + loadUsers(); + loadGroups(); +} + +void UserManager::DeleteUser(User user) +{ + //Delete User + qDebug() << "Deleting user " << user.UserName; + + //remove the dataset associated with the home folder + QStringList args; + args << user.HomeFolder; + General::RunCommand("/usr/local/share/pcbsd/scripts/rmzfsdir.sh",args); + + //delete the user and their home directory + args.clear(); + if ( ! chroot.isEmpty() ) //if we're in a chroot we need to use chroot before pw + args << chroot << "pw"; + args << "userdel"; //delete a user + args << user.UserName; //this user + args << "-r"; //remove the contents of the user's home directory + if ( ! chroot.isEmpty() ) + General::RunCommand("chroot", args); + else + General::RunCommand("pw", args); + + loadUsers(); + loadGroups(); +} + +const QVector UserManager::GetUsers() +{ + return users; +} + +const User UserManager::GetUser(int id) +{ + for(User user: users) + { + if(user.ID == id) + return user; + } + return User(); +} + +const User UserManager::GetUser(QString userName) +{ + for(User user: users) + { + if(user.UserName == userName) + return user; + } + return User(); +} + +void UserManager::ChangeUserPassword(User user, QString newPassword) +{ + //Don't Change the password of a user with an encrypted Home directory + if( !QFile::exists("/var/db/personacrypt/"+user.UserName+".key") ){ return; } + + //Create a temporary file to store the password in + QTemporaryFile nfile("/tmp/.XXXXXXXX"); + if ( nfile.open() ) + { + QTextStream stream( &nfile ); + stream << newPassword; + nfile.close(); + } + + //set the user password + QStringList args; + args.append(nfile.fileName()); //the temp file holding the password + args.append("|"); //which we're going to pipe to the stdin of + if ( ! chroot.isEmpty() ) //if we're in a chroot + { + args << "chroot"; //a chroot + args << chroot; //located here + } + args << "pw"; //change users + args << "usermod"; //where we're going to modify a user + args << user.UserName;//this user + args << "-h"; //set the user's password + args << "0"; //using stdin + General::RunCommand("cat",args); + + //remove the temp file holding the password + nfile.remove(); + +} + +void UserManager::ChangeUserShell(User user, QString shell) +{ + if(shells.contains(shell)) + { + qDebug("Shell found"); + QStringList args; + args << "usermod"; // modify the user + args << "-n"; //specify a user name + args << user.UserName; //for this user + args << "-s"; //set the shell to + args << shell; //this shell + General::RunCommand("pw",args); + } + else + qDebug("Shell not found"); + + loadUsers(); +} + +void UserManager::ChangeUserFullName(User user, QString newName) +{ + QStringList args; + args << "usermod"; //modify the user + args << user.UserName; //for this user + args << "-c"; //change the gecos field to + args << newName; //this name + General::RunCommand("pw",args); + loadUsers(); +} + +void UserManager::AddUserToGroup(User user, Group group) +{ + QStringList args; + args << "groupmod"; //modify a group + args << "-n"; //modify for a group + args << group.Name;//this group + args << "-m";//by adding a member + args << user.UserName; //this user + General::RunCommand("pw",args); + + loadGroups(); +} + +void UserManager::RemoveUserFromGroup(User user, Group group) +{ + QStringList args; + args << "groupmod"; //modify a group + args << "-n"; //modify for a group + args << group.Name; //this group + args << "-d"; //by removing a user + args << user.UserName ; //this user + General::RunCommand("pw", args); + + loadGroups(); +} + +void UserManager::NewGroup(QString name, QStringList members) +{ + QStringList args; + qDebug() << "Adding group " << name; + if ( ! chroot.isEmpty() ) //if we're in a chroot need to add chroot before pw + args << chroot << "pw"; + args << "groupadd"; //create a new group + args << name; // with this name + args << "-M"; //with this list of users + args << members.join(","); //these guys + if ( ! chroot.isEmpty() ) //if we're in a chroot + General::RunCommand("chroot", args); + else + General::RunCommand("pw", args); + + loadGroups(); +} + +void UserManager::DeleteGroup(Group group) +{ + QStringList args; + qDebug() << "Deleting group " << group.Name; + if ( ! chroot.isEmpty() ) //if we're in a chroot need to add chroot before pw + args << chroot << "pw"; + args << "groupdel"; //delete a group + args << group.Name; //of this name + if ( ! chroot.isEmpty() ) //if we're in a chroot + General::RunCommand("chroot", args); + else + General::RunCommand("pw", args); + + loadGroups(); +} + +const QVector UserManager::GetGroups() +{ + return groups; +} + +const Group UserManager::getGroup(int id) +{ + for(Group group : groups) + { + if(group.ID == id) + return group; + } + return Group(); +} + +const Group UserManager::getGroup(QString name) +{ + for(Group group : groups) + { + if(group.Name == name) + return group; + } + return Group(); +} + +const QStringList UserManager::GetShells() +{ + return shells; +} + +void UserManager::loadUsers() +{ + users.clear(); + QStringList userStrings; + QStringList args; + if(!chroot.isEmpty()) + { + args << chroot; + args << "pw"; + } + args << "usershow"; + args << "-a"; + if(chroot.isEmpty()) + userStrings = General::RunCommand("pw",args).split("\n"); + else + userStrings = General::RunCommand("chroot",args).split("\n"); + + //remove the empty string at the end + userStrings.removeLast(); + + for(QString line : userStrings) + { + User user; + user.UserName = line.section(":",0,0); + user.ID = line.section(":",2,2).toInt(); + user.GroupID = line.section(":",3,3).toInt(); + user.HomeFolder = line.section(":",8,8); + user.Shell = line.section(":",9,9); + user.FullName = line.section(":",7,7); + + users.append(user); + } +} + +void UserManager::loadGroups() +{ + groups.clear(); + QStringList groupStrings; + QStringList args; + if(!chroot.isEmpty()) + { + args << chroot; + args << "pw"; + } + args << "groupshow"; + args << "-a"; + if(chroot.isEmpty()) + groupStrings = General::RunCommand("pw",args).split("\n"); + else + groupStrings = General::RunCommand("chroot",args).split("\n"); + + //remove the empty string at the end + groupStrings.removeLast(); + + for(QString line : groupStrings) + { + Group group; + group.Name = line.section(":",0,0); + group.ID = line.section(":",2,2).toInt(); + QString memberString = line.section(":",3,3); + group.Members = memberString.split(","); + + groups.append(group); + } +} + +void UserManager::loadShells() +{ + shells.clear(); + QFile shellFile(chroot + "/etc/shells"); + if ( shellFile.open(QIODevice::ReadOnly) ) { + QTextStream stream(&shellFile); + stream.setCodec("UTF-8"); + + QString line; + + while ( !stream.atEnd() ) { + line = stream.readLine(); + + if ( !line.startsWith("#") && !line.isEmpty() ) { //Make sure it isn't a comment or blank + shells.append(line); + } + } + } else { + //Unable to open file error + qWarning("Error! Unable to open /etc/shells"); + } + + // Add /sbin/nologin as well + shells.append("/sbin/nologin"); +} + + +void UserManager::importPCKey(User user, QString filename){ + //Double check that the key does not exist (button should have been hidden earlier if invalid) + if( QFile::exists("/var/db/personacrypt/"+user.UserName+".key") ){ return; } + + //if the location is empty cancel + if(filename.isEmpty()){ return; } + + //Now run the import command + QStringList args; + args << "import"; + args << "\""+filename + "\""; + if( 0 == General::RunCommand("personacrypt",args) ){ + //Success + qDebug("The key file was imported successfully."); + }else{ + //Failure + qWarning("The key file could not be imported. Please ensure you are using a valid file."); + } +} + +void UserManager::exportPCKey(User user, QString filename){ + //Double check that the key exists (button should have been hidden earlier if invalid) + if( !QFile::exists("/var/db/personacrypt/"+user.UserName+".key") ){ return; } + + if(filename.isEmpty()){ return; } //cancelled + if( !filename.endsWith(".key") ){ filename.append(".key"); } + //Now get/save the key file + QStringList args; + args << "export"; + args << "\"" + user.UserName + "\""; + QString key = General::RunCommand("personacrypt",args); + + QFile file(filename); + if( !file.open(QIODevice::WriteOnly | QIODevice::Truncate) ){ + //Could not open output file + qWarning() <<"Output file could not be opened:\n\n" << filename; + return; + } + QTextStream out(&file); + out << key; + file.close(); + qDebug() << "The PersonaCrypt key has been saved successfully: \n\n" << filename; +} + +void UserManager::disablePCKey(User user){ +//Double check that the key exists (button should have been hidden earlier if invalid) + if( !QFile::exists("/var/db/personacrypt/"+user.UserName+".key") ){ return; } + + if( QFile::remove("/var/db/personacrypt/"+user.UserName+".key") ){ + //Success + qDebug("The PersonaCrypt user key has been disabled." ); + }else{ + //Failure (should almost never happen, since this utility runs as root and just needs to delete a file) + qDebug("The PersonaCrypt user key could not be removed. Do you have the proper permissions?" ); + } +} + +void UserManager::disableAndCopyPCKey(User user, QString password){ + QStringList args; + args << "list"; + QStringList cusers = General::RunCommand("personacrypt",args).split("\n"); + bool available = false; + for(int i=0; i