diff options
author | Martin Abente <martin.abente.lahaye@gmail.com> | 2011-01-18 19:13:20 (GMT) |
---|---|---|
committer | Martin Abente <martin.abente.lahaye@gmail.com> | 2011-01-18 19:13:20 (GMT) |
commit | 9e96098f0ef7a9da0ef7f95660ed97e77d6ecaa1 (patch) | |
tree | d754887aee98772f86776d72c1b5f189bc2cff04 |
Initial commit
-rw-r--r-- | README | 9 | ||||
-rw-r--r-- | config.ini | 10 | ||||
-rw-r--r-- | daemon.py | 144 | ||||
-rwxr-xr-x | fbdaemon.py | 45 | ||||
-rw-r--r-- | fbserver.py | 114 | ||||
-rw-r--r-- | fbserver.spec | 55 | ||||
-rw-r--r-- | fdserverd | 51 |
7 files changed, 428 insertions, 0 deletions
@@ -0,0 +1,9 @@ +INSTALLATION: +1. Create private key and certificate: + openssl req -new -x509 -days 99999 -nodes -out cert.pem -keyout pkey.pem + +2. Modify config.ini and complete information (USE ABSOLUTE PATHS). + vim config.ini + +USAGE: + /etc/init.d/fbserverd start diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..2f0ff7c --- /dev/null +++ b/config.ini @@ -0,0 +1,10 @@ +[server] +host: localhost +port: 8080 + +[ssl] +pkey_file: /home/tch/Devel/feedback/pkey-cert.pem +cert_file: /home/tch/Devel/feedback/pkey-cert.pem + +[feedback] +reports_path: /home/tch/Devel/feedback/reports/ diff --git a/daemon.py b/daemon.py new file mode 100644 index 0000000..f9cdb91 --- /dev/null +++ b/daemon.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python + +# This source code is based on Sander Marechal's examples provided at +# http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ + +import sys, os, time, atexit +from signal import SIGTERM + +class Daemon: + """ + A generic daemon class. + + Usage: subclass the Daemon class and override the run() method + """ + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + self.stdin = stdin + self.stdout = stdout + self.stderr = stderr + self.pidfile = pidfile + + def daemonize(self): + """ + do the UNIX double-fork magic, see Stevens' "Advanced + Programming in the UNIX Environment" for details (ISBN 0201563177) + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + """ + try: + pid = os.fork() + if pid > 0: + # exit first parent + sys.exit(0) + except OSError, e: + sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent + sys.exit(0) + except OSError, e: + sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) + sys.exit(1) + + # redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = file(self.stdin, 'r') + so = file(self.stdout, 'a+') + se = file(self.stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # write pidfile + atexit.register(self.delpid) + pid = str(os.getpid()) + file(self.pidfile,'w+').write("%s\n" % pid) + + def delpid(self): + os.remove(self.pidfile) + + def start(self): + """ + Start the daemon + """ + if self.running(): + message = "pidfile %s already exist. Daemon already running?\n" + sys.stderr.write(message % self.pidfile) + sys.exit(1) + + # Start the daemon + self.daemonize() + self.run() + + def status(self): + pid = self.running() + if pid: + message = "Running with pid %s\n" % pid + else: + message = "Is not running.\n" + + sys.stderr.write(message) + + def running(self): + # Check for a pidfile to see if the daemon already runs + try: + pf = file(self.pidfile,'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + return pid + + def stop(self): + """ + Stop the daemon + """ + # Get the pid from the pidfile + try: + pf = file(self.pidfile,'r') + pid = int(pf.read().strip()) + pf.close() + except IOError: + pid = None + + if not pid: + message = "pidfile %s does not exist. Daemon not running?\n" + sys.stderr.write(message % self.pidfile) + return # not an error in a restart + + # Try killing the daemon process + try: + while 1: + os.kill(pid, SIGTERM) + time.sleep(0.1) + except OSError, err: + err = str(err) + if err.find("No such process") > 0: + if os.path.exists(self.pidfile): + os.remove(self.pidfile) + else: + print str(err) + sys.exit(1) + + def restart(self): + """ + Restart the daemon + """ + self.stop() + self.start() + + def run(self): + """ + You should override this method when you subclass Daemon. It will be called after the process has been + daemonized by start() or restart(). + """ diff --git a/fbdaemon.py b/fbdaemon.py new file mode 100755 index 0000000..7e96177 --- /dev/null +++ b/fbdaemon.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# Copyright (C) 2011, Martin Abente +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see <http://www.gnu.org/licenses/> + +# This source code is based on Sander Marechal's examples provided at +# http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ + +import sys +import fbserver +from daemon import Daemon + +class FeedbackDaemon(Daemon): + def run(self): + fbserver.listen() + +if __name__ == "__main__": + daemon = FeedbackDaemon('/tmp/fbdaemon.pid') + if len(sys.argv) == 2: + if 'start' == sys.argv[1]: + daemon.start() + elif 'stop' == sys.argv[1]: + daemon.stop() + elif 'restart' == sys.argv[1]: + daemon.restart() + elif 'status' == sys.argv[1]: + daemon.status() + else: + print "Unknown command" + sys.exit(2) + sys.exit(0) + else: + print "usage: %s start|stop|restart" % sys.argv[0] + sys.exit(2) diff --git a/fbserver.py b/fbserver.py new file mode 100644 index 0000000..3a9ce60 --- /dev/null +++ b/fbserver.py @@ -0,0 +1,114 @@ +# Copyright (C) 2011, Aleksey Lim +# Copyright (C) 2011, Martin Abente +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + +import os +import cgi +import time +import socket +import logging +import tarfile +import threading +from ConfigParser import ConfigParser +from cStringIO import StringIO +from SocketServer import ThreadingMixIn, TCPServer, BaseServer +from SimpleHTTPServer import SimpleHTTPRequestHandler +from OpenSSL import SSL + +config = ConfigParser() +config.read('config.ini') + +HOST = config.get('server', 'host') +PORT = config.getint('server', 'port') +PKEY_FILE = config.get('ssl', 'pkey_file') +CERT_FILE = config.get('ssl', 'cert_file') +REPORTS_PATH = config.get('feedback', 'reports_path') + +class RequestHandler(SimpleHTTPRequestHandler): + + def setup(self): + self.connection = self.request + self.rfile = socket._fileobject(self.request, 'rb', self.rbufsize) + self.wfile = socket._fileobject(self.request, 'wb', self.wbufsize) + + def do_GET(self): + self._reply(403, 'Only POST requests are allowed') + + def do_POST(self): + ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) + + if ctype != 'multipart/form-data': + self._reply(403, 'Only multipart/form-data is accepted') + return + + body = cgi.parse_multipart(self.rfile, pdict) + tar_content = body.get('report') + if not tar_content: + self._reply(403, 'No "report" form parameter') + return + + try: + tar = tarfile.open(fileobj=StringIO(tar_content[0]), mode='r:gz') + report = tar.extractfile('report').read() + self._save_report(report) + self._reply(200, 'Report was accepted') + except Exception: + logging.exception('Cannot process request') + self._reply(403, 'Cannot process request') + + def _reply(self, code, message): + self.send_response(code) + self.send_header('Content-type', 'text/html') + self.send_header('Content-length', len(message)) + self.end_headers() + + self.wfile.write(message) + + self.wfile.flush() + self.connection.shutdown() + + def _save_report(self, report): + report_path = self._unique_path() + open(report_path, 'w').write(report) + + def _unique_path(self): + base_name = time.strftime('%Y-%m-%d-%s') + counter = 0 + while True: + attempt = '%s-%s' % (base_name, str(counter)) + path = os.path.join(REPORTS_PATH, attempt) + if not os.path.exists(path): + return path + counter += 1 + +class Server(ThreadingMixIn, TCPServer): + + def __init__(self, server_address, HandlerClass): + BaseServer.__init__(self, server_address, HandlerClass) + + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(PKEY_FILE) + ctx.use_certificate_file(CERT_FILE) + self.socket = SSL.Connection(ctx, + socket.socket(self.address_family, self.socket_type)) + + self.allow_reuse_address = True + self.server_bind() + self.server_activate() + +def listen(): + srv = Server((HOST, PORT), RequestHandler) + srv_thread = threading.Thread(target=srv.serve_forever) + srv_thread.start() diff --git a/fbserver.spec b/fbserver.spec new file mode 100644 index 0000000..ae352d3 --- /dev/null +++ b/fbserver.spec @@ -0,0 +1,55 @@ +Name: sugar-fbserver +Version: 0.1 +Release: 1 +Vendor: Activity Central +Summary: Sugar debugging feedback server +Group: Applications/Internet +License: GPL +URL: http://git.sugarlabs.org +Source0: %{name}-%{version}.tar.gz +BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) +Requires: python +BuildArch: noarch + +%description +Recieves debugging information from sugar clients. + +%prep +%setup -q + +%build +%install +rm -rf $RPM_BUILD_ROOT + +mkdir -p $RPM_BUILD_ROOT/opt/%{name} +cp -r * $RPM_BUILD_ROOT/opt/%{name} + +mkdir -p $RPM_BUILD_ROOT/etc/init.d/ +cp fbserverd $RPM_BUILD_ROOT/etc/init.d/ + +# kill packaging +rm $RPM_BUILD_ROOT/opt/%{name}/fbserver.spec +rm $RPM_BUILD_ROOT/opt/%{name}/fbserverd + +%clean +rm -rf $RPM_BUILD_ROOT + +%post +chkconfig --level 345 fbserverd on + +%preun +chkconfig --level 345 fbserverd off + +%postun + +%files +%defattr(-,root,root,-) +%dir /opt/%{name} +/opt/%{name}/ +%attr(755,root,root) /etc/init.d/fbserverd + +%changelog + +* Tue Jan 18 2011 Martin Abente. <mabente@paraguayeduca.org> +- Initial project + diff --git a/fdserverd b/fdserverd new file mode 100644 index 0000000..2a0082d --- /dev/null +++ b/fdserverd @@ -0,0 +1,51 @@ +#!/bin/bash +# feedback server daemon launcher +# +# chkconfig: - 91 24 +# description: Feedback server daemon +# pidfile: /opt/fbserver/fbdaemon.pid + +# Source function library. +. /etc/init.d/functions + +start() { + echo "Starting feedback server: " + /opt/fbserver/fbdaemon.py start + return 0 +} + +stop() { + echo "Stopping feedback server: " + /opt/fbserver/fbdaemon.py stop + return 0 +} + +status() { + /opt/fbserver/fbdaemon.py status + return 0 +} + +restart() { + stop + start +} + +case "$1" in + start) + start + ;; + status) + status + ;; + stop) + stop + ;; + restart|reload) + restart + ;; + *) + echo "*** Usage: {start|stop|restart}" + exit 1 +esac + +exit $? |