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:
@@ -21,12 +21,15 @@ Every lifepreserver class request contains the following parameters:
|
||||
| | | |
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| action | | supported actions include "listcron", "cronsnap", "cronscrub", "listsnap", "revertsnap", "removesnap", |
|
||||
| | | "addreplication", "settings", and "savesettings" |
|
||||
| | | "addreplication", "removereplication", "listreplication", "runreplication", "initreplication", "settings", and |
|
||||
| | | "savesettings" |
|
||||
| | | |
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
The rest of this section provides examples of the available *actions* for each type of request, along with their responses.
|
||||
|
||||
.. index:: listcron, Life Preserver
|
||||
|
||||
.. _List Schedules:
|
||||
|
||||
List Schedules
|
||||
@@ -93,6 +96,8 @@ the time that snapshots are taken. If scrubs have been configured on that ZFS po
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: cronsnap, Life Preserver
|
||||
|
||||
.. _Create a Snapshot Schedule:
|
||||
|
||||
Create a Snapshot Schedule
|
||||
@@ -175,6 +180,8 @@ The "cronsnap" action is used to create snapshot schedules for Life Preserver. T
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: cronscrub, Life Preserver
|
||||
|
||||
.. _Create a Scrub Schedule:
|
||||
|
||||
Create a Scrub Schedule
|
||||
@@ -249,6 +256,8 @@ The "cronscrub" action is used to schedule a ZFS scrub. This action supports the
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: listsnap, Life Preserver
|
||||
|
||||
.. _List Snapshots:
|
||||
|
||||
List Snapshots
|
||||
@@ -329,6 +338,8 @@ The "listsnap" action retrieves the list of saved snapshots.
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: revertsnap, Life Preserver
|
||||
|
||||
.. _Revert a Snapshot:
|
||||
|
||||
Revert a Snapshot
|
||||
@@ -394,6 +405,8 @@ The "revertsnap" action is used to rollback the contents of the specified datase
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: removesnap, Life Preserver
|
||||
|
||||
.. _Remove a Snapshot:
|
||||
|
||||
Remove a Snapshot
|
||||
@@ -456,6 +469,8 @@ The "removesnap" action is used to remove a ZFS snapshot from the specified data
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: addreplication, Life Preserver
|
||||
|
||||
.. _Add Replication:
|
||||
|
||||
Add Replication
|
||||
@@ -564,6 +579,301 @@ The "addreplication" action is used to create a replication task in Life Preserv
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: removereplication, Life Preserver
|
||||
|
||||
.. _Remove Replication:
|
||||
|
||||
Remove Replication
|
||||
===============
|
||||
|
||||
The "removereplication" action is used to delete an existing replication task. Note that this action only deletes the task--it does not remove any already replicated data from the
|
||||
remote system.
|
||||
|
||||
This action supports the following parameters:
|
||||
|
||||
+---------------------------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| **Parameter** | **Description** |
|
||||
| | |
|
||||
+=================================+======================================================================================================================+
|
||||
| host | remote hostname or IP address |
|
||||
| | |
|
||||
+---------------------------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| dataset | name of local dataset to remove from replication |
|
||||
| | |
|
||||
+---------------------------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
**REST Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
PUT /sysadm/lifepreserver
|
||||
{
|
||||
"dataset" : "tank",
|
||||
"host" : "192.168.0.10",
|
||||
"action" : "removereplication"
|
||||
}
|
||||
|
||||
**REST Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"removereplication": {
|
||||
"dataset": "tank",
|
||||
"host": "192.168.0.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id" : "fooid",
|
||||
"args" : {
|
||||
"action" : "removereplication",
|
||||
"dataset" : "tank",
|
||||
"host" : "192.168.0.10"
|
||||
},
|
||||
"name" : "lifepreserver",
|
||||
"namespace" : "sysadm"
|
||||
}
|
||||
|
||||
**WebSocket Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"removereplication": {
|
||||
"dataset": "tank",
|
||||
"host": "192.168.0.10"
|
||||
}
|
||||
},
|
||||
"id": "fooid",
|
||||
"name": "response",
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: listreplication, Life Preserver
|
||||
|
||||
.. _List Replications:
|
||||
|
||||
List Replications
|
||||
=================
|
||||
|
||||
The "listreplication" action is used to retrieve the settings of configured replication tasks. For each task, the response includes the name of the local ZFS pool or dataset to replicate,
|
||||
the IP address and listening port number of the remote system to replicate to, when the replication occurs (see the "frequency" description in :ref:`Add Replication`), the name of the
|
||||
dataset on the remote system to store the replicated data ("rdset"), and the name of the replication user account.
|
||||
|
||||
**REST Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
PUT /sysadm/lifepreserver
|
||||
{
|
||||
"action" : "listreplication"
|
||||
}
|
||||
|
||||
**REST Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"listreplication": {
|
||||
"tank1->192.168.0.9": {
|
||||
"dataset": "tank1",
|
||||
"frequency": "22",
|
||||
"host": "192.168.0.9",
|
||||
"port": "22",
|
||||
"rdset": "tank/backups",
|
||||
"user": "backups"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"namespace" : "sysadm",
|
||||
"args" : {
|
||||
"action" : "listreplication"
|
||||
},
|
||||
"id" : "fooid",
|
||||
"name" : "lifepreserver"
|
||||
}
|
||||
|
||||
**WebSocket Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"listreplication": {
|
||||
"tank1->192.168.0.9": {
|
||||
"dataset": "tank1",
|
||||
"frequency": "22",
|
||||
"host": "192.168.0.9",
|
||||
"port": "22",
|
||||
"rdset": "tank/backups",
|
||||
"user": "backups"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": "fooid",
|
||||
"name": "response",
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: runreplication, Life Preserver
|
||||
|
||||
.. _Start Replication:
|
||||
|
||||
Start Replication
|
||||
=================
|
||||
|
||||
The "runreplication" action can be used to manually replicate the specified dataset to the specified remote server.
|
||||
|
||||
**REST Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
PUT /sysadm/lifepreserver
|
||||
{
|
||||
"host" : "10.0.10.100",
|
||||
"dataset" : "mypool",
|
||||
"action" : "runreplication"
|
||||
}
|
||||
|
||||
**REST Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"runreplication": {
|
||||
"dataset": "mypool",
|
||||
"host": "10.0.10.100"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id" : "fooid",
|
||||
"name" : "lifepreserver",
|
||||
"args" : {
|
||||
"host" : "10.0.10.100",
|
||||
"dataset" : "mypool",
|
||||
"action" : "runreplication"
|
||||
},
|
||||
"namespace" : "sysadm"
|
||||
}
|
||||
|
||||
**WebSocket Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"runreplication": {
|
||||
"dataset": "mypool",
|
||||
"host": "10.0.10.100"
|
||||
}
|
||||
},
|
||||
"id": "fooid",
|
||||
"name": "response",
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: initreplication, Life Preserver
|
||||
|
||||
.. _Initialize Replication:
|
||||
|
||||
Initialize Replication
|
||||
======================
|
||||
|
||||
The "initreplication" action can be used to clear the replication data on the remote server. This is useful if a replication becomes stuck. After running this action, issue a
|
||||
"runreplication" action to start a new replication.
|
||||
|
||||
The "initreplication" action supports the following parameters:
|
||||
|
||||
+---------------------------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| **Parameter** | **Description** |
|
||||
| | |
|
||||
+=================================+======================================================================================================================+
|
||||
| host | remote hostname or IP address |
|
||||
| | |
|
||||
+---------------------------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| dataset | name of local dataset or pool being replicated |
|
||||
| | |
|
||||
+---------------------------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
**REST Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
PUT /sysadm/lifepreserver
|
||||
{
|
||||
"dataset" : "tank1",
|
||||
"host" : "192.168.0.9",
|
||||
"action" : "initreplication"
|
||||
}
|
||||
|
||||
**REST Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"initreplication": {
|
||||
"dataset": "tank1",
|
||||
"host": "192.168.0.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id" : "fooid",
|
||||
"args" : {
|
||||
"host" : "192.168.0.9",
|
||||
"dataset" : "tank1",
|
||||
"action" : "initreplication"
|
||||
},
|
||||
"namespace" : "sysadm",
|
||||
"name" : "lifepreserver"
|
||||
}
|
||||
|
||||
**WebSocket Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"initreplication": {
|
||||
"dataset": "tank1",
|
||||
"host": "192.168.0.9"
|
||||
}
|
||||
},
|
||||
"id": "fooid",
|
||||
"name": "response",
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: settings, Life Preserver
|
||||
|
||||
.. _View Settings:
|
||||
|
||||
@@ -630,6 +940,8 @@ Run :command:`lpreserver help set` for more information about each available set
|
||||
"namespace": "sysadm"
|
||||
}
|
||||
|
||||
.. index:: savesettings, Life Preserver
|
||||
|
||||
.. _Save Settings:
|
||||
|
||||
Save Settings
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
.. _Connection:
|
||||
|
||||
Connection
|
||||
==========
|
||||
|
||||
Some intro text here...
|
||||
|
||||
.. _Getting Started:
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
***************
|
||||
|
||||
Some intro text here...
|
||||
|
||||
|
||||
Add some links to docs on websockets and json....
|
||||
|
||||
.. _Authentication:
|
||||
|
||||
Authentication
|
||||
--------------
|
||||
==============
|
||||
|
||||
Describe how to authenticate to websockets via Local / Remote, local connections do not need username / password...
|
||||
|
||||
@@ -44,7 +40,7 @@ request contains the following parameters:
|
||||
|
||||
Several methods are available for authentication. Here is an example of a login using a username and password:
|
||||
|
||||
**Request**
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -60,7 +56,7 @@ Several methods are available for authentication. Here is an example of a login
|
||||
|
||||
Here is an example of using token authentication, where the token is invalidated after 5 minutes of inactivity:
|
||||
|
||||
**Request**
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -75,7 +71,7 @@ Here is an example of using token authentication, where the token is invalidated
|
||||
|
||||
A successful authentication will provide a reply similar to this:
|
||||
|
||||
**Reply**
|
||||
**WebSocket Reply**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -95,7 +91,7 @@ A successful authentication will provide a reply similar to this:
|
||||
|
||||
An invalid authentication, or a system request after the user session has timed out due to inactivity, looks like this:
|
||||
|
||||
**Reply**
|
||||
**WebSocket Reply**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -111,7 +107,7 @@ An invalid authentication, or a system request after the user session has timed
|
||||
|
||||
To clear a pre-saved authentication token, such as signing out, use this request:
|
||||
|
||||
**Request**
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
@@ -121,3 +117,79 @@ To clear a pre-saved authentication token, such as signing out, use this request
|
||||
"id" : "sampleID",
|
||||
"args" : "junk argument"
|
||||
}
|
||||
|
||||
.. _Server Subsystems:
|
||||
|
||||
Server Subsystems
|
||||
=================
|
||||
|
||||
An RPC query can be issued to probe all the known subsystems and return which ones are currently available and what level of read and write access the user has.
|
||||
A query contains the following parameters:
|
||||
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| **Parameter** | **Value** | **Description** |
|
||||
| | | |
|
||||
+=================================+===============+======================================================================================================================+
|
||||
| id | | any unique value for the request; examples include a hash, checksum, or uuid |
|
||||
| | | |
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| name | query | |
|
||||
| | | |
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| namespace | rpc | |
|
||||
| | | |
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
| args | | can be any data |
|
||||
| | | |
|
||||
+---------------------------------+---------------+----------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
**REST Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
PUT /rpc/query
|
||||
{
|
||||
"junk" : "junk"
|
||||
}
|
||||
|
||||
**REST Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"rpc/dispatcher": "read/write",
|
||||
"rpc/syscache": "read",
|
||||
"sysadm/lifepreserver": "read/write",
|
||||
"sysadm/network": "read/write"
|
||||
}
|
||||
}
|
||||
|
||||
**WebSocket Request**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"id" : "fooid",
|
||||
"name" : "query",
|
||||
"namespace" : "rpc",
|
||||
"args" : {
|
||||
"junk" : "junk"
|
||||
}
|
||||
}
|
||||
|
||||
**WebSocket Response**
|
||||
|
||||
.. code-block:: json
|
||||
|
||||
{
|
||||
"args": {
|
||||
"rpc/dispatcher": "read/write",
|
||||
"rpc/syscache": "read",
|
||||
"sysadm/lifepreserver": "read/write",
|
||||
"sysadm/network": "read/write"
|
||||
},
|
||||
"id": "fooid",
|
||||
"name": "response",
|
||||
"namespace": "rpc"
|
||||
}
|
||||
@@ -14,17 +14,19 @@ HEADERS += sysadm-global.h \
|
||||
sysadm-general.h \
|
||||
sysadm-lifepreserver.h \
|
||||
sysadm-network.h \
|
||||
sysadm-firewall.h
|
||||
sysadm-firewall.h \
|
||||
sysadm-usermanager.h
|
||||
|
||||
SOURCES += sysadm-general.cpp \
|
||||
sysadm-lifepreserver.cpp \
|
||||
sysadm-network.cpp \
|
||||
NetDevice.cpp \
|
||||
sysadm-firewall.cpp
|
||||
sysadm-firewall.cpp \
|
||||
sysadm-usermanager.cpp
|
||||
|
||||
include.path=/usr/local/include/
|
||||
include.files=sysadm-*.h
|
||||
|
||||
INSTALLS += target include
|
||||
INSTALLS += target include
|
||||
|
||||
QMAKE_LIBDIR = /usr/local/lib/qt5 /usr/local/lib
|
||||
|
||||
@@ -83,6 +83,46 @@ QJsonObject LifePreserver::addReplication(QJsonObject jsin) {
|
||||
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;
|
||||
@@ -146,6 +186,54 @@ QJsonObject LifePreserver::listCron() {
|
||||
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;
|
||||
@@ -199,6 +287,47 @@ QJsonObject LifePreserver::listSnap(QJsonObject jsin) {
|
||||
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;
|
||||
@@ -240,6 +369,47 @@ QJsonObject LifePreserver::removeSnapshot(QJsonObject jsin) {
|
||||
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;
|
||||
|
||||
@@ -15,10 +15,14 @@ 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);
|
||||
|
||||
546
src/library/sysadm-usermanager.cpp
Normal file
546
src/library/sysadm-usermanager.cpp
Normal file
@@ -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<User> 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<Group> 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<cusers.length(); i++){
|
||||
if(cusers[i].section(" on ",0,0) == user.UserName){ available = true; break; } //disk is connected to the system
|
||||
}
|
||||
if(!available){
|
||||
//Warn the user that they need to plug in their USB stick first
|
||||
qWarning("PersonaCrypt Device Not Found, Please ensure that your PersonaCrypt device is connected to the system and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(password.isEmpty()){ return; } //cancelled
|
||||
//Save the password to a temporary file
|
||||
QTemporaryFile tmpfile("/tmp/.XXXXXXXXXXXXXXXXXXXX");
|
||||
if( !tmpfile.open() ){ return; } //could not create a temporary file (extremely rare)
|
||||
QTextStream out(&tmpfile);
|
||||
out << password;
|
||||
tmpfile.close();
|
||||
|
||||
//Now run the PersonaCrypt command
|
||||
args.clear();
|
||||
args << "remove";
|
||||
args << "\"" + user.UserName + "\"";
|
||||
args << "\"" + tmpfile.fileName() + "\"";
|
||||
if(0 == General::RunCommand("personacrypt",args) ){
|
||||
//Success
|
||||
qDebug("Success; The data for this user has been merged onto the system and the system key has been disabled");
|
||||
}else{
|
||||
//Failure
|
||||
qWarning("Failure; The PersonaCrypt user data could not be merged onto the system. Invalid Password?" );
|
||||
}
|
||||
}
|
||||
|
||||
void UserManager::initPCDevice(User user, QString home, QString password)
|
||||
{
|
||||
//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; }
|
||||
|
||||
//Prompt for the user to select a device
|
||||
QStringList args;
|
||||
args << "list";
|
||||
args << "-r";
|
||||
QStringList devlist = General::RunCommand("personacrypt",args).split("\n");
|
||||
for(int i=0; i<devlist.length(); i++){
|
||||
//qDebug() << "Devlist:" << devlist[i];
|
||||
if(devlist[i].isEmpty() || devlist[i].startsWith("gpart:"))
|
||||
{
|
||||
devlist.removeAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if(devlist.isEmpty() || devlist.join("").simplified().isEmpty()){
|
||||
qWarning("No Devices Found; Please connect a removable device and try again");
|
||||
return;
|
||||
}
|
||||
|
||||
args.clear();
|
||||
args << "-h";
|
||||
args << user.HomeFolder;
|
||||
bool ok = false;
|
||||
QString space = General::RunCommand("df -h "+home).split("\n").filter(home).join("");
|
||||
space.replace("\t"," ");
|
||||
space = space.section(" ",2,2,QString::SectionSkipEmpty);
|
||||
|
||||
if(!ok || home.isEmpty()){ return; }
|
||||
|
||||
home = home.section(":",0,0); //only need the raw device
|
||||
//Save the password to a temporary file (for input to personacrypt)
|
||||
QTemporaryFile tmpfile("/tmp/.XXXXXXXXXXXXXXX");
|
||||
if(!tmpfile.open()){
|
||||
//Error: could not open a temporary file
|
||||
qWarning("Error; Could not create a temporary file for personacrypt");
|
||||
return;
|
||||
}
|
||||
QTextStream out(&tmpfile);
|
||||
out << password;
|
||||
tmpfile.close();
|
||||
//Now start the process of setting up the device
|
||||
bool success = false;
|
||||
args.clear();
|
||||
args << "init";
|
||||
args << "\""+user.UserName + "\"";
|
||||
args << "\"" + tmpfile.fileName() + "\"";
|
||||
args << home;
|
||||
QStringList output = General::RunCommand(success,"personacrypt",args).split("\n");
|
||||
if(success){
|
||||
//Success
|
||||
qDebug("Success; The PersonaCrypt device was successfully initialized");
|
||||
}else{
|
||||
//Failure - make sure the key was not created before the failure
|
||||
if(QFile::exists("/var/db/personacrypt/"+user.UserName+".key")){
|
||||
QFile::remove("/var/db/personacrypt/"+user.UserName+".key");
|
||||
}
|
||||
//Now show the error message with the log
|
||||
qWarning("Failure; The PersonaCrypt device could not be initialized");
|
||||
}
|
||||
}
|
||||
231
src/library/sysadm-usermanager.h
Normal file
231
src/library/sysadm-usermanager.h
Normal file
@@ -0,0 +1,231 @@
|
||||
//===========================================
|
||||
// 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 USERMANAGER_H
|
||||
#define USERMANAGER_H
|
||||
#include<QtCore>
|
||||
|
||||
namespace sysadm{
|
||||
class User
|
||||
{
|
||||
public:
|
||||
User()
|
||||
{
|
||||
FullName = "";
|
||||
UserName = "";
|
||||
ID = -1;
|
||||
HomeFolder = "";
|
||||
Shell = "";
|
||||
GroupID = -1;
|
||||
Encrypted = false;
|
||||
}
|
||||
|
||||
QString FullName;
|
||||
QString UserName;
|
||||
int ID;
|
||||
QString HomeFolder;
|
||||
QString Shell;
|
||||
int GroupID;
|
||||
bool Encrypted;
|
||||
friend bool operator<(const User lhs, const User rhs){
|
||||
return std::tie(lhs.ID,lhs.UserName) < std::tie(rhs.ID,rhs.UserName);
|
||||
}
|
||||
friend bool operator>(const User lhs, const User rhs)
|
||||
{ return rhs < lhs;}
|
||||
friend bool operator==(const User lhs, const User rhs)
|
||||
{
|
||||
return lhs.ID == rhs.ID && lhs.UserName == rhs.UserName;
|
||||
}
|
||||
friend bool operator !=(const User lhs, const User rhs)
|
||||
{ return !(lhs == rhs);}
|
||||
};
|
||||
class Group
|
||||
{
|
||||
public:
|
||||
Group()
|
||||
{
|
||||
ID = -1;
|
||||
Name = "";
|
||||
Members = QStringList();
|
||||
}
|
||||
|
||||
int ID;
|
||||
QString Name;
|
||||
//While the object model would be more "correct" if
|
||||
//Users were to be a Vector of User pointers, it's
|
||||
//expensive to wire up and we don't really gain anything
|
||||
//from doing so
|
||||
QStringList Members;
|
||||
};
|
||||
|
||||
class UserManager
|
||||
{
|
||||
public:
|
||||
UserManager(QString chroot = "");
|
||||
|
||||
//#section user actions
|
||||
/**
|
||||
* @brief NewUser Create a new user
|
||||
* @param fullName The full name of the user
|
||||
* @param userName The username of the user
|
||||
* @param password The user's password
|
||||
* @param home the location of the home directory
|
||||
* @param shell the user's shell, defaults to /bin/tcsh
|
||||
* @param uid the user id of the user
|
||||
* @param gid the group id of the user
|
||||
* @param encrypt whether to personaCrypt the User's home directory
|
||||
*/
|
||||
void NewUser(QString fullName, QString userName, QString password, QString home = "", QString shell = "/bin/tcsh", int uid = -1, int gid = -1, bool encrypt=false);
|
||||
/**
|
||||
* @brief DeleteUser Deletes a user
|
||||
* @param user the user to delete
|
||||
*/
|
||||
void DeleteUser(User user);
|
||||
|
||||
/**
|
||||
* @brief GetUsers getter for the users vector
|
||||
* @return a QVector<Users> that is a copy of the current state
|
||||
* do not modify it, instead call functions on this class to change
|
||||
* things and then get another copy of the vector
|
||||
*/
|
||||
const QVector<User> GetUsers();
|
||||
/**
|
||||
* @brief GetUser get a particular user by their UID
|
||||
* @param id the UID of the user to get
|
||||
* @return the user with the UID specified, if not found
|
||||
* returns a blank User
|
||||
*/
|
||||
const User GetUser(int id);
|
||||
|
||||
/**
|
||||
* @brief GetUser get a particular user by their UID
|
||||
* @param userName the username of the user to get
|
||||
* @return the user with the user name specified, if not found
|
||||
* returns a blank User
|
||||
*/
|
||||
const User GetUser(QString userName);
|
||||
|
||||
/**
|
||||
* @brief ChangeUserPassword changes the specified user's password
|
||||
* @param user the user to change the password of
|
||||
* @param newPassword the new password
|
||||
*/
|
||||
void ChangeUserPassword(User user, QString newPassword);
|
||||
/**
|
||||
* @brief ChangeUserShell change a specified user's shell
|
||||
* @param user the user to change the shell for
|
||||
* @param shell the shell to change to, note that if the shell
|
||||
* is not in the shells list then it does nothing
|
||||
*/
|
||||
void ChangeUserShell(User user, QString shell);
|
||||
/**
|
||||
* @brief ChangeUserFullName change the gecos field of a user to a new name
|
||||
* @param user the user to change the name of
|
||||
* @param newName the name to change to
|
||||
*/
|
||||
void ChangeUserFullName(User user, QString newName);
|
||||
//#endsection
|
||||
|
||||
//#section group actions
|
||||
/**
|
||||
* @brief AddUserToGroup add the specified user to the specified group
|
||||
* @param user the user to add to the group
|
||||
* @param group the group to add the user to
|
||||
*/
|
||||
void AddUserToGroup(User user, Group group);
|
||||
/**
|
||||
* @brief RemoveUserFromGroup removes the specified user from the specified group
|
||||
* @param user the user to remove from the group
|
||||
* @param group the group to remove the user from
|
||||
*/
|
||||
void RemoveUserFromGroup(User user, Group group);
|
||||
|
||||
/**
|
||||
* @brief NewGroup creates a new group
|
||||
* @param name the name of the new group
|
||||
* @param Users a list of users to add to the group
|
||||
*/
|
||||
void NewGroup(QString name, QStringList Users = QStringList());
|
||||
/**
|
||||
* @brief DeleteGroup delete a specified group
|
||||
* @param group the group to delete
|
||||
*/
|
||||
void DeleteGroup(Group group);
|
||||
|
||||
/**
|
||||
* @brief GetGroups get the internal list of groups
|
||||
* @return a QVector<Group> that is a copy of the current state
|
||||
* do not modify it, instead call functions on this class to change
|
||||
* things and then get another copy of the vector
|
||||
*/
|
||||
const QVector<Group> GetGroups();
|
||||
/**
|
||||
* @brief getGroup get a specified group by their gid
|
||||
* @param id the gid of the group to get
|
||||
* @return the group with the specified gid
|
||||
*/
|
||||
const Group getGroup(int id);
|
||||
/**
|
||||
* @brief getGroup get a specified group by their name
|
||||
* @param name the name of the group to get
|
||||
* @return the group with the specified name
|
||||
*/
|
||||
const Group getGroup(QString name);
|
||||
//#endsection
|
||||
|
||||
/**
|
||||
* @brief GetShells the list of shells that are currently installed on the system
|
||||
* @return a QStringList of shells on the system
|
||||
*/
|
||||
const QStringList GetShells();
|
||||
|
||||
/**
|
||||
* @brief initPCDevice Initiate PersonaCrypt for the user
|
||||
* @param user the user to initiate PersonaCrypt for
|
||||
* @param home the location of the home directory
|
||||
* @param password the password of the user
|
||||
*/
|
||||
void initPCDevice(User user, QString home, QString password);
|
||||
|
||||
/**
|
||||
* @brief importPCKey Import a PersonaCrypt Key
|
||||
* @param user the user to import the key for
|
||||
* @param filename the location of the key
|
||||
*/
|
||||
void importPCKey(User user, QString filename);
|
||||
/**
|
||||
* @brief exportPCKey Export a PersonaCrypt Key
|
||||
* @param user the user to export the key for
|
||||
* @param filename the file to export to
|
||||
*/
|
||||
void exportPCKey(User user, QString filename);
|
||||
/**
|
||||
* @brief disablePCKey Disables a PersonaCrypt key
|
||||
* @param user the user to diable PersonaCrypt for
|
||||
*/
|
||||
void disablePCKey(User user);
|
||||
/**
|
||||
* @brief disableAndCopyPCKey Disable a PersonaCrypt Key
|
||||
* @param user for this user
|
||||
* @param password password for the PersonaCrypt
|
||||
*/
|
||||
void disableAndCopyPCKey(User user, QString password);
|
||||
private:
|
||||
QVector<User> users;
|
||||
QVector<Group> groups;
|
||||
QStringList shells;
|
||||
QString chroot;
|
||||
|
||||
//loads the users from /etc/passwd
|
||||
void loadUsers();
|
||||
//load the groups from /etc/group
|
||||
void loadGroups();
|
||||
//load the shells from /etc/shells
|
||||
void loadShells();
|
||||
};
|
||||
}
|
||||
#endif // USERMANAGER_H
|
||||
@@ -189,14 +189,26 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmLifePreserverRequest(const Q
|
||||
ok = true;
|
||||
out->insert("cronsnap", sysadm::LifePreserver::scheduleSnapshot(in_args.toObject()));
|
||||
}
|
||||
if(act=="initreplication"){
|
||||
ok = true;
|
||||
out->insert("initreplication", sysadm::LifePreserver::initReplication(in_args.toObject()));
|
||||
}
|
||||
if(act=="listcron"){
|
||||
ok = true;
|
||||
out->insert("listcron", sysadm::LifePreserver::listCron());
|
||||
}
|
||||
if(act=="listreplication"){
|
||||
ok = true;
|
||||
out->insert("listreplication", sysadm::LifePreserver::listReplication());
|
||||
}
|
||||
if(act=="listsnap"){
|
||||
ok = true;
|
||||
out->insert("listsnap", sysadm::LifePreserver::listSnap(in_args.toObject()));
|
||||
}
|
||||
if(act=="removereplication"){
|
||||
ok = true;
|
||||
out->insert("removereplication", sysadm::LifePreserver::removeReplication(in_args.toObject()));
|
||||
}
|
||||
if(act=="removesnap"){
|
||||
ok = true;
|
||||
out->insert("removesnap", sysadm::LifePreserver::removeSnapshot(in_args.toObject()));
|
||||
@@ -205,6 +217,10 @@ RestOutputStruct::ExitCode WebSocket::EvaluateSysadmLifePreserverRequest(const Q
|
||||
ok = true;
|
||||
out->insert("revertsnap", sysadm::LifePreserver::revertSnapshot(in_args.toObject()));
|
||||
}
|
||||
if(act=="runreplication"){
|
||||
ok = true;
|
||||
out->insert("runreplication", sysadm::LifePreserver::runReplication(in_args.toObject()));
|
||||
}
|
||||
if(act=="savesettings"){
|
||||
ok = true;
|
||||
out->insert("savesettings", sysadm::LifePreserver::saveSettings(in_args.toObject()));
|
||||
|
||||
@@ -70,17 +70,21 @@ echo ""
|
||||
# Source our resty functions
|
||||
. ./utils/resty -W "https://127.0.0.1:12151" -H "Accept: application/json" -H "Content-Type: application/json" -u ${fuser}:${fpass}
|
||||
|
||||
# Check the reply of this REST query
|
||||
echo ""
|
||||
echo "REST Request:"
|
||||
echo "-------------------------------"
|
||||
echo "PUT /${namesp}/${name}"
|
||||
echo "${payload}" | perl -0007 -MJSON -ne'print to_json(from_json($_, {allow_nonref=>1}),{pretty=>1})."\n"'
|
||||
# Save output to a file in addition to stdout
|
||||
ofile="/tmp/api-response"
|
||||
echo "" > /tmp/api-response
|
||||
|
||||
echo ""
|
||||
echo "REST Response:"
|
||||
echo "-------------------------------"
|
||||
PUT /${namesp}/${name} "${payload}" -v -k 2>/tmp/.rstErr
|
||||
# Check the reply of this REST query
|
||||
echo "" | tee -a $ofile
|
||||
echo "REST Request:" | tee -a $ofile
|
||||
echo "-------------------------------" | tee -a $ofile
|
||||
echo "PUT /${namesp}/${name}" | tee -a $ofile
|
||||
echo "${payload}" | perl -0007 -MJSON -ne'print to_json(from_json($_, {allow_nonref=>1}),{pretty=>1})."\n"' | tee -a $ofile
|
||||
|
||||
echo "" | tee -a $ofile
|
||||
echo "REST Response:" | tee -a $ofile
|
||||
echo "-------------------------------" | tee -a $ofile
|
||||
PUT /${namesp}/${name} "${payload}" -v -k 2>/tmp/.rstErr | tee -a $ofile
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed.. Error output:"
|
||||
cat /tmp/.rstErr
|
||||
@@ -90,12 +94,12 @@ fi
|
||||
# Now check the response via WebSockets
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
|
||||
echo ""
|
||||
echo "WebSocket Request:"
|
||||
echo "-------------------------------"
|
||||
echo "{ \"namespace\":\"${namesp}\", \"name\":\"${name}\", \"id\":\"fooid\", \"args\":${payload} }" | perl -0007 -MJSON -ne'print to_json(from_json($_, {allow_nonref=>1}),{pretty=>1})."\n"'
|
||||
echo "" | tee -a $ofile
|
||||
echo "WebSocket Request:" | tee -a $ofile
|
||||
echo "-------------------------------" | tee -a $ofile
|
||||
echo "{ \"namespace\":\"${namesp}\", \"name\":\"${name}\", \"id\":\"fooid\", \"args\":${payload} }" | perl -0007 -MJSON -ne'print to_json(from_json($_, {allow_nonref=>1}),{pretty=>1})."\n"' | tee -a $ofile
|
||||
|
||||
echo ""
|
||||
echo "WebSocket Response:"
|
||||
echo "-------------------------------"
|
||||
echo "{ \"namespace\":\"${namesp}\", \"name\":\"${name}\", \"id\":\"fooid\", \"args\":${payload} }" | node sendwebsocket.js "$fuser" "$fpass"
|
||||
echo "" | tee -a $ofile
|
||||
echo "WebSocket Response:" | tee -a $ofile
|
||||
echo "-------------------------------" | tee -a $ofile
|
||||
echo "{ \"namespace\":\"${namesp}\", \"name\":\"${name}\", \"id\":\"fooid\", \"args\":${payload} }" | node sendwebsocket.js "$fuser" "$fpass" | tee -a $ofile
|
||||
|
||||
Reference in New Issue
Block a user