mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-15 09:04:39 +00:00
333 lines
13 KiB
Python
333 lines
13 KiB
Python
"""
|
|
Copyright (c) 2014 Ad Schellevis
|
|
|
|
part of opnSense (https://www.opnsense.org/)
|
|
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
--------------------------------------------------------------------------------------
|
|
package : check_reload_status
|
|
function: unix domain socket process worker process
|
|
|
|
|
|
"""
|
|
__author__ = 'Ad Schellevis'
|
|
|
|
import os
|
|
import subprocess
|
|
import socket
|
|
import traceback
|
|
import syslog
|
|
import threading
|
|
import ConfigParser
|
|
import glob
|
|
import time
|
|
|
|
class Handler(object):
|
|
""" Main handler class, opens unix domain socket and starts listening
|
|
- New connections are handed over to a HandlerClient type object in a new thread
|
|
- All possible actions are stored in 1 ActionHandler type object and parsed to every client for script execution
|
|
|
|
processflow:
|
|
Handler ( waits for client )
|
|
-> new client is send to HandlerClient
|
|
-> execute ActionHandler command
|
|
<- send back result string
|
|
"""
|
|
def __init__(self,socket_filename,config_path,simulation_mode=False):
|
|
""" Constructor
|
|
|
|
:param socket_filename: filename of unix domain socket to use
|
|
:param config_path: location of configuration files
|
|
:param emulate: emulation mode, do not start actual (script) commands
|
|
:return: object
|
|
"""
|
|
self.socket_filename = socket_filename
|
|
self.config_path = config_path
|
|
self.simulation_mode =simulation_mode
|
|
|
|
def run(self):
|
|
""" Run process handler
|
|
|
|
:return:
|
|
"""
|
|
while True:
|
|
try:
|
|
# open action handler
|
|
actHandler = ActionHandler(config_path=self.config_path)
|
|
|
|
# remove previous socket ( if exists )
|
|
try:
|
|
os.unlink(self.socket_filename)
|
|
except OSError:
|
|
if os.path.exists(self.socket_filename):
|
|
raise
|
|
|
|
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
sock.bind(self.socket_filename)
|
|
os.chmod(self.socket_filename,0o666)
|
|
sock.listen(30)
|
|
while True:
|
|
# wait for a connection to arrive
|
|
connection, client_address = sock.accept()
|
|
# spawn a client connection
|
|
cmd_thread = HandlerClient(connection=connection,
|
|
client_address=client_address,
|
|
action_handler=actHandler,
|
|
simulation_mode=self.simulation_mode)
|
|
cmd_thread.start()
|
|
except KeyboardInterrupt:
|
|
# exit on <ctrl><c>
|
|
raise
|
|
except:
|
|
# something went wrong... send traceback to syslog, restart listener (wait for a short time)
|
|
print (traceback.format_exc())
|
|
syslog.syslog(syslog.LOG_ERR, 'Handler died on %s'%traceback.format_exc())
|
|
time.sleep(1)
|
|
|
|
|
|
class HandlerClient(threading.Thread):
|
|
""" Handle commands via specified socket connection
|
|
"""
|
|
def __init__ (self,connection,client_address,action_handler,simulation_mode=False):
|
|
"""
|
|
|
|
:param connection: socket connection object
|
|
:param client_address: client address ( from socket accept )
|
|
:param action_handler: action handler object
|
|
:param emulate: Emulation mode, do not start actual (script) commands
|
|
:return: None
|
|
"""
|
|
threading.Thread.__init__(self)
|
|
self.connection = connection
|
|
self.client_address = client_address
|
|
self.action_handler = action_handler
|
|
self.simulation_mode = simulation_mode
|
|
|
|
def run(self):
|
|
""" handle single action ( read data, execute command, send response )
|
|
|
|
:return: None
|
|
"""
|
|
try:
|
|
# receive command, maximum data length is 4k... longer messages will be truncated
|
|
data = self.connection.recv(4096)
|
|
# map command to action
|
|
data_parts = data.strip().split(' ')
|
|
if len(data_parts) == 0 or len(data_parts[0]) == 0:
|
|
# no data found
|
|
self.connection.sendall('no data\n')
|
|
else:
|
|
exec_command = data_parts[0]
|
|
if len(data_parts) > 1:
|
|
exec_action = data_parts[1]
|
|
else:
|
|
exec_action = None
|
|
if len(data_parts) >2:
|
|
exec_params = data_parts[2:]
|
|
else:
|
|
exec_params = None
|
|
|
|
# execute requested action
|
|
if self.simulation_mode:
|
|
self.action_handler.showAction(exec_command,exec_action,exec_params)
|
|
result='OK'
|
|
else:
|
|
result = self.action_handler.execute(exec_command,exec_action,exec_params)
|
|
|
|
# send response back to client( including trailing enter )
|
|
self.connection.sendall('%s\n'%result)
|
|
except:
|
|
print (traceback.format_exc())
|
|
syslog.syslog(syslog.LOG_ERR,'unable to sendback response, message was %s'%traceback.format_exc())
|
|
finally:
|
|
self.connection.close()
|
|
|
|
class Action(object):
|
|
""" Action class, handles actual system calls. set command, parameters (template) type and log message
|
|
"""
|
|
def __init__(self):
|
|
""" setup default properties
|
|
|
|
:return:
|
|
"""
|
|
self.command = None
|
|
self.parameters = None
|
|
self.type = None
|
|
self.message = None
|
|
self._parameter_start_pos = 0
|
|
|
|
def setParameterStartPos(self,pos):
|
|
"""
|
|
|
|
:param pos: start position of parameter list
|
|
:return: position
|
|
"""
|
|
self._parameter_start_pos = pos
|
|
|
|
def getParameterStartPos(self):
|
|
""" getter for _parameter_start_pos
|
|
:return: start position of parameter list ( first argument can be part of action to start )
|
|
"""
|
|
return self._parameter_start_pos
|
|
|
|
def execute(self,parameters):
|
|
""" execute an action
|
|
|
|
:param parameters: list of parameters
|
|
:return:
|
|
"""
|
|
# validate input
|
|
if self.type == None:
|
|
return 'No action type'
|
|
elif self.type.lower() == 'script':
|
|
if self.command == None:
|
|
return 'No command'
|
|
|
|
try:
|
|
script_command = self.command
|
|
if self.parameters != None and type(self.parameters) == str:
|
|
script_command = '%s %s'%(script_command,self.parameters)
|
|
|
|
if script_command.find('%s') > -1 and len(parameters) > 0:
|
|
# use command execution parameters in action parameter template
|
|
script_command = script_command % tuple(parameters[0:script_command.count('%s')])
|
|
|
|
# execute script command
|
|
if self.message != None:
|
|
if self.message.count('%s') > 0 and parameters != None and len(parameters) > 0:
|
|
syslog.syslog(syslog.LOG_NOTICE,self.message % tuple(parameters[0:self.message.count('%s')]) )
|
|
else:
|
|
syslog.syslog(syslog.LOG_NOTICE,self.message)
|
|
|
|
exit_status = subprocess.call(script_command, shell=True)
|
|
except:
|
|
print traceback.format_exc()
|
|
# todo log traceback on exception
|
|
return 'Execute error'
|
|
|
|
# send response
|
|
if exit_status == 0 :
|
|
return 'OK'
|
|
else:
|
|
return 'Error (%d)'%exit_status
|
|
|
|
return 'Unknown action type'
|
|
|
|
class ActionHandler(object):
|
|
""" Start/stop services and functions using configuration data definced in conf/actions_<topic>.conf
|
|
"""
|
|
def __init__(self,config_path):
|
|
""" Initialize action handler to start system functions
|
|
|
|
:param config_path: full path of configuration data
|
|
:return:
|
|
"""
|
|
self.config_path = config_path
|
|
self.action_map = {}
|
|
self.load_config()
|
|
|
|
|
|
def load_config(self):
|
|
""" load action configuration from config files into local dictionary
|
|
|
|
:return: None
|
|
"""
|
|
|
|
self.action_map = {}
|
|
for config_filename in glob.glob('%s/actions_*.conf'%(self.config_path)):
|
|
# traverse config directory and open all filenames starting with actions_
|
|
cnf=ConfigParser.RawConfigParser()
|
|
cnf.read(config_filename)
|
|
topic_map = {}
|
|
for section in cnf.sections():
|
|
# map configuration data on object
|
|
action_obj = Action()
|
|
for act_prop in cnf.items(section):
|
|
setattr(action_obj,act_prop[0],act_prop[1])
|
|
|
|
if section.find('.') > -1:
|
|
# at this moment we only support 2 levels of actions ( 3 if you count topic as well )
|
|
for alias in section.split('.')[0].split('|'):
|
|
if topic_map.has_key(alias) == False:
|
|
topic_map[alias] = {}
|
|
topic_map[alias][section.split('.')[1]] = action_obj
|
|
else:
|
|
for alias in section.split('|'):
|
|
topic_map[alias] = action_obj
|
|
|
|
self.action_map[config_filename.split('actions_')[-1].split('.')[0]] = topic_map
|
|
|
|
def findAction(self,command,action,parameters):
|
|
""" find action object
|
|
|
|
:param command: command/topic for example interface
|
|
:param action: action to run ( for example linkup )
|
|
:param parameters: the parameters to supply
|
|
:return: action object or None if not found
|
|
"""
|
|
action_obj = None
|
|
if self.action_map.has_key(command):
|
|
if self.action_map[command].has_key(action):
|
|
if type(self.action_map[command][action]) == dict:
|
|
if len(parameters) > 0 and self.action_map[command][action].has_key(parameters[0]) == True:
|
|
# 3 level action ( "interface linkup start" for example )
|
|
if isinstance(self.action_map[command][action][parameters[0]],Action):
|
|
action_obj = self.action_map[command][action][parameters[0]]
|
|
action_obj.setParameterStartPos(1)
|
|
elif isinstance(self.action_map[command][action],Action):
|
|
action_obj = self.action_map[command][action]
|
|
|
|
return action_obj
|
|
|
|
def execute(self,command,action,parameters):
|
|
""" execute configuration defined action
|
|
|
|
:param command: command/topic for example interface
|
|
:param action: action to run ( for example linkup )
|
|
:param parameters: the parameters to supply
|
|
:return: OK on success, else error code
|
|
"""
|
|
action_params = []
|
|
action_obj = self.findAction(command,action,parameters)
|
|
|
|
if action_obj != None:
|
|
if parameters != None and len(parameters) > action_obj.getParameterStartPos():
|
|
action_params = parameters[action_obj.getParameterStartPos():]
|
|
|
|
return '%s\n'%action_obj.execute(action_params)
|
|
|
|
return 'Action not found\n'
|
|
|
|
|
|
def showAction(self,command,action,parameters):
|
|
""" debug/simulation mode: show action information
|
|
:return:
|
|
"""
|
|
action_obj = self.findAction(command,action,parameters)
|
|
print ('---------------------------------------------------------------------')
|
|
print ('execute %s.%s with parameters : %s '%(command,action,parameters) )
|
|
print ('action object %s (%s)' % (action_obj,action_obj.command) )
|
|
print ('---------------------------------------------------------------------')
|