diff --git a/install.sh b/install.sh
index 0f84dc8..61525ec 100755
--- a/install.sh
+++ b/install.sh
@@ -131,12 +131,18 @@ then
fi
fi
-echo "Select installation type:"
+echo "Select installation type: (default: [1] Complete installation)"
echo "[1] Complete installation (manager, webinterface, worker)"
echo "[2] Management installation (manager, webinterface)"
echo "[3] Worker installation (worker only)"
+echo "Note: When there is a default option available pressing enter will select it automatically."
read INSTALL_TYPE
+if [ -z "$INSTALL_TYPE" ]
+then
+ INSTALL_TYPE="1"
+ echo "Performing complete installation"
+fi
check_requirements $INSTALL_TYPE
@@ -228,10 +234,16 @@ then
echo "Setting password to default value"
fi
- echo "Should we take care of rabbitmq.config file? [yes/no]"
+ echo "Should we take care of rabbitmq.config file? [yes/no] (default: yes)"
echo "Select no if you already have an existing rabbitmq setup."
read CREATE_RMQ_CONFIG
+ if [ -z "$CREATE_RMQ_CONFIG" ]
+ then
+ CREATE_RMQ_CONFIG="yes"
+ echo "Creating default rabbitmq configuration file"
+ fi
+
if [ "$CREATE_RMQ_CONFIG" = "yes" ] || [ "$CREATE_RMQ_CONFIG" = "y" ];
then
if [ ! -d /etc/rabbitmq ];
@@ -295,7 +307,7 @@ then
cp scripts/renew_cert.sh $SECPI_PATH
# generate ca cert
- openssl req -config $CERT_PATH/ca/openssl.cnf -x509 -newkey rsa:2048 -days 365 -out $CERT_PATH/ca/cacert.pem -keyout $CERT_PATH/ca/private/cakey.pem -outform PEM -subj /CN=$CA_DOMAIN/ -nodes
+ openssl req -config $CERT_PATH/ca/openssl.cnf -x509 -newkey rsa:2048 -days 3650 -out $CERT_PATH/ca/cacert.pem -keyout $CERT_PATH/ca/private/cakey.pem -outform PEM -subj /CN=$CA_DOMAIN/ -nodes
# secure ca key
chmod 600 $CERT_PATH/ca/private
diff --git a/manager/dropbox_dropper.py b/manager/dropbox_dropper.py
index e2b55ef..278c9bb 100644
--- a/manager/dropbox_dropper.py
+++ b/manager/dropbox_dropper.py
@@ -36,8 +36,9 @@ def notify(self, info):
#self.dbx.files_create_folder(dropbox_dir) # shouldn't be necessary, automatically created
for file in os.listdir(latest_subdir):
if os.path.isfile("%s/%s" % (latest_subdir, file)):
- f = open("%s/%s" % (latest_subdir, file), "rb")
- data = f.read()
+ with open("%s/%s" % (latest_subdir, file), "rb") as f:
+ data = f.read()
+
try:
logging.info("Dropbox: Trying to upload file %s to %s" % (file, dropbox_dir))
res = self.dbx.files_upload(data, "%s/%s" % (dropbox_dir, file))
@@ -46,7 +47,6 @@ def notify(self, info):
logging.error("Dropbox: API error: %s" % d)
except Exception as e: # currently this catches wrong authorization, we should change this
logging.error("Dropbox: Wasn't able to upload file: %s" % e)
- f.close()
else:
logging.error("Dropbox: Wasn't able to notify because there was an initialization error")
diff --git a/manager/mailer.py b/manager/mailer.py
index 4a13510..32ea287 100644
--- a/manager/mailer.py
+++ b/manager/mailer.py
@@ -1,11 +1,15 @@
-import logging
import os
+import logging
import smtplib
-
-from email.mime.application import MIMEApplication
+import mimetypes
from email.mime.multipart import MIMEMultipart
+from email.mime.nonmultipart import MIMENonMultipart
+from email.mime.application import MIMEApplication
from email.mime.text import MIMEText
+from email.mime.image import MIMEImage
+from email.mime.audio import MIMEAudio
from tools.notifier import Notifier
+from tools.utils import str_to_value
class Mailer(Notifier):
@@ -20,6 +24,7 @@ def __init__(self, id, params):
self.smtp_user = params["smtp_user"]
self.smtp_pass = params["smtp_pass"]
self.smtp_security = params["smtp_security"]
+ self.unzip_attachments = str_to_value(params.get("unzip_attachments", False))
except KeyError as ke: # if config parameters are missing
logging.error("Mailer: Wasn't able to initialize the notifier, it seems there is a config parameter missing: %s" % ke)
self.corrupted = True
@@ -38,7 +43,7 @@ def notify(self, info):
self.message["From"] = self.params["sender"]
self.message["To"] = self.params["recipient"]
self.message["Subject"] = self.params.get("subject", "SecPi Alarm")
- self.message.attach(MIMEText(self.params.get("text", "Your SecPi raised an alarm. Please check the attached files."), "plain"))
+ self.message.attach(MIMEText(self.params.get("text", "Your SecPi raised an alarm. Please check the attached files."), "plain", 'utf-8'))
info_str = "Recieved alarm on sensor %s from worker %s: %s"%(info['sensor'], info['worker'], info['message'])
self.message.attach(MIMEText(info_str, "plain"))
@@ -73,18 +78,65 @@ def prepare_mail_attachments(self):
logging.debug("Mailer: Will look into %s for data" % latest_subdir)
#then iterate through it and attach all the files to the mail
for file in os.listdir(latest_subdir):
+ filepath = "%s/%s" % (latest_subdir, file)
# check if it really is a file
- if os.path.isfile("%s/%s" % (latest_subdir, file)):
- f = open("%s/%s" % (latest_subdir, file), "rb")
- att = MIMEApplication(f.read())
- att.add_header('Content-Disposition','attachment; filename="%s"' % file)
- f.close()
- self.message.attach(att)
- logging.debug("Mailer: Attached file '%s' to message" % file)
+ if os.path.isfile(filepath):
+
+ # Add each file in zipfile as separate attachment
+ if self.unzip_attachments and file.endswith('.zip'):
+ self.prepare_expand_zip_attachment(filepath)
+
+ # Add file as a whole (default)
+ else:
+ with open(filepath, "rb") as f:
+ self.prepare_add_attachment(file, f.read())
+
else:
logging.debug("Mailer: %s is not a file" % file)
# TODO: maybe log something if there are no files?
+ def prepare_add_attachment(self, filename, payload):
+ """Add single attachment to current mail message"""
+
+ # Determine content type
+ ctype, encoding = mimetypes.guess_type(filename, strict=False)
+ maintype, subtype = ctype.split('/', 1)
+
+ # Create proper MIME part by maintype
+ if maintype == 'application' and subtype in ['xml', 'json']:
+ mimepart = MIMENonMultipart(maintype, subtype, charset='utf-8')
+ mimepart.set_payload(payload.encode('utf-8'), 'utf-8')
+
+ elif maintype == 'text':
+ mimepart = MIMEText(payload.encode('utf-8'), _subtype=subtype, _charset='utf-8')
+
+ elif maintype == 'image':
+ mimepart = MIMEImage(payload, _subtype=subtype)
+
+ elif maintype == 'audio':
+ mimepart = MIMEAudio(payload, _subtype=subtype)
+
+ else:
+ # Encode the payload using Base64 (Content-Transfer-Encoding)
+ mimepart = MIMEApplication(payload)
+
+ # Attach MIME part to message
+ mimepart.add_header('Content-Disposition','attachment; filename="%s"' % filename)
+ self.message.attach(mimepart)
+
+ logging.debug("Mailer: Attached file '%s' to message" % filename)
+
+ def prepare_expand_zip_attachment(self, filepath):
+ """Decode zip file and add each containing file as attachment to current mail message"""
+ logging.debug("Mailer: Decoding zip file '%s' as requested" % filepath)
+ import zipfile
+ with zipfile.ZipFile(filepath) as zip:
+ filenames = zip.namelist()
+
+ for filename in filenames:
+ payload = zip.read(filename)
+ self.prepare_add_attachment(filename, payload)
+
def send_mail_starttls(self):
logging.debug("Mailer: Trying to send mail with STARTTLS")
try:
diff --git a/manager/manager.py b/manager/manager.py
index dc33e08..94ebb36 100644
--- a/manager/manager.py
+++ b/manager/manager.py
@@ -108,7 +108,10 @@ def connect(self):
connected = True
logging.info("Connection to rabbitmq service established")
except pika.exceptions.AMQPConnectionError as pe: # if connection can't be established
- logging.error("Wasn't able to connect to rabbitmq service: %s" % pe)
+ if "The AMQP connection was closed" in repr(pe):
+ logging.error("Wasn't able to connect to the rabbitmq service, please check if the rabbitmq service is reachable and running")
+ else:
+ logging.error("Wasn't able to connect to the rabbitmq service: %s" % repr(pe))
time.sleep(30)
#define exchange
@@ -239,9 +242,9 @@ def got_data(self, ch, method, properties, body):
newFile_bytes = bytearray(body)
if newFile_bytes: #only write data when body is not empty
try:
- newFile = open("%s/%s.zip" % (self.current_alarm_dir, hashlib.md5(newFile_bytes).hexdigest()), "wb")
- newFile.write(newFile_bytes)
- logging.info("Data written")
+ with open("%s/%s.zip" % (self.current_alarm_dir, hashlib.md5(newFile_bytes).hexdigest()), "wb") as newFile:
+ newFile.write(newFile_bytes)
+ logging.info("Data written")
except IOError as ie: # File can't be written, e.g. permissions wrong, directory doesn't exist
logging.exception("Wasn't able to write received data: %s" % ie)
self.received_data_counter += 1
@@ -356,7 +359,7 @@ def setup_notifiers(self):
# timeout thread which sends the received data from workers
def notify(self, info):
- for i in range(0, self.data_timeout):
+ for i in xrange(0, self.data_timeout):
if self.received_data_counter < self.num_of_workers: #not all data here yet
logging.debug("Waiting for data from workers: data counter: %d, #workers: %d" % (self.received_data_counter, self.num_of_workers))
time.sleep(1)
@@ -377,7 +380,7 @@ def notify(self, info):
# go into holddown state, while in this state subsequent alarms are interpreted as one alarm
def holddown(self):
self.holddown_state = True
- for i in range(0, self.holddown_timer):
+ for i in xrange(0, self.holddown_timer):
time.sleep(1)
logging.debug("Holddown is over")
self.holddown_state = False
diff --git a/manager/slack.py b/manager/slack.py
new file mode 100644
index 0000000..aed1e7b
--- /dev/null
+++ b/manager/slack.py
@@ -0,0 +1,55 @@
+import logging
+
+from slacker import Slacker
+
+from tools.notifier import Notifier
+
+class SlackNotifier(Notifier):
+
+ def __init__(self, id, params):
+ super(SlackNotifier, self).__init__(id, params)
+ if(not 'bot_token' in params or not 'channel' in params):
+ self.corrupted = True
+ logging.error("Slack: Token or channel name missing!")
+ return
+
+
+ def notify(self, info):
+ if(not self.corrupted):
+ try:
+ logging.debug("Sending Slack notification!")
+
+ channel_name = self.params['channel']
+ slack = Slacker(self.params['bot_token'])
+
+ # groups are private chats
+ channels = slack.groups.list(1) # 1 --> exclude archived
+ channel = [c for c in channels.body['groups'] if c['name'] == channel_name]
+
+ if(len(channel)>0):
+ # we got a channel
+ channel_id = channel[0]['id']
+ logging.debug("Got existing channel!")
+ else:
+ # if not exists, create it
+ logging.debug("No channel found, creating one!")
+ new_channel_req = slack.groups.create(channel_name)
+
+ channel_id = new_channel_req.body['group']['id']
+
+ if(channel_id!=None):
+ logging.debug("Found channel: %s"%channel_id)
+ info_str = "Recieved alarm on sensor %s from worker %s: %s"%(info['sensor'], info['worker'], info['message'])
+ slack.chat.post_message(channel_name, info_str)
+
+ else:
+ logging.error("No channel found!")
+ except KeyError as ke:
+ logging.error("Error in Slack Notifier: %s"%ke)
+
+ else:
+ logging.error("Slack Notifier corrupted! No token or channel name given as parameter.")
+
+ def cleanup(self):
+ logging.debug("Slack Notifier: No cleanup necessary at the moment.")
+
\ No newline at end of file
diff --git a/manager/spark.py b/manager/spark.py
new file mode 100644
index 0000000..311cd56
--- /dev/null
+++ b/manager/spark.py
@@ -0,0 +1,59 @@
+import logging
+import requests
+import requests.exceptions
+
+from tools.notifier import Notifier
+
+class SparkNotifier(Notifier):
+
+ def __init__(self, id, params):
+ super(SparkNotifier, self).__init__(id, params)
+ if(not 'personal_token' in params or not 'room' in params):
+ self.corrupted = True
+ logging.error("Token or room name missing!")
+ return
+
+
+ def notify(self, info):
+ if(not self.corrupted):
+ try:
+ logging.debug("Sending Cisco Spark notification!")
+
+ room_name = self.params['room']
+ auth_header = {'Authorization': 'Bearer %s'%self.params['personal_token']}
+
+ # get room id
+ rooms_req = requests.get('https://api.ciscospark.com/v1/rooms', headers=auth_header)
+ rooms = rooms_req.json()
+ room = [r for r in rooms['items'] if r['title'] == room_name]
+
+ if(len(room)>0):
+ # we got a room
+ room_id = room[0]['id']
+ logging.debug("Got existing room!")
+ else:
+ # if not exists, create it
+ logging.debug("No room found, creating one!")
+ new_room_req = requests.post('https://api.ciscospark.com/v1/rooms', headers=auth_header, data={'title': room_name})
+ new_room = new_room_req.json()
+
+ room_id = new_room['id']
+
+ if(room_id!=None):
+ logging.debug("Found room: %s"%room_id)
+ info_str = "Recieved alarm on sensor %s from worker %s: %s"%(info['sensor'], info['worker'], info['message'])
+ noti_req = requests.post('https://api.ciscospark.com/v1/messages', headers=auth_header, data={'roomId': room_id, 'text': info_str})
+
+ else:
+ logging.error("No room found!")
+ except RequestException as ce:
+ logging.error("Error in Spark Notifier: %s"%ce)
+ except KeyError as ke:
+ logging.error("Error in Spark Notifier: %s"%ke)
+
+ else:
+ logging.error("Cisco Spark Notifier corrupted! No authorization code or room name given as parameter.")
+
+ def cleanup(self):
+ logging.debug("Cisco Spark Notifier: No cleanup necessary at the moment.")
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index a103413..869d794 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,4 +9,6 @@ tweepy
dropbox
logging
netifaces
-python-gsmmodem
\ No newline at end of file
+python-gsmmodem
+slacker
+pyping
\ No newline at end of file
diff --git a/stuff/secpi-logo.ai b/stuff/secpi-logo.ai
new file mode 100644
index 0000000..736208c
--- /dev/null
+++ b/stuff/secpi-logo.ai
@@ -0,0 +1,1624 @@
+%PDF-1.5
%
+1 0 obj
<>/OCGs[5 0 R 6 0 R 26 0 R 27 0 R 47 0 R 48 0 R 49 0 R 84 0 R 85 0 R 86 0 R 121 0 R 122 0 R 123 0 R 124 0 R 161 0 R 162 0 R 163 0 R 165 0 R 164 0 R 166 0 R 205 0 R 206 0 R 207 0 R 209 0 R 208 0 R 210 0 R 249 0 R 250 0 R 251 0 R 252 0 R 254 0 R 253 0 R 255 0 R 297 0 R 301 0 R 302 0 R 298 0 R 299 0 R 300 0 R 303 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+
+