diff --git a/da_integrare.spec b/da_integrare.spec new file mode 100644 index 0000000..0742cb1 --- /dev/null +++ b/da_integrare.spec @@ -0,0 +1,97 @@ +Summary: apache/mod_php stack for Doraemon +Name: doraemon +Version: 1.3.0.1 +Release: 1%{?dist} +License: GPL +URL: %{url_prefix}/%{name} +BuildArch: noarch + +Source0: %{name}-%{version}.tar.gz + + +BuildRequires: nethserver-devtools + +BuildRequires: systemd +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd + +Requires: httpd, php, sudo, php-xml, php-mcrypt, nethserver-httpd-admin +Requires: nethserver-base, nethserver-php +Requires: nethserver-lang-en + +%description +Runs an Apache instance on port 3000 that serves +the doraemon web application + +%prep +%setup + + +# Nethgui: +cd %{_builddir}/nethgui-%{nethgui_commit} + +%build +perl createlinks +mkdir -p root/%{_nseventsdir}/%{name}-update + +%install +(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 + +# Copy the server-manager dir +mkdir -p %{buildroot}%{_nsuidir} +cp -av nethserver-manager %{buildroot}%{_nsuidir}/nethserver-manager + +%files -f %{name}-%{version}-%{release}-filelist +%defattr(-,root,root) +%doc %{extradocs} + +%{_nsuidir}/nethserver-manager + + +%dir %{_nseventsdir}/%{name}-update +%dir %{_sysconfdir}/httpd/doraemon.d + +%attr(0750,srvmgr,srvmgr) %dir %{_localstatedir}/cache/doraemon +%attr(0644,root,root) %ghost %{_sysconfdir}/init/doraemon.conf +%attr(0644,root,root) %ghost %{_sysconfdir}/httpd/doraemon/httpd.conf +%attr(0700,root,root) %dir %{_localstatedir}/log/doraemon +%attr(0644,root,root) %config %ghost %{_localstatedir}/log/doraemon/access_log +%attr(0644,root,root) %config %ghost %{_localstatedir}/log/doraemon/error_log +%config(noreplace) /etc/sysconfig/doraemon + +%post +%systemd_post doraemon.service + +%preun +%systemd_preun doraemon.service + +%postun +%systemd_postun + +%changelog +* Tue Oct 27 2015 Emiliano Vavassori - 1.2.1-2.ns6 +- Packing corrections to rpm to fix upgrading issue + +* Tue Oct 27 2015 Emiliano Vavassori - 1.2.1-1.ns6 +- Packing new minor correction on how packages are extracted from db + +* Mon Sep 11 2015 Emiliano Vavassori - 1.2.0-1.ns6 +- Packing new version with ansible dynamic inventory support, epoptes server role support. + +* Mon Sep 07 2015 Emiliano Vavassori - 1.1.1-1.ns6 +- Packing new version with correct roles. + +* Sun Sep 06 2015 Emiliano Vavassori - 1.1.0-1.ns6 +- Packing new version with new functionalities. + +* Wed Jul 15 2015 Emiliano Vavassori - 1.0.0-2.ns6 +- Added logrotate file. + +* Mon Jul 13 2015 Emiliano Vavassori - 1.0.0-1.ns6 +- Unique binary for both ansiblehelper and mac2hostname. diff --git a/defaults/access b/defaults/access deleted file mode 100644 index 3e18ebf..0000000 --- a/defaults/access +++ /dev/null @@ -1 +0,0 @@ -private diff --git a/doraemon.ini b/doraemon.ini deleted file mode 100644 index a9bbdc8..0000000 --- a/doraemon.ini +++ /dev/null @@ -1,16 +0,0 @@ -[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 diff --git a/doraemon.logrotate b/doraemon.logrotate deleted file mode 100644 index 443f997..0000000 --- a/doraemon.logrotate +++ /dev/null @@ -1,6 +0,0 @@ -/var/log/doraemon.log { - daily - rotate 15 - compress - missingok -} diff --git a/doraemon.py b/doraemon.py deleted file mode 100644 index 459c316..0000000 --- a/doraemon.py +++ /dev/null @@ -1,234 +0,0 @@ -#!/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() diff --git a/doraemon/EsmithDatabase.php b/doraemon/EsmithDatabase.php new file mode 100644 index 0000000..e0e0268 --- /dev/null +++ b/doraemon/EsmithDatabase.php @@ -0,0 +1,391 @@ +. + */ + +/** + * 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: + * + * Cmnd_Alias SME = /sbin/e-smith/db, /sbin/e-smith/signal-event + * www ALL=NOPASSWD: SME + * + * + * + * @author Giacomo Sanchietti + * @author Davide Principi + * @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; + } + + + + +} \ No newline at end of file diff --git a/doraemon/index.php b/doraemon/index.php new file mode 100644 index 0000000..41daa53 --- /dev/null +++ b/doraemon/index.php @@ -0,0 +1,254 @@ +getKey($role); + $return = array('role'=>$role,'Delpkg'=>'','Addpkg'=>''); + if (is_array($result)) { + if (is_string($result['Addpkg'])) $return['Addpkg'] = $result['Addpkg']; + if (is_string($result['Delpkg'])) $return['Delpkg'] = $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 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'); + $content = file_get_contents($theFile); + if (false !== $content) { + echo $content; + } +} + +function ROUTE_mgmtkey() { + global $config_db; + $theFile = $config_db->getProp(CONFIG_KEY,'ManagementKeyFile'); + $content = file_get_contents($theFile); + if (false !== $content) { + echo $content; + } +} + +function ROUTE_epoptes_srv() { + global $hosts_db; + $hosts = $hosts_db->getAllByProp('Role','docenti'); + if (count($hosts) == 0) { + echo 'none'; + } else { + $host = array_pop($hosts); + echo json_encode($host); + } +} + +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; + global $get_params; + if (isset($get_params['role'])) { + $items = $hosts_db->getAllByProp('Role', $get_params['role']); + } else { + $items = $hosts_db->getAll(); + } + $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'); + $content = trim(file_get_contents($theFile)); + if (false === $content) { + echo 'no file'; return; + } + if (!$client = getClient()) { + echo 'no client'; return; + } + $key = md5($client['name']); + $padlenght = 16 - (strlen($content) % 16); + $content_padded = 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; + $mac = getClientMac(); + if (!$mac) return false; + $base = $config_db->getProp(CONFIG_KEY,'NamingBase'); + $role = $config_db->getProp(CONFIG_KEY,'DefaultRole'); + echo newHostname($mac, $base, $role); +} + +function ROUTE_mac2hostname() { + global $config_db; + global $get_params; + $mac = getClientMac(); + if (!$mac) { + echo 'Usage: GET /mac2hostname?mac=XX_XX_XX_XX_XX_XX[&base=YYY][&role=ZZZ]'; + return; + } + + 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'); + } + + echo newHostname($mac, $base, $role); +} + +function newHostname($mac, $base, $role) { + global $config_db; + global $hosts_db; + if(!preg_match('/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/', $mac)) { + echo "invalid mac address"; + return; + } + + if ($client=getClient($mac)) { + echo $client['name']; + return; + } + + $domainName = $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); + if ($domainName != '') { + $hostname .= '.' . $domainname; + } + } while ($hosts_db->getKey($hostname)); + + $hosts_db->setKey($hostname, 'remote', array( + 'MacAddress' => $mac, + 'Role' => $role, + 'Description' => '' + )); + + echo $hostname; +} + diff --git a/initrd b/initrd deleted file mode 100644 index 5453b61..0000000 --- a/initrd +++ /dev/null @@ -1,79 +0,0 @@ -#!/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 diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/DefaultRole b/root/etc/e-smith/db/configuration/defaults/doraemon/DefaultRole new file mode 100644 index 0000000..b051c6c --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/DefaultRole @@ -0,0 +1 @@ +client diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/DomainFile b/root/etc/e-smith/db/configuration/defaults/doraemon/DomainFile new file mode 100644 index 0000000..e978cbd --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/DomainFile @@ -0,0 +1 @@ +/etc/domain.yml diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/ManagementKeyFile b/root/etc/e-smith/db/configuration/defaults/doraemon/ManagementKeyFile new file mode 100644 index 0000000..a2d5166 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/ManagementKeyFile @@ -0,0 +1 @@ +/home/amgmt/.ssh/id_rsa.pub diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/NamingBase b/root/etc/e-smith/db/configuration/defaults/doraemon/NamingBase new file mode 100644 index 0000000..10441b4 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/NamingBase @@ -0,0 +1 @@ +lab diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/NamingDigits b/root/etc/e-smith/db/configuration/defaults/doraemon/NamingDigits new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/NamingDigits @@ -0,0 +1 @@ +2 diff --git a/defaults/TCPPort b/root/etc/e-smith/db/configuration/defaults/doraemon/TCPPort similarity index 100% rename from defaults/TCPPort rename to root/etc/e-smith/db/configuration/defaults/doraemon/TCPPort diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/VaultPassFile b/root/etc/e-smith/db/configuration/defaults/doraemon/VaultPassFile new file mode 100644 index 0000000..65f2cd9 --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/VaultPassFile @@ -0,0 +1 @@ +/home/amgmt/.ansible/vault.txt diff --git a/root/etc/e-smith/db/configuration/defaults/doraemon/access b/root/etc/e-smith/db/configuration/defaults/doraemon/access new file mode 100644 index 0000000..a48cf0d --- /dev/null +++ b/root/etc/e-smith/db/configuration/defaults/doraemon/access @@ -0,0 +1 @@ +public diff --git a/defaults/status b/root/etc/e-smith/db/configuration/defaults/doraemon/status similarity index 100% rename from defaults/status rename to root/etc/e-smith/db/configuration/defaults/doraemon/status diff --git a/defaults/type b/root/etc/e-smith/db/configuration/defaults/doraemon/type similarity index 100% rename from defaults/type rename to root/etc/e-smith/db/configuration/defaults/doraemon/type diff --git a/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/00template_defaults b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/00template_defaults new file mode 100644 index 0000000..4c1a258 --- /dev/null +++ b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/00template_defaults @@ -0,0 +1,10 @@ +{ + # + # 00template_defaults + # + + $localAccess = ''; + $port = ${'doraemon'}{TCPPort} || "3000"; + + ""; +} \ No newline at end of file diff --git a/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/20Manager b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/20Manager new file mode 100644 index 0000000..b879b6a --- /dev/null +++ b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/20Manager @@ -0,0 +1,127 @@ +{ + + $OUT .= <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 +} + diff --git a/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/40DefaultAccess b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/40DefaultAccess new file mode 100644 index 0000000..fe3528e --- /dev/null +++ b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/40DefaultAccess @@ -0,0 +1,16 @@ + +# First, we configure the "default" to be a very restrictive set of +# permissions. + + + Options None + AllowOverride None + order deny,allow + deny from all + allow from none + + + + Allow from all + + diff --git a/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/60DoraemonInterface b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/60DoraemonInterface new file mode 100644 index 0000000..743bbf3 --- /dev/null +++ b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/60DoraemonInterface @@ -0,0 +1,23 @@ + +DocumentRoot /usr/share/doraemon +Alias /icons/ /var/www/icons/ + + + 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 + + + + + diff --git a/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/90include b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/90include new file mode 100644 index 0000000..e7f3cdb --- /dev/null +++ b/root/etc/e-smith/templates/etc/httpd/doraemon/httpd.conf/90include @@ -0,0 +1 @@ +Include conf.d/php.conf diff --git a/root/etc/httpd/doraemon/.gitignore b/root/etc/httpd/doraemon/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/root/etc/logrotate.d/doraemon b/root/etc/logrotate.d/doraemon new file mode 100644 index 0000000..d409c39 --- /dev/null +++ b/root/etc/logrotate.d/doraemon @@ -0,0 +1,9 @@ +/var/log/doraemon/*log { + missingok + notifempty + sharedscripts + delaycompress + postrotate + /sbin/reload doraemon > /dev/null 2>/dev/null || true + endscript +} \ No newline at end of file diff --git a/root/etc/sysconfig/doraemon b/root/etc/sysconfig/doraemon new file mode 100644 index 0000000..007b91f --- /dev/null +++ b/root/etc/sysconfig/doraemon @@ -0,0 +1,8 @@ +# +# This file can be used to set additional environment variables for +# the httpd-admin process, or pass additional options to the +# httpd-admin executable. See httpd-admin.service unit. +# + +LANG=C + diff --git a/root/usr/lib/systemd/system/doraemon-reload.service b/root/usr/lib/systemd/system/doraemon-reload.service new file mode 100644 index 0000000..320c054 --- /dev/null +++ b/root/usr/lib/systemd/system/doraemon-reload.service @@ -0,0 +1,9 @@ +[Unit] +Description=Graceful restart of doraemon service +Documentation=https://github.com/bglug-it/doraemon +Requisite=doraemon.service + +[Service] +Type=oneshot +ExecStart=/usr/sbin/httpd -f /etc/httpd/doraemon/httpd.conf -k graceful + diff --git a/root/usr/lib/systemd/system/doraemon-reload.timer b/root/usr/lib/systemd/system/doraemon-reload.timer new file mode 100644 index 0000000..7531f12 --- /dev/null +++ b/root/usr/lib/systemd/system/doraemon-reload.timer @@ -0,0 +1,9 @@ +[Unit] +Description=Delayed graceful restart of doraemon service +Documentation=https://github.com/bglug-it/doraemon +Conflicts=doraemon-reload.service + +[Timer] +OnActiveSec=5 + + diff --git a/root/usr/lib/systemd/system/doraemon.service b/root/usr/lib/systemd/system/doraemon.service new file mode 100644 index 0000000..d4eeb35 --- /dev/null +++ b/root/usr/lib/systemd/system/doraemon.service @@ -0,0 +1,14 @@ +[Unit] +Description=Doraemon httpd instance +Documentation=https://github.com/bglug-it/doraemon +After=network.target remote-fs.target nss-lookup.target + +[Service] +Type=simple +EnvironmentFile=/etc/sysconfig/doraemon +ExecStart=/usr/sbin/httpd -f /etc/httpd/doraemon/httpd.conf -c 'MaxConnectionsPerChild 12' -DFOREGROUND +ExecReload=/usr/sbin/httpd -f /etc/httpd/doraemon/httpd.conf -k graceful +LimitNOFILE=4096 + +[Install] +WantedBy=multi-user.target diff --git a/root/usr/sbin/doraemon b/root/usr/sbin/doraemon new file mode 120000 index 0000000..558a907 --- /dev/null +++ b/root/usr/sbin/doraemon @@ -0,0 +1 @@ +httpd \ No newline at end of file