Compare commits

..

No commits in common. "master" and "branch-v1" have entirely different histories.

55 changed files with 382 additions and 1691 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
*.sh
*.tar.*
*.rpm

View File

@ -1,15 +0,0 @@
#!/usr/bin/perl -w
use esmith::Build::CreateLinks qw(:all);
use File::Basename;
use File::Path;
$event = "doraemon-reboot";
event_actions($event, 'doraemon-reboot' => 50);
$event = "doraemon-shutdown";
event_actions($event, 'doraemon-shutdown' => 50);
$event = "doraemon-wake";
event_actions($event, 'doraemon-wake' => 50);

1
defaults/access Normal file
View File

@ -0,0 +1 @@
private

16
doraemon.ini Normal file
View File

@ -0,0 +1,16 @@
[Daemon]
Database = /var/lib/doraemon/doraemon.db
LogFile = /var/log/doraemon.log
PIDFile = /var/run/doraemon.pid
BindAddress = 0.0.0.0
Port = 3000
[NameSettings]
Base = lab
Role = client
Digits = 2
[Files]
Domain = /etc/domain.yml
MgmtKey = /home/amgmt/.ssh/id_rsa.pub
VaultPassFile = /home/amgmt/.ansible/vault.txt

6
doraemon.logrotate Normal file
View File

@ -0,0 +1,6 @@
/var/log/doraemon.log {
daily
rotate 15
compress
missingok
}

234
doraemon.py Normal file
View File

@ -0,0 +1,234 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Helps a client to join domain and maintain itself."""
__author__ = 'BgLUG'
__email__ = 'info@bglug.it'
from contextlib import contextmanager, closing
from subprocess import Popen, PIPE
from sqlite3 import connect
from bottle import Bottle, request
from json import dumps
from ConfigParser import ConfigParser
from Crypto.Hash import MD5
from Crypto.Cipher import AES
import base64
import json
import sys
import re
import os
class MyApp:
def __init__(self, configfile = '/etc/mac2hostname.ini'):
if not os.path.isfile(configfile):
print "Cannot find main file, %s. Exiting." % configfile
sys.exit(1)
# Loads configurations
config = ConfigParser()
config.read(configfile)
# Populates private properties with configuration
self.__dbfile = config.get('Daemon', 'Database') if config.has_option('Daemon', 'Database') else '/var/lib/doraemon/doraemon.db'
self.__logdir = config.get('Daemon', 'LogFile') if config.has_option('Daemon', 'LogFile') else '/var/log/doraemon.log'
self.__pidfile = config.get('Daemon', 'PIDFile') if config.has_option('Daemon', 'PIDFile') else '/var/run/doraemon.pid'
self.__bindaddress = config.get('Daemon', 'BindAddress') if config.has_option('Daemon', 'BindAddress') else '127.0.0.1'
self.__port = config.getint('Daemon', 'Port') if config.has_option('Daemon',
'Port') else 8080
self.__defaultbase = config.get('NameSettings', 'Base') if config.has_option('NameSettings', 'Base') else 'client'
self.__defaultrole = config.get('NameSettings', 'Role') if config.has_option('NameSettings', 'Role') else 'client'
self.__namedigits = config.get('NameSettings', 'Digits') if config.has_option('NameSettings', 'Digits') else '2'
self.__domainfile = config.get('Files', 'Domain') if config.has_option('Files', 'Domain') else None
self.__mgmtfile = config.get('Files', 'MgmtKey') if config.has_option('Files', 'MgmtKey') else None
self.__vaultpassfile = config.get('Files', 'VaultPassFile') if config.has_option('Files', 'VaultPassFile') else None
self.__app = Bottle()
# Applies routes
self.__route()
# Assures table creation
self.__init_tables()
@contextmanager
def __getcursor(self):
with connect(self.__dbfile) as connection:
with closing(connection.cursor()) as cursor:
yield cursor
# Private methods
def __route(self):
self.__app.get('/mac2hostname', callback=self.mac2hostname)
self.__app.get('/whatsmyhostname', callback=self.whatsmyhostname)
self.__app.get('/hosts', callback=self.hosts)
self.__app.get('/domain', callback=self.domain)
self.__app.get('/mgmtkey', callback=self.mgmtkey)
self.__app.get('/vaultpass', callback=self.vaultpass)
self.__app.get('/ansible_list', callback=self.ansible_list)
self.__app.get('/ansible_host', callback=self.ansible_host)
self.__app.get('/epoptes-srv', callback=self.epoptes_srv)
def __init_tables(self):
with self.__getcursor() as cursor:
cursor.execute('CREATE TABLE IF NOT EXISTS client (id INT PRIMARY KEY,'
'hostname TEXT NOT NULL UNIQUE, mac TEXT UNIQUE, role TEXT)')
cursor.execute('CREATE INDEX IF NOT EXISTS idxmac ON client(mac)')
def __getmac(self, ip):
Popen(['ping', '-c1', '-t2', ip], stdout=PIPE).communicate()
arp = Popen(['arp', '-n', ip], stdout=PIPE).communicate()[0]
return re.search(r'(([\da-fA-F]{1,2}\:){5}[\da-fA-F]{1,2})', arp).group(1).lower()
def __normalizemac(self, mac):
return ':'.join(x.zfill(2) for x in mac.split('_')).lower()
def __gethostname(self, mac, base = None, role = None):
if base == None:
base = self.__defaultbase
if role == None:
role = self.__defaultrole
with self.__getcursor() as cursor:
(newid,) = cursor.execute('SELECT COALESCE(MAX(id) + 1, 1) FROM client').fetchone()
# Constucts the hostname format
formatstring = '%s-%0' + self.__namedigits + 'd'
data = (newid, formatstring % (base, newid), mac, role)
# Since MAC is Unique, this fails with the same MAC address
cursor.execute('INSERT OR IGNORE INTO client VALUES (?, ?, ?, ?)', data)
(hostname,) = cursor.execute('SELECT hostname FROM client WHERE mac = "%s"' % mac).fetchone()
return hostname
# This should be used only after registration of the client
# (whatsmyhostname)
def __getrole(self):
macaddress = self.__normalizemac(self.__getmac(request['REMOTE_ADDR']))
hostname = self.__gethostname(macaddress)
with self.__getcursor() as cursor:
(role,) = cursor.execute("SELECT role FROM client WHERE hostname = '%s' AND mac = '%s'" % (hostname, macaddress)).fetchone()
return role
# Variables returned to the client
def __rolevars(self, role=None):
retval = {'role': role, 'addpkg': [], 'delpkg': []}
try:
db = Popen(['/sbin/e-smith/db', 'roles', 'getjson'], stdout=PIPE).communicate()[0]
for data in json.loads(db):
if data['name'] == role:
retval['addpkg'] = re.split(r'[\s,;]+', data['props']['Addpkg'])
retval['delpkg'] = re.split(r'[\s,;]+', data['props']['Delpkg'])
continue
except: pass
return retval
# Instance methods AKA routes
def mac2hostname(self):
# Ensure required parameters have been passed
if not request.query.mac:
return "Usage: GET /mac2hostname?mac=XX_XX_XX_XX_XX_XX[&base=YYY][&role=ZZZ]"
# Sets up variables for possible parameters
mac = self.__normalizemac(request.query.mac)
base = request.query.base or self.__defaultbase
role = request.query.role or self.__defaultrole
return self.__gethostname(mac, base, role)
def whatsmyhostname(self):
# No required parameters
ip = request.query.ip or request['REMOTE_ADDR']
base = request.query.base or self.__defaultbase
role = request.query.role or self.__defaultrole
return self.__gethostname(self.__getmac(ip), base, role)
def hosts(self):
# Default where clause: no where specifications
where = ''
# If a role parameter is passed, list the hosts for that role
if request.query.role:
where = "WHERE role = '%s'" % request.query.role
with self.__getcursor() as cursor:
return dumps([dict((meta[0], data)
for meta, data in zip(cursor.description, row))
for row in cursor.execute('SELECT role, hostname, mac FROM client '
+ where + 'ORDER BY role ASC, hostname ASC')], indent=4)
def domain(self):
with open(self.__domainfile, 'r') as f:
return f.read()
def mgmtkey(self):
with open(self.__mgmtfile, 'r') as f:
return f.read()
def vaultpass(self):
with open(self.__vaultpassfile, 'r') as f:
ip = request.query.ip or request['REMOTE_ADDR']
base = request.query.base or self.__defaultbase
role = request.query.role or self.__defaultrole
hostname = self.__gethostname(self.__getmac(ip), base, role)
# Encryption key
key = MD5.new(hostname).digest()
# Secret
secret = f.read().strip()
# Creating a secret that is multiple of 16 in length
i = 16 - (len(secret) % 16)
lengthy_secret = secret + i * 'x'
cipher = AES.new(key, AES.MODE_ECB)
crypted = cipher.encrypt(lengthy_secret)
# Encode in base64 because of unicode strings
return base64.b64encode(crypted)
def ansible_list(self):
# Faking calls
if request.query.role:
role = request.query.role
else:
role = self.__getrole()
result = {
'localhost': {
'hosts': [ 'localhost' ],
'vars': {
'ansible_connection': 'local'
}
},
'_meta': {
'hostvars': {
'localhost': self.__rolevars(role)
}
}
}
return dumps(result)
def ansible_host(self):
# Ignoring 'host' parameter, since it should always be 'localhost'.
# Instead, for testing purposes, if asked for a role, use it
if request.query.role:
role = request.query.role
else:
role = self.__getrole()
return dumps(self.__rolevars(role))
def epoptes_srv(self):
hostname = None
try:
with self.__getcursor() as cursor:
(hostname,) = cursor.execute('SELECT hostname FROM client WHERE role = \'docenti\' ORDER BY id DESC LIMIT 1').fetchone()
except TypeError:
return 'none'
return hostname
def start(self):
# Opens up a PID file
pid = os.getpid()
pidfile = open(self.__pidfile, 'w')
pidfile.write('%s' % pid)
pidfile.close()
# Runs Bottle, at last.
self.__app.run(host=self.__bindaddress, port=self.__port)
# Main body
if __name__ == '__main__':
cfgfile = '/etc/doraemon.ini'
if len(sys.argv) > 1:
cfgfile = sys.argv[1]
app = MyApp(cfgfile)
app.start()

View File

@ -1,165 +1,85 @@
Summary: Helps client to join domain and maintain itself
Name: doraemon
Version: 2.0.4
Release: 1.ns6
Version: 1.2.1
Release: 2.ns6
URL: https://github.com/bglug-it/doraemon/
License: GPLv2+
Packager: Paolo Asperti <paolo@asperti.com>
Group: System Environment/Daemons
BuildRoot: %{_tmppath}/%{name}-root
BuildRequires: nethserver-devtools > 1.0.1
BuildRequires: hunspell-en
Requires: httpd, php, sudo, php-xml, php-mcrypt
Requires: nethserver-base, nethserver-php
Requires: net-tools
Requires: upstart
Source0: %{name}-%{version}.tar.gz
Requires: python python-bottle python-crypto2.6 nethserver-base
Requires(post): chkconfig nethserver-base
Requires(preun): chkconfig initscripts nethserver-base
Source0: doraemon-1.2.1.tar.gz
BuildArch: noarch
%description
Helps client on a domain network to get information for its maintenance.
%prep
%setup
%setup -q
%build
%{makedocs}
perl createlinks.pl
%install
rm -Rf $RPM_BUILD_ROOT
(cd root ; find . -depth -print | cpio -dump %{buildroot})
rm -f %{name}-%{version}-%{release}-filelist
%{genfilelist} %{buildroot} > %{name}-%{version}-%{release}-filelist
mkdir -p %{buildroot}%{_localstatedir}/log/doraemon
mkdir -p %{buildroot}%{_localstatedir}/cache/doraemon
%pre
#if [ "$1" = 1 ]; then
# #installation
#fi
if [ "$1" = 2 ]; then
#upgrade
/sbin/service %{name} stop >/dev/null 2>&1 || :
/sbin/stop %{name} >/dev/null 2>&1 || :
DATABASE=/var/lib/doraemon/doraemon.db
INI=/etc/doraemon.ini
if [ -f $INI ]; then
DATABASE=$(awk -F "=" '/Database/ {print $2}' $INI)
PORT=$(awk -F "=" '/Port/ {print $2}' $INI)
BASE=$(awk -F "=" '/Base/ {print $2}' $INI)
ROLE=$(awk -F "=" '/Role/ {print $2}' $INI)
DIGITS=$(awk -F "=" '/Digits/ {print $2}' $INI)
DOMAIN=$(awk -F "=" '/Domain/ {print $2}' $INI)
MGMTKEY=$(awk -F "=" '/MgmtKey/ {print $2}' $INI)
VAULTPASSFILE=$(awk -F "=" '/VaultPassFile/ {print $2}' $INI)
rm $INI
/sbin/e-smith/db configuration set %{name} service \
status enabled \
TCPPort $PORT \
access private \
DefaultRole $ROLE \
DomainFile $DOMAIN \
ManagementKeyFile $MGMTKEY \
NamingBase $BASE \
NamingDigits $DIGITS \
VaultPassFile $VAULTPASSFILE
else
/sbin/e-smith/db configuration set %{name} service \
status enabled \
TCPPort 3000 \
access private \
DefaultRole client \
DomainFile /etc/domain.yml \
ManagementKeyFile /home/amgmt/.ssh/id_rsa.pub \
NamingBase lab \
NamingDigits 2 \
VaultPassFile /home/amgmt/.ansible/vault.txt
fi
if [ -f $DATABASE ]; then
sqlite3 -separator " " $DATABASE \
"select * from client" | while read id hostname mac role; do
/sbin/e-smith/db hosts set $hostname local \
MacAddress $mac Role $role
done
rm $DATABASE
fi
fi
rm -rf %{buildroot}
install -d %{buildroot}
install -d -m 755 %{buildroot}%{_sysconfdir}
install -m 644 doraemon.ini %{buildroot}%{_sysconfdir}/%{name}.ini
# Installo il servizio
install -d -m 755 %{buildroot}%{_initrddir}
install -m 755 initrd %{buildroot}%{_initrddir}/%{name}
# Installo lo script vero e proprio
install -d -m 755 %{buildroot}%{_bindir}
install -m 755 doraemon.py %{buildroot}%{_bindir}/%{name}.py
# Cartella del database
install -d %{buildroot}%{_sharedstatedir}/%{name}
# File per il rotate
install -d %{buildroot}%{_sysconfdir}/logrotate.d
install -m 644 doraemon.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/%{name}
# File di Nethserver
install -d %{buildroot}%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}
install -m 644 type %{buildroot}%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/type
install -m 644 status %{buildroot}%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/status
install -m 644 access %{buildroot}%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/access
install -m 644 TCPPort %{buildroot}%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/TCPPort
%post
/etc/e-smith/events/actions/initialize-default-databases
[ -e /usr/sbin/doraemon ] && [ ! -L /usr/sbin/doraemon ] && rm /usr/sbin/doraemon
[ ! -L /usr/sbin/doraemon ] && ln -s httpd /usr/sbin/doraemon
if [ "$1" = 1 ]; then
#installation
/sbin/e-smith/db configuration set %{name} service status enabled TCPPort 3000 access private DefaultRole client DomainFile /etc/domain.yml ManagementKeyFile /home/amgmt/.ssh/id_rsa.pub NamingBase lab NamingDigits 2 VaultPassFile /home/amgmt/.ansible/vault.txt
/sbin/e-smith/expand-template /etc/sudoers
/sbin/e-smith/expand-template /etc/httpd/doraemon/httpd.conf
/sbin/start %{name} >/dev/null 2>&1 || :
/sbin/chkconfig --add %{name}
/sbin/service %{name} start >/dev/null 2>&1
/sbin/e-smith/db configuration set %{name} service status enabled TCPPort 3000 access private
/sbin/e-smith/signal-event runlevel-adjust
/sbin/e-smith/signal-event firewall-adjust
fi
if [ "$1" = 2 ]; then
#upgrade
# TODO: this won't be needed in the next version
/sbin/e-smith/expand-template /etc/sudoers
/sbin/e-smith/expand-template /etc/httpd/doraemon/httpd.conf
/sbin/start %{name} >/dev/null 2>&1 || :
/sbin/e-smith/signal-event runlevel-adjust
/sbin/e-smith/signal-event firewall-adjust
fi
%preun
#if [ "$1" = 1 ]; then
# #upgrade
#fi
if [ "$1" = 0 ]; then
#uninstallation
/sbin/stop %{name} >/dev/null 2>&1 || :
fi
%postun
#if [ "$1" = 1 ]; then
# #upgrade
#fi
if [ "$1" = 0 ]; then
#uninstallation
/sbin/e-smith/db configuration delete %{name}
/sbin/e-smith/expand-template /etc/sudoers
/sbin/service %{name} stop >/dev/null 2>&1
chkconfig --del %{name}
/sbin/e-smith/signal-event runlevel-adjust
/sbin/e-smith/signal-event firewall-adjust
fi
%clean
rm -rf %{buildroot}
%files -f %{name}-%{version}-%{release}-filelist
%defattr(0644,root,root,755)
%doc README.md LICENSE.txt
%attr(0644,srvmgr,srvmgr) %ghost %{_localstatedir}/log/doraemon/access_log
%attr(0644,srvmgr,srvmgr) %ghost %{_localstatedir}/log/doraemon/error_log
%files
%defattr(644,root,root,755)
%doc README.md LICENSE
%config(noreplace) %{_sysconfdir}/%{name}.ini
%attr(755,-,-) %{_initrddir}/%{name}
%attr(755,-,-) %{_bindir}/%{name}.py
%dir %attr(755,-,-) %{_sharedstatedir}/%{name}
%{_sysconfdir}/logrotate.d/%{name}
%dir %attr(755,-,-) %{_sysconfdir}/e-smith/db/configuration/defaults/%{name}
%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/status
%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/type
%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/access
%{_sysconfdir}/e-smith/db/configuration/defaults/%{name}/TCPPort
%changelog
* Sun Jun 17 2018 Emiliano Vavassori <syntaxerrormmm-AT-gmail.com> - 2.0.4-1
- FIX: Doraemon doesn't start after installation
* Sun Jun 17 2018 Emiliano Vavassori <syntaxerrormmm-AT-gmail.com> - 2.0.3-1
- FIX: whatsmyhostname correctly parse role variable
- Minor aesthetic syntax fixes
* Sun Oct 8 2017 Paolo Asperti <paolo@asperti.com> - 2.0.2-1
- FIX: bug in WOL
- FIX: small language typos
* Wed Nov 9 2016 Paolo Asperti <paolo@asperti.com> - 2.0.1-1
- PHP porting
- Added a basic UI
* Tue Oct 27 2015 Emiliano Vavassori <syntaxerrormmm-AT-gmail.com> - 1.2.1-2.ns6
- Packing corrections to rpm to fix upgrading issue

79
initrd Normal file
View File

@ -0,0 +1,79 @@
#!/bin/sh
# chkconfig: 2345 20 80
# description: Manage assignation of hostnames for clients
# pidfile: /var/run/doraemon.pid
# config: /etc/doraemon.ini
### BEGIN INIT INFO
# Provides: doraemon
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Helps client to join the domain and maintain itself
# Description: Helps client to join the domain and maintain itself
### END INIT INFO
# Change the next 3 lines to suit where you install your script and what you want to call it
DAEMON=/usr/bin/doraemon.py
DAEMON_NAME=doraemon
# Add any command line options for your daemon here
DAEMON_OPTS=""
# This next line determines what user the script runs as.
# Root generally not recommended but necessary if you are using the Raspberry Pi GPIO from Python.
#DAEMON_USER=mac2hostname
#DAEMON_GROUP=mac2hostname
# The process ID of the script when it runs is stored here:
LOGFILE=/var/log/$DAEMON_NAME.log
PIDFILE=/var/run/$DAEMON_NAME.pid
lockfile=/var/lock/subsys/$DAEMON_NAME
. /etc/rc.d/init.d/functions
. /etc/sysconfig/network
[ "$NETWORKING" = "no" ] && exit 0
do_start () {
echo -n $"Starting $DAEMON_NAME: "
daemon --pidfile=$PIDFILE "python $DAEMON >>$LOGFILE 2>/dev/null &"
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch $lockfile
return $RETVAL
}
do_stop () {
echo -n $"Stopping $DAEMON_NAME: "
killproc -p $PIDFILE python
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && rm -f $lockfile
return $RETVAL
}
case "$1" in
start|stop)
do_${1}
;;
restart|reload|force-reload)
do_stop
do_start
;;
status)
status -p $PIDFILE python
;;
*)
echo "Usage: /etc/init.d/$DAEMON_NAME {start|stop|restart|status}"
exit 1
;;
esac
exit 0

View File

@ -1 +0,0 @@
/etc/domain.yml

View File

@ -1 +0,0 @@
/home/amgmt/.ssh/id_rsa.pub

View File

@ -1 +0,0 @@
/home/amgmt/.ansible/vault.txt

View File

@ -1,34 +0,0 @@
#!/usr/bin/perl -w
package esmith;
use strict;
use Errno;
use esmith::DB::db;
use IO::File;
use English;
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
my $event = $ARGV[0];
defined $ARGV[1] || die "No host specified";
my $host = $ARGV[1];
reboot_host($host);
exit 0;
sub reboot_host
{
my ($hostName) = @_;
#------------------------------------------------------------
# Reboot the host
#------------------------------------------------------------
my $h = $db_hosts->get($hostName) or die "No host record for $hostName";
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no amgmt\@$hostName /usr/bin/sudo /sbin/reboot") == 0
or die "Error rebooting $hostName";
}

View File

@ -1,20 +0,0 @@
#!/usr/bin/perl -w
package esmith;
use strict;
use Errno;
use esmith::DB::db;
use IO::File;
use English;
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
my @items = $db_hosts->get_all('local');
foreach my $item (@items) {
my $hostName = $item->key;
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no amgmt\@$hostName /usr/bin/sudo /sbin/reboot &") == 0
or die "Error rebooting $hostName";
}
exit 0;

View File

@ -1,34 +0,0 @@
#!/usr/bin/perl -w
package esmith;
use strict;
use Errno;
use esmith::DB::db;
use IO::File;
use English;
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
my $event = $ARGV[0];
defined $ARGV[1] || die "No host specified";
my $host = $ARGV[1];
shutdown_host($host);
exit 0;
sub shutdown_host
{
my ($hostName) = @_;
#------------------------------------------------------------
# Shutdown the host
#------------------------------------------------------------
my $h = $db_hosts->get($hostName) or die "No host record for $hostName";
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no amgmt\@$hostName /usr/bin/sudo /sbin/poweroff") == 0
or die "Error shutting down $hostName";
}

View File

@ -1,20 +0,0 @@
#!/usr/bin/perl -w
package esmith;
use strict;
use Errno;
use esmith::DB::db;
use IO::File;
use English;
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
my @items = $db_hosts->get_all('local');
foreach my $item (@items) {
my $hostName = $item->key;
system("/usr/bin/sudo -u amgmt /usr/bin/ssh -o StrictHostKeyChecking=no amgmt\@$hostName /usr/bin/sudo /sbin/poweroff &") == 0
or die "Error shutting down $hostName";
}
exit 0;

View File

@ -1,35 +0,0 @@
#!/usr/bin/perl -w
package esmith;
use strict;
use Errno;
use esmith::DB::db;
use IO::File;
use English;
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
my $event = $ARGV[0];
defined $ARGV[1] || die "No host specified";
my $host = $ARGV[1];
wake_host($host);
exit 0;
sub wake_host
{
my ($hostName) = @_;
#------------------------------------------------------------
# Wake up the host
#------------------------------------------------------------
my @h = $db_hosts->get($hostName) or die "No host record for $hostName";
my $client = $h[0];
my $mac = $client->prop('MacAddress');
system("/sbin/ether-wake -b -i lan0 $mac") == 0
or die "Error waking up $hostName";
}

View File

@ -1,21 +0,0 @@
#!/usr/bin/perl -w
package esmith;
use strict;
use Errno;
use esmith::DB::db;
use IO::File;
use English;
my $db_hosts = esmith::DB::db->open('hosts') || die("Could not open e-smith db (" . esmith::DB::db->error . ")\n");
my @items = $db_hosts->get_all('local');
foreach my $item (@items) {
my $hostName = $item->key;
my $mac = $item->prop('MacAddress');
system("/sbin/ether-wake -b -i lan0 $mac &") == 0
or die "Error waking up $hostName";
}
exit 0;

View File

@ -1 +0,0 @@
../actions/doraemon-reboot-all

View File

@ -1 +0,0 @@
../actions/doraemon-reboot

View File

@ -1 +0,0 @@
../actions/doraemon-shutdown-all

View File

@ -1 +0,0 @@
../actions/doraemon-shutdown

View File

@ -1 +0,0 @@
../actions/doraemon-wake-all

View File

@ -1 +0,0 @@
../actions/doraemon-wake

View File

@ -1,3 +0,0 @@
TEMPLATE_PATH='upstart-job'
OUTPUT_FILENAME='/etc/init/doraemon.conf'
MORE_DATA={ name => 'doraemon', description => 'Apache instance that runs the doraemon service', author => 'Paolo Asperti <paolo@asperti.com>', daemon_bin => '/usr/sbin/doraemon', daemon_args => '-f /etc/httpd/doraemon/httpd.conf', respawn => 1, 'expect' => 'fork', stop_on => 'stopping network', start_on => 'started network' }

View File

@ -1,10 +0,0 @@
{
#
# 00template_defaults
#
$localAccess = '';
$port = ${'doraemon'}{TCPPort} || "3000";
"";
}

View File

@ -1,127 +0,0 @@
{
$OUT .= <<HERE;
Listen $port
HostnameLookups off
ServerAdmin root@$DomainName
ServerRoot /etc/httpd
ServerTokens ProductOnly
User srvmgr
Group srvmgr
ErrorLog /var/log/doraemon/error_log
LogLevel warn
HERE
foreach (qw(
env
log_config
mime
negotiation
include
autoindex
dir
alias
rewrite
access
authz_host
expires
headers
setenvif
ssl
))
{
next unless -f "/usr/lib/httpd/modules/mod_${_}.so" ||
-f "/usr/lib64/httpd/modules/mod_${_}.so";
$OUT .= "LoadModule ${_}_module modules/mod_${_}.so\n";
}
$OUT .= <<HERE;
PidFile /var/run/doraemon.pid
ScoreBoardFile /var/run/doraemon.scoreboard
UseCanonicalName off
LogFormat "%h %l %u %t \\"%r\\" %>s %b" common
LogFormat "%{User-agent}i" agent
CustomLog /var/log/doraemon/access_log common
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 15
MaxClients 150
MaxRequestsPerChild 100
MinSpareServers 1
MaxSpareServers 5
StartServers 1
Timeout 300
DefaultIcon /icons/unknown.gif
DirectoryIndex index.htm index.html index.shtml index.cgi
IndexOptions FancyIndexing VersionSort NameWidth=*
IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
AccessFileName .htaccess
AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
AddIconByType (TXT,/icons/text.gif) text/*
AddIconByType (IMG,/icons/image2.gif) image/*
AddIconByType (SND,/icons/sound2.gif) audio/*
AddIconByType (VID,/icons/movie.gif) video/*
DefaultType text/plain
TypesConfig /etc/mime.types
AddEncoding x-compress Z
AddEncoding x-gzip gz
AddIcon /icons/binary.gif .bin .exe
AddIcon /icons/binhex.gif .hqx
AddIcon /icons/tar.gif .tar
AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
AddIcon /icons/a.gif .ps .ai .eps
AddIcon /icons/layout.gif .html .shtml .htm .pdf
AddIcon /icons/text.gif .txt
AddIcon /icons/c.gif .c
AddIcon /icons/p.gif .pl .py
AddIcon /icons/f.gif .for
AddIcon /icons/dvi.gif .dvi
AddIcon /icons/uuencoded.gif .uu
AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
AddIcon /icons/tex.gif .tex
AddIcon /icons/bomb.gif core
AddIcon /icons/back.gif ..
AddIcon /icons/hand.right.gif README
AddIcon /icons/folder.gif ^^DIRECTORY^^
AddIcon /icons/blank.gif ^^BLANKICON^^
AddLanguage en .en
AddLanguage fr .fr
AddLanguage de .de
AddLanguage da .da
AddLanguage el .el
AddLanguage it .it
LanguagePriority en it de fr
AddType text/html .shtml
AddType application/x-pkcs7-crl .crl
AddType application/x-x509-ca-cert .crt
BrowserMatch "Mozilla/2" nokeepalive
BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
BrowserMatch "RealPlayer 4\.0" force-response-1.0
BrowserMatch "Java/1\.0" force-response-1.0
BrowserMatch "JDK/1\.0" force-response-1.0
AddHandler cgi-script .cgi
AddHandler server-parsed .shtml
AddHandler imap-file map
HERE
}

View File

@ -1,16 +0,0 @@
# First, we configure the "default" to be a very restrictive set of
# permissions.
<Directory />
Options None
AllowOverride None
order deny,allow
deny from all
allow from none
</Directory>
<Directory /var/www/icons>
Allow from all
</Directory>

View File

@ -1,23 +0,0 @@
DocumentRoot /usr/share/doraemon
Alias /icons/ /var/www/icons/
<Directory /usr/share/doraemon/>
Options +Indexes
Order Deny,Allow
Deny from all
Allow from { $localAccess || 'all' }
AddType application/x-httpd-php .php
Options +FollowSymlinks
RewriteEngine on
RewriteBase /
RewriteRule ^(js/|fonts/|css/|images/|icons/|.+\.php) - [PT,L]
RewriteRule ^favicon.ico$ images/favicon.ico [L]
RewriteRule ^(.*)$ index.php/$1 [L]
php_value session.save_path /var/cache/doraemon
php_flag magic_quotes_gpc off
</Directory>

View File

@ -1 +0,0 @@
Include conf.d/php.conf

View File

@ -1,15 +0,0 @@
{
$OUT = 'srvmgr ALL=NOPASSWD: /bin/cat ';
use esmith::ConfigDB;
my $confdb = esmith::ConfigDB->open;
my $backup = $confdb->get('doraemon') or die "No doraemon db entry found\n";
my $VaultPassFile = $backup->prop('VaultPassFile') || '/home/amgmt/.ansible/vault.txt';
my $ManagementKeyFile = $backup->prop('ManagementKeyFile') || '/home/amgmt/.ssh/id_rsa.pub';
my $DomainFile = $backup->prop('DomainFile') || '/etc/domain.yml';
$OUT = 'srvmgr ALL=NOPASSWD: /bin/cat ' . $VaultPassFile . '
srvmgr ALL=NOPASSWD: /bin/cat ' . $ManagementKeyFile .'
srvmgr ALL=NOPASSWD: /bin/cat ' . $DomainFile;
}

View File

@ -1,162 +0,0 @@
# ================= DO NOT MODIFY THIS FILE =================
#
# Manual changes will be lost when this file is regenerated.
#
# Please read the developer's guide, which is available
# at https://dev.nethesis.it/projects/nethserver/wiki/NethServer
# original work from http://www.contribs.org/development/
#
# Copyright (C) 2013 Nethesis S.r.l.
# http://www.nethesis.it - support@nethesis.it
#
Listen 3000
HostnameLookups off
ServerAdmin root
ServerRoot /etc/httpd
ServerTokens ProductOnly
User srvmgr
Group srvmgr
ErrorLog /var/log/doraemon/error_log
LogLevel warn
LoadModule env_module modules/mod_env.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module modules/mod_mime.so
LoadModule negotiation_module modules/mod_negotiation.so
LoadModule include_module modules/mod_include.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule ssl_module modules/mod_ssl.so
PidFile /var/run/doraemon.pid
ScoreBoardFile /var/run/doraemon.scoreboard
UseCanonicalName off
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{User-agent}i" agent
CustomLog /var/log/doraemon/access_log common
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 15
MaxClients 150
MaxRequestsPerChild 100
MinSpareServers 1
MaxSpareServers 5
StartServers 1
Timeout 300
DefaultIcon /icons/unknown.gif
DirectoryIndex index.htm index.html index.shtml index.cgi
IndexOptions FancyIndexing VersionSort NameWidth=*
IndexIgnore .??* *~ *# HEADER* README* RCS CVS *,v *,t
AccessFileName .htaccess
AddIconByEncoding (CMP,/icons/compressed.gif) x-compress x-gzip
AddIconByType (TXT,/icons/text.gif) text/*
AddIconByType (IMG,/icons/image2.gif) image/*
AddIconByType (SND,/icons/sound2.gif) audio/*
AddIconByType (VID,/icons/movie.gif) video/*
DefaultType text/plain
TypesConfig /etc/mime.types
AddEncoding x-compress Z
AddEncoding x-gzip gz
AddIcon /icons/binary.gif .bin .exe
AddIcon /icons/binhex.gif .hqx
AddIcon /icons/tar.gif .tar
AddIcon /icons/world2.gif .wrl .wrl.gz .vrml .vrm .iv
AddIcon /icons/compressed.gif .Z .z .tgz .gz .zip
AddIcon /icons/a.gif .ps .ai .eps
AddIcon /icons/layout.gif .html .shtml .htm .pdf
AddIcon /icons/text.gif .txt
AddIcon /icons/c.gif .c
AddIcon /icons/p.gif .pl .py
AddIcon /icons/f.gif .for
AddIcon /icons/dvi.gif .dvi
AddIcon /icons/uuencoded.gif .uu
AddIcon /icons/script.gif .conf .sh .shar .csh .ksh .tcl
AddIcon /icons/tex.gif .tex
AddIcon /icons/bomb.gif core
AddIcon /icons/back.gif ..
AddIcon /icons/hand.right.gif README
AddIcon /icons/folder.gif ^^DIRECTORY^^
AddIcon /icons/blank.gif ^^BLANKICON^^
AddLanguage en .en
AddLanguage fr .fr
AddLanguage de .de
AddLanguage da .da
AddLanguage el .el
AddLanguage it .it
LanguagePriority en it de fr
AddType text/html .shtml
AddType application/x-pkcs7-crl .crl
AddType application/x-x509-ca-cert .crt
BrowserMatch "Mozilla/2" nokeepalive
BrowserMatch "MSIE 4.0b2;" nokeepalive downgrade-1.0 force-response-1.0
BrowserMatch "RealPlayer 4.0" force-response-1.0
BrowserMatch "Java/1.0" force-response-1.0
BrowserMatch "JDK/1.0" force-response-1.0
AddHandler cgi-script .cgi
AddHandler server-parsed .shtml
AddHandler imap-file map
# First, we configure the "default" to be a very restrictive set of
# permissions.
<Directory />
Options None
AllowOverride None
order deny,allow
deny from all
allow from none
</Directory>
<Directory /var/www/icons>
Allow from all
</Directory>
DocumentRoot /usr/share/doraemon
Alias /icons/ /var/www/icons/
<Directory /usr/share/doraemon/>
Options +Indexes
Order Deny,Allow
Deny from all
Allow from all
AddType application/x-httpd-php .php
Options +FollowSymlinks
RewriteEngine on
RewriteBase /
RewriteRule ^(js/|fonts/|css/|images/|icons/|.+\.php) - [PT,L]
RewriteRule ^favicon.ico$ images/favicon.ico [L]
RewriteRule ^(.*)$ index.php/$1 [L]
php_value session.save_path /var/cache/doraemon
php_flag magic_quotes_gpc off
</Directory>
Include conf.d/php.conf

View File

@ -1,12 +0,0 @@
#
# doraemon-reload task
#
# Signals an Apache graceful restart for doraemon instance
#
# service job, runs asynchronously
script
sleep 5
exec /usr/sbin/doraemon -f /etc/httpd/doraemon/httpd.conf -k graceful
end script

View File

@ -1,42 +0,0 @@
# ================= DO NOT MODIFY THIS FILE =================
#
# Manual changes will be lost when this file is regenerated.
#
# Please read the developer's guide, which is available
# at https://dev.nethesis.it/projects/nethserver/wiki/NethServer
# original work from http://www.contribs.org/development/
#
# Copyright (C) 2013 Nethesis S.r.l.
# http://www.nethesis.it - support@nethesis.it
#
#
# 10base doraemon - Apache instance that runs the doraemon service
#
description "Apache instance that runs the doraemon service"
author "Paolo Asperti <paolo@asperti.com>"
respawn
respawn limit 10 5
expect fork
start on started network
stop on stopping network
#
# 20pre_start
#
pre-start script
STAT=`/sbin/e-smith/config getprop doraemon status`
if test "x$STAT" = "xenabled" \
-a -f "/etc/e-smith/db/configuration/defaults/doraemon/status" \
-a -x /usr/sbin/doraemon; then
exit 0;
fi
stop; exit 0;
end script
#
# 30script
#
exec /usr/sbin/doraemon -f /etc/httpd/doraemon/httpd.conf

View File

@ -1,9 +0,0 @@
/var/log/doraemon/*log {
missingok
notifempty
sharedscripts
delaycompress
postrotate
/sbin/reload doraemon > /dev/null 2>/dev/null || true
endscript
}

View File

@ -1,391 +0,0 @@
<?php
/*
* Copyright (C) 2011 Nethesis S.r.l.
*
* This script is part of NethServer.
*
* NethServer is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NethServer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NethServer. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Read and write parameters into SME DB
*
* Ths class implements an interface to SME database executing the command /sbin/e-smith/db with sudo.
* The class needs /etc/sudoers configurazione. In the sudoers file you must have something like this:
* <code>
* Cmnd_Alias SME = /sbin/e-smith/db, /sbin/e-smith/signal-event
* www ALL=NOPASSWD: SME
* </code>
*
*
* @author Giacomo Sanchietti <giacomo.sanchietti@nethesis.it>
* @author Davide Principi <davide.principi@nethesis.it>
* @since 1.0
* @internal
*/
class EsmithDatabase
{
/**
* @var SME DB database command
* */
private $command = "/usr/bin/sudo /sbin/e-smith/db";
/**
* @var $db Database name, it's translated into the db file path. For example: /home/e-smith/db/testdb
* */
private $db;
/**
* This static class variable shares a socket connection to smwingsd
*
* @var resource
*/
private static $socket;
/**
* Construct an object to access a SME Configuration database file
* with $user's privileges.
*
* @param string $database Database name
*/
public function __construct($database)
{
if ( ! $database)
sprintf("%s: You must provide a valid database name.", get_class($this));
$this->db = $database;
}
public function getAll($type = NULL)
{
$output = "";
$ret = $this->dbRead('getjson', array(), $output);
if ($ret !== 0) {
sprintf("%s: internal database command failed!", __CLASS__);
}
$data = json_decode($output, TRUE);
if ( ! is_array($data)) {
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
}
$result = array();
foreach ($data as $item) {
// Apply type check filter:
if (isset($type) && $type !== $item['type']) {
continue;
}
$props = isset($item['props']) ? $item['props'] : array();
$result[$item['name']] = array_merge($props, array('type' => $item['type']));
}
return $result;
}
public function getAllByProp($propName = NULL, $propValue = NULL)
{
$output = "";
$ret = $this->dbRead('getjson', array(), $output);
if ($ret !== 0) {
sprintf("%s: internal database command failed!", __CLASS__);
}
$data = json_decode($output, TRUE);
if ( ! is_array($data)) {
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
}
$result = array();
foreach ($data as $item) {
$item['props']['type'] = $item['type'];
$item['props']['name'] = $item['name'];
if (isset($propName) && isset($propValue)) {
if (isset($item['props'][$propName]) && $propValue == $item['props'][$propName]) {
$result[$item['name']] = $item['props'];
}
} else {
$result[$item['name']] = $item['props'];
}
}
return $result;
}
public function getKey($key)
{
$output = '';
$ret = $this->dbRead('getjson', array($key), $output);
if ($ret !== 0) {
sprintf("%s: internal database command failed", __CLASS__);
}
$data = json_decode($output, TRUE);
if ($data === 1) {
// Key has not been found
return array();
} elseif ( ! is_array($data)) {
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
}
$data['props']['type'] = $data['type'];
$data['props']['name'] = $data['name'];
return $data['props'];
}
public function getKeyValue($key)
{
$output = '';
$ret = $this->dbRead('getjson', array($key), $output);
if ($ret !== 0) {
sprintf("%s: internal database command failed", __CLASS__);
}
$data = json_decode($output, TRUE);
if ($data === 1) {
// Key has not been found
return array();
} elseif ( ! is_array($data)) {
sprintf("%s: unexpected json string `%s`", __CLASS__, substr($output, 0, 8));
}
return $data['type'];
}
public function setKey($key, $type, $props)
{
$output = NULL;
$ret = $this->dbExec('set', $this->prepareArguments($key, $type, $props), $output);
return ($ret == 0);
}
public function deleteKey($key)
{
$output = NULL;
$output = NULL;
$ret = $this->dbExec('delete', $this->prepareArguments($key), $output);
return ($ret == 0);
}
/**
* Return the type of a key
* Act like: /sbin/e-smith/db dbfile gettype key
*
* @param string $key the key to retrieve
* @access public
* @return string the type of the key
*/
public function getType($key)
{
$output = NULL;
$ret = $this->dbRead('gettype', array($key), $output);
return trim($output);
}
/**
* Set the type of a key
* Act like: /sbin/e-smith/db dbfile settype key type
*
* @param string $key the key to change
* @param string $type the new type
* @access public
* @return bool true on success, FALSE otherwise
*/
public function setType($key, $type)
{
$output = NULL;
$ret = $this->dbExec('settype', $this->prepareArguments($key, $type), $output);
return ($ret == 0);
}
public function getProp($key, $prop)
{
$output = NULL;
$ret = $this->dbRead('getprop', array($key, $prop), $output);
return trim($output);
}
public function setProp($key, $props)
{
$output = NULL;
$ret = $this->dbExec('setprop', $this->prepareArguments($key, $props), $output);
return ($ret == 0);
}
public function delProp($key, $props)
{
$output = NULL;
$ret = $this->dbExec('delprop', array_merge(array($key), array_values($props)), $output);
return ($ret == 0);
}
/**
* Execute the db command
* @param string $command The command to invoke
* @param array $args The command arguments
* @param string &$output The output from the command process
* @return int The command exit code
*/
private function dbExec($command, $args, &$output)
{
// prepend the database name and command
array_unshift($args, $this->db, $command);
$exitCode = 0;
$oArr = array();
exec($this->command . ' ' . implode(' ', array_map('escapeshellarg', $args)), $oArr, $exitCode);
$output = implode("\n", $oArr);
return $exitCode;
}
/**
* Read db data from memory cache daemon
* @param string $command The command to invoke
* @param array $args The command arguments
* @param string &$output The output from the read socket
* @return int The command exit code
*/
private function dbRead($command, $args, &$output)
{
if ( ! isset(self::$socket) ) {
$socketPath = '/var/run/smwingsd.sock';
$errno = 0;
$errstr = '';
self::$socket = fsockopen('unix://' . $socketPath, -1, $errno, $errstr);
if( ! is_resource(self::$socket)) {
sprintf("Invalid socket (%d): %s. Fall back to exec().", $errno, $errstr);
}
}
if ( ! is_resource(self::$socket)) {
return $this->dbExec($command, call_user_func_array(array($this, 'prepareArguments'), $args), $output);
}
// prepend the database name and command
array_unshift($args, $this->db, $command);
$ret = $this->sendMessage(self::$socket, 0x10, $args);
if ( ! $ret ) {
return 1;
}
$ret = $this->recvMessage(self::$socket);
if ( ! $ret ) {
return 1;
}
if ($command === 'getjson') {
$output = $ret;
} else {
$output = json_decode($ret, TRUE);
}
return 0;
}
private function sendMessage($socket, $type, $args = array())
{
$payload = json_encode($args);
$data = pack('CN', (int) $type, strlen($payload)) . $payload;
$written = fwrite($socket, $data);
if ($written !== strlen($data)) {
echo 'Socket write error';
}
return TRUE;
}
private function recvMessage($socket)
{
$buf = $this->safeRead($socket, 5);
if ($buf === FALSE) {
echo 'Socket read error';
return false;
}
$header = unpack('Ctype/Nsize', $buf);
if ( ! is_array($header)) {
echo 'Invalid message header';
return false;
}
$message = $this->safeRead($socket, $header['size']);
if ($message === FALSE) {
echo 'Socket read error';
return false;
}
if ($header['type'] & 0x02) {
return NULL;
}
return $message;
}
private function safeRead($socket, $size)
{
$buffer = "";
$count = 0;
while($count < $size) {
if(feof($socket)) {
return FALSE;
}
$chunk = fread($socket, $size - $count);
$count += strlen($chunk);
if($chunk === FALSE) {
return FALSE;
}
$buffer .= $chunk;
}
return $buffer;
}
/**
* Take arbitrary arguments and flattenize to an array
*
* @param mixed $_
* @return array
*/
private function prepareArguments()
{
$args = array();
foreach (func_get_args() as $arg) {
if (is_array($arg)) {
foreach ($arg as $propName => $propValue) {
$args[] = $propName;
$args[] = $propValue;
}
} else {
$args[] = (String) $arg;
}
}
return $args;
}
}

View File

@ -1,261 +0,0 @@
<?php
include_once("EsmithDatabase.php");
define('CONFIG_KEY', 'doraemon');
$hosts_db = new EsmithDatabase('hosts');
$roles_db = new EsmithDatabase('roles');
$config_db = new EsmithDatabase('configuration');
$uri = $_SERVER['REQUEST_URI'];
// add leading '/' to uri string, works if $uri is empty
$uri = (substr($uri, 0, 1) == '/') ? $uri : '/' . $uri;
$pos = strpos($uri, '?');
if (false !== $pos) {
$route = substr($uri, 0, (int) $pos);
} else {
$route = $uri;
}
// remove trailing '/'
$route = rtrim($route, '/');
$query_string = $_SERVER['QUERY_STRING'];
$get_params = array();
parse_str($query_string, $get_params);
// determine internal function name (all lowercase, dash2underscore, named ROUTE_routename )
$function = preg_replace(':\-:', '_', strtolower($route));
$function = preg_replace(':^/:', 'ROUTE_', $function);
if (function_exists($function)) {
$function();
} else {
die('Go away.');
}
function varsForRole($role, $json=true) {
global $roles_db;
$result = $roles_db->getKey($role);
$return = array('role'=>$role,'delpkg'=>'','addpkg'=>'');
if (is_array($result)) {
if (is_string($result['Addpkg']))
$return['addpkg'] = preg_split("/[\s,]+/", $result['Addpkg']);
if (is_string($result['Delpkg']))
$return['delpkg'] = preg_split("/[\s,]+/", $result['Delpkg']);
}
if ($json) {
return json_encode($return);
} else {
return $return;
}
}
function getClient($mac=false) {
global $hosts_db;
if (!$mac) {
$mac = getClientMac();
if (!$mac)
return false;
}
$hosts = $hosts_db->getAllByProp('MacAddress',$mac);
if (count($hosts)>0) {
return array_pop($hosts);
} else {
return false;
}
}
function getClientMac($ip=false) {
if (false === $ip) {
$ip = $_SERVER['REMOTE_ADDR'];
}
$result = pingHost($ip,1);
if (!$result)
return false;
$macAddr=false;
$arptable=`arp -n $ip`;
$lines=explode("\n", $arptable);
foreach($lines as $line) {
$cols=preg_split('/\s+/', trim($line));
if ($cols[0]==$ip)
{
return $cols[2];
}
}
}
function newHostname($mac, $base, $role) {
global $config_db, $hosts_db;
if(!preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $mac)) {
echo "invalid mac address";
return false;
}
if ($client=getClient($mac)) {
echo $client['name'];
return;
}
$domainName = (string)$config_db->getKeyValue('DomainName');
$digits = $config_db->getProp(CONFIG_KEY,'NamingDigits');
$formatstring = '%s-%0'.$digits.'d';
$nr=0;
$hostname = '';
do {
$nr++;
$hostname = sprintf($formatstring, $base, $nr);
# TODO: we'll use the hostname in future, maybe
# if (trim($domainName) != '') {
# $hostname .= '.' . $domainName;
# }
} while ($hosts_db->getKey($hostname));
$hosts_db->setKey($hostname, 'local', array(
'MacAddress' => $mac,
'Role' => $role,
'Description' => ''
));
echo $hostname;
}
function pingHost($host, $timeout=1) {
$pingresult = exec("/bin/ping -c 1 -t 2 -W $timeout $host", $output, $retvar);
return (0 == $retvar);
}
function ROUTE_domain() {
global $config_db;
$theFile = $config_db->getProp(CONFIG_KEY,'DomainFile');
passthru("/usr/bin/sudo /bin/cat $theFile");
}
function ROUTE_mgmtkey() {
global $config_db;
$theFile = $config_db->getProp(CONFIG_KEY,'ManagementKeyFile');
passthru("/usr/bin/sudo /bin/cat $theFile");
}
function ROUTE_epoptes_srv() {
global $hosts_db;
$hosts = $hosts_db->getAllByProp('Role','docenti');
ksort($hosts);
if (count($hosts) == 0) {
echo 'none';
} else {
$host = array_pop($hosts);
echo $host['name'];
}
}
function ROUTE_ansible_host() {
global $get_params;
if (isset($get_params['role'])) {
$role = $get_params['role'];
} elseif ($client = getClient()) {
$role = $client['Role'];
} else {
$role = 'unknown';
}
echo varsForRole($role);
}
function ROUTE_hosts() {
global $hosts_db, $get_params;
if (isset($get_params['role'])) {
$items = $hosts_db->getAllByProp('Role', $get_params['role']);
} else {
$items = $hosts_db->getAll('local');
}
$results=array();
foreach ($items as $name=>$item) {
$results[]=array(
'mac'=>$item['MacAddress'],
'hostname'=>$name,
'role'=>$item['Role']
);
}
echo json_encode($results);
}
function ROUTE_ansible_list() {
global $get_params;
if (isset($get_params['role'])) {
$role = $get_params['role'];
} elseif ($client = getClient()) {
$role = $client['Role'];
} else {
$role = 'unknown';
}
$vars = varsForRole($role, false);
echo json_encode(array(
'localhost'=>array(
'hosts'=>array('localhost'),
'vars'=>array('ansible_connection'=>'local')
),
'_meta'=> array('hostvars'=>array('localhost'=>$vars))
));
}
function ROUTE_vaultpass() {
global $config_db;
$theFile = $config_db->getProp(CONFIG_KEY,'VaultPassFile');
$oArr = array();
exec('/usr/bin/sudo /bin/cat ' . $theFile, $oArr, $exitCode);
$content = trim(implode("\n", $oArr));
if (false === $content) {
echo 'no file';
return false;
}
if (!$client = getClient()) {
echo 'no client';
return false;
}
$key = md5($client['name'], true);
$padlenght = 16 - (strlen($content) % 16);
$content_padded = $content . str_repeat('x', $padlenght);
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $content_padded, MCRYPT_MODE_ECB);
echo base64_encode($ciphertext);
}
function ROUTE_whatsmyhostname() {
global $config_db, $get_params;
if (isset($get_params['role'])) {
$role = $get_params['role'];
} else {
$role = $config_db->getProp(CONFIG_KEY,'DefaultRole');
}
if (isset($get_params['base'])) {
$base = $get_params['base'];
} else {
$base = $config_db->getProp(CONFIG_KEY,'NamingBase');
}
$mac = getClientMac();
if (!$mac) {
echo 'no-mac-address';
return false;
}
newHostname($mac, $base, $role);
}
function ROUTE_mac2hostname() {
global $config_db, $get_params;
if (isset($get_params['mac'])) {
$mac = $get_params['mac'];
} else {
echo 'Usage: GET /mac2hostname?mac=XX_XX_XX_XX_XX_XX[&base=YYY][&role=ZZZ]';
return false;
}
if (isset($get_params['role'])) {
$role = $get_params['role'];
} else {
$role = $config_db->getProp(CONFIG_KEY,'DefaultRole');
}
if (isset($get_params['base'])) {
$base = $get_params['base'];
} else {
$base = $config_db->getProp(CONFIG_KEY,'NamingBase');
}
newHostname($mac, $base, $role);
}

View File

@ -1,13 +0,0 @@
<?php
/* /usr/share/nethesis/NethServer/Language/en/NethServer_Module_Doraemon.php */
$L['Doraemon_Title'] = 'Doraemon';
$L['hostname_label'] = 'Hostname';
$L['MacAddress_label'] = 'MAC Address';
$L['shutdown_label'] = 'Shutdown';
$L['reboot_label'] = 'Reboot';
$L['wake_label'] = 'Wake Up';
$L['shutdown-all_label'] = 'Shutdown all';
$L['reboot-all_label'] = 'Reboot all';
$L['wake-all_label'] = 'Wake Up all';

View File

@ -1,9 +0,0 @@
<?php
/* /usr/share/nethesis/NethServer/Language/en/NethServer_Module_Doraemon_Hosts.php */
$L['Hosts_Title'] = 'Hosts';
$L['Create_Host_Header'] = 'Create a new host';
$L['Update_Host_Header'] = 'Update host "${0}"';
$L['Role_label'] = 'Role';
$L['Key_label'] = 'Hostname';

View File

@ -1,13 +0,0 @@
<?php
/* /usr/share/nethesis/NethServer/Language/it/NethServer_Module_Doraemon.php */
$L['Doraemon_Title'] = 'Doraemon';
$L['hostname_label'] = 'Hostname';
$L['MacAddress_label'] = 'MAC Address';
$L['shutdown_label'] = 'Spegni';
$L['reboot_label'] = 'Riavvia';
$L['wake_label'] = 'Accendi';
$L['shutdown-all_label'] = 'Spegni tutti';
$L['reboot-all_label'] = 'Riavvia tutti';
$L['wake-all_label'] = 'Accendi tutti';

View File

@ -1,9 +0,0 @@
<?php
/* /usr/share/nethesis/NethServer/Language/it/NethServer_Module_Doraemon_Hosts.php */
$L['Hosts_Title'] = 'Hosts';
$L['Create_Host_Header'] = 'Crea un nuovo host';
$L['Update_Host_Header'] = 'Modifica il nome host "${0}"';
$L['Role_label'] = 'Ruolo';
$L['Key_label'] = 'Hostname';

View File

@ -1,17 +0,0 @@
<?php
namespace NethServer\Module;
class Doraemon extends \Nethgui\Controller\TabsController
{
protected function initializeAttributes(\Nethgui\Module\ModuleAttributesInterface $base)
{
return \Nethgui\Module\SimpleModuleAttributesProvider::extendModuleAttributes($base, 'Configuration', 50);
}
public function initialize()
{
parent::initialize();
$this->loadChildrenDirectory();
}
}

View File

@ -1,36 +0,0 @@
<?php
namespace NethServer\Module\Doraemon;
class Hosts extends \Nethgui\Controller\TableController
{
public function initialize()
{
$columns = array(
'Key',
'MacAddress',
'Role',
'Actions',
);
$this
->setTableAdapter($this->getPlatform()->getTableAdapter('hosts', 'local'))
->setColumns($columns)
->addRowAction(new \NethServer\Module\Doraemon\Hosts\Modify('update'))
->addRowAction(new \NethServer\Module\Doraemon\Hosts\TogglePower('wake'))
->addRowAction(new \NethServer\Module\Doraemon\Hosts\TogglePower('reboot'))
->addRowAction(new \NethServer\Module\Doraemon\Hosts\TogglePower('shutdown'))
->addRowAction(new \NethServer\Module\Doraemon\Hosts\Modify('delete'))
->addTableAction(new \NethServer\Module\Doraemon\Hosts\Modify('create'))
->addTableAction(new \NethServer\Module\Doraemon\Hosts\TogglePowerAll('wake-all'))
->addTableAction(new \NethServer\Module\Doraemon\Hosts\TogglePowerAll('reboot-all'))
->addTableAction(new \NethServer\Module\Doraemon\Hosts\TogglePowerAll('shutdown-all'))
->addTableAction(new \Nethgui\Controller\Table\Help('Help'))
;
parent::initialize();
}
}

View File

@ -1,55 +0,0 @@
<?php
namespace NethServer\Module\Doraemon\Hosts;
use Nethgui\System\PlatformInterface as Validate;
class Modify extends \Nethgui\Controller\Table\Modify
{
public function initialize()
{
$parameterSchema = array(
// was: Validate::HOSTNAME_FQDN, but we need hostname
array('hostname', Validate::HOSTNAME_SIMPLE, \Nethgui\Controller\Table\Modify::KEY),
array('MacAddress', Validate::MACADDRESS, \Nethgui\Controller\Table\Modify::FIELD),
array('Role', Validate::ANYTHING, \Nethgui\Controller\Table\Modify::FIELD),
// array('Role', Validate::ROLES_COLLECTION ??? TODO!!!, \Nethgui\Controller\Table\Modify::FIELD),
);
$this->setSchema($parameterSchema);
parent::initialize();
}
public function validate(\Nethgui\Controller\ValidationReportInterface $report)
{
if ($this->getIdentifier() === 'delete') {
$v = $this->createValidator()->platform('host-delete');
if ( ! $v->evaluate($this->parameters['hostname'])) {
$report->addValidationError($this, 'Key', $v);
}
}
parent::validate($report);
}
protected function onParametersSaved($parameters)
{
$actionName = $this->getIdentifier();
if ($actionName === 'update') {
$actionName = 'modify';
}
$this->getPlatform()->signalEvent(sprintf('host-%s &', $actionName));
}
public function prepareView(\Nethgui\View\ViewInterface $view)
{
parent::prepareView($view);
$templates = array(
'create' => 'NethServer\Template\Doraemon\Hosts',
'update' => 'NethServer\Template\Doraemon\Hosts',
'delete' => 'Nethgui\Template\Table\Delete',
);
$view->setTemplate($templates[$this->getIdentifier()]);
}
}

View File

@ -1,48 +0,0 @@
<?php
namespace NethServer\Module\Doraemon\Hosts;
use Nethgui\System\PlatformInterface as Validate;
/**
* Toggle host power status
*
* Fires events
* - doraemon-wake
* - doraemon-reboot
* - doraemon-shutdown
*/
class TogglePower extends \Nethgui\Controller\Table\AbstractAction
{
public function __construct($identifier = NULL)
{
if ($identifier !== 'wake' && $identifier !== 'reboot' && $identifier !== 'shutdown') {
throw new \InvalidArgumentException(sprintf('%s: module identifier must be one of "wake" or "reboot" or "shutdown".', get_class($this)), 1325579395);
}
parent::__construct($identifier);
}
public function bind(\Nethgui\Controller\RequestInterface $request)
{
$this->declareParameter('hostname', Validate::HOSTNAME_SIMPLE);
parent::bind($request);
$hostname = \Nethgui\array_end($request->getPath());
if ( ! $hostname) {
throw new \Nethgui\Exception\HttpException('Not found', 404, 1322148400);
}
$this->parameters['hostname'] = $hostname;
}
public function process()
{
if ( ! $this->getRequest()->isMutation()) {
return;
}
$this->getPlatform()->signalEvent(sprintf('doraemon-%s@post', $this->getIdentifier()), array($this->parameters['hostname']));
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace NethServer\Module\Doraemon\Hosts;
use Nethgui\System\PlatformInterface as Validate;
/**
* Toggle host power status
*
* Fires events
* - doraemon-wake-all
* - doraemon-reboot-all
* - doraemon-shutdown-all
*/
class TogglePowerAll extends \Nethgui\Controller\Table\AbstractAction
{
public function __construct($identifier = NULL)
{
if ($identifier !== 'wake-all' && $identifier !== 'reboot-all' && $identifier !== 'shutdown-all') {
throw new \InvalidArgumentException(sprintf('%s: module identifier must be one of "wake-all" or "reboot-all" or "shutdown-all".', get_class($this)), 1325579395);
}
parent::__construct($identifier);
}
public function process()
{
if ( ! $this->getRequest()->isMutation()) {
return;
}
$this->getPlatform()->signalEvent(sprintf('doraemon-%s@post', $this->getIdentifier()));
}
}

View File

@ -1,22 +0,0 @@
<?php
/* @var $view \Nethgui\Renderer\Xhtml */
if ($view->getModule()->getIdentifier() == 'update') {
// disable updating of hostname
// the hostname is the key of the db item, if you change it, bad things happens
$keyFlags = $view::STATE_DISABLED;
$template = 'Update_Host_Header';
} else {
$keyFlags = 0;
$template = 'Create_Host_Header';
}
echo $view->header('hostname')->setAttribute('template', $T($template));
echo $view->panel()
->insert($view->textInput('hostname', $keyFlags))
->insert($view->textInput('MacAddress'))
->insert($view->textInput('Role'))
;
echo $view->buttonList($view::BUTTON_SUBMIT | $view::BUTTON_CANCEL | $view::BUTTON_HELP);

View File

@ -1,23 +0,0 @@
<?php
$view->requireFlag($view::INSET_DIALOG);
if ($view->getModule()->getIdentifier() == 'shutdown') {
$headerText = $T('Shutdown host `${0}`');
$panelText = $T('Proceed with host `${0}` shutdown?');
} elseif ($view->getModule()->getIdentifier() == 'reboot') {
$headerText = $T('Reboot host `${0}`');
$panelText = $T('Proceed with host `${0}` reboot?');
} else {
$headerText = $T('Wakeup host `${0}`');
$panelText = $T('Proceed with host `${0}` wakeup?');
}
echo $view->panel()
->insert($view->header('hostname')->setAttribute('template', $headerText))
->insert($view->textLabel('hostname')->setAttribute('template', $panelText))
;
echo $view->buttonList()
->insert($view->button('Yes', $view::BUTTON_SUBMIT))
->insert($view->button('No', $view::BUTTON_CANCEL)->setAttribute('value', $view['Cancel']))
;

View File

@ -1,21 +0,0 @@
<?php
$view->requireFlag($view::INSET_DIALOG);
if ($view->getModule()->getIdentifier() == 'shutdown-all') {
$headerText = $T('Shutdown all hosts');
$panelText = $T('Proceed with all hosts shutdown?');
} elseif ($view->getModule()->getIdentifier() == 'reboot-all') {
$headerText = $T('Reboot all hosts');
$panelText = $T('Proceed with all hosts reboot?');
} else {
$headerText = $T('Wakeup all hosts');
$panelText = $T('Proceed with all hosts wakeup?');
}
echo $view->header()->setAttribute('template', $headerText);
// TODO: add a text label here
// echo $view->textLabel()->setAttribute('template', $panelText);
echo $view->buttonList()
->insert($view->button('Yes', $view::BUTTON_SUBMIT))
->insert($view->button('No', $view::BUTTON_CANCEL)->setAttribute('value', $view['Cancel']))
;