summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorb1galez <b1galez@fbcee277-3294-991b-8290-beb7048acdd6>2010-12-14 10:40:58 +0000
committerb1galez <b1galez@fbcee277-3294-991b-8290-beb7048acdd6>2010-12-14 10:40:58 +0000
commit14c76e5b936e3c91d88d21e30558d4eee1ab98ef (patch)
tree3c5d639ef203e7d43154f58edeac4172ce084a2f
parentFixed version identification in headers sent to client. (diff)
downloadyubico-yubiserve-14c76e5b936e3c91d88d21e30558d4eee1ab98ef.tar.gz
yubico-yubiserve-14c76e5b936e3c91d88d21e30558d4eee1ab98ef.tar.bz2
yubico-yubiserve-14c76e5b936e3c91d88d21e30558d4eee1ab98ef.zip
3.0 Release, added MySQL support, various fixes.
git-svn-id: http://yubico-yubiserve.googlecode.com/svn/trunk@29 fbcee277-3294-991b-8290-beb7048acdd6
-rw-r--r--README17
-rwxr-xr-xdbconf.py65
-rw-r--r--src/dump.mysql61
-rwxr-xr-x[-rw-r--r--]src/dump.sqlite (renamed from src/dump.sql)0
-rw-r--r--yubikeys.sqlitebin11264 -> 11264 bytes
-rw-r--r--yubiserve.cfg9
-rwxr-xr-xyubiserve.py138
7 files changed, 218 insertions, 72 deletions
diff --git a/README b/README
index efee60f..033211b 100644
--- a/README
+++ b/README
@@ -3,10 +3,11 @@ YubiServe has been written by Alessio Periloso <mail *at* periloso.it>
Version 1.0: 21/05/2010
Version 2.0: 19/11/2010
Version 2.9: 13/12/2010
+Version 3.0: 14/12/2010
== Description ==
This simple service allows to authenticate Yubikeys and OATH Tokens using
-only a small sqlite database.
+only a small sqlite database (the mysql support is optional!)
The code has been released under GNU license (license into LICENSE file)
The project is divided into two parts:
@@ -17,12 +18,20 @@ The project is divided into two parts:
== Installation ==
Installation is pretty simple, you just have to install few python packages:
Under Debian, you can run:
-apt-get install python python-sqlite python-crypto python-openssl
+apt-get install python python-crypto python-openssl
+If you want to add the sqlite support, you should run:
+apt-get install python-sqlite
+Or, if you want to add the mysql support, you should run:
+apt-get install python-mysqldb
+If you chosen the mysql support, you must create a database and create the
+tables. The mysql dump is at src/dump.mysql.
Then, you have to generate the certificate for ssl validation, so if you don't
already have a certificate you have to issue the following command to self-sign
one:
openssl req -new -x509 -keyout yubiserve.pem -out yubiserve.pem -days 365 -nodes
-That's all, yes!
+
+A good idea would be taking a look at yubiserve.cfg, to configure the validation server settings.
+
After installing the needed packages, you just need to extract the files
to a directory, add the keys and launch the server (or, if you prefer
you can launch the server before adding the keys, it doesn't matter).
@@ -156,4 +165,4 @@ h=vYoG9Av8uG6OqVkmMFuANi4fyWw=
That's all. Pretty simple, huh?
Of course you can add new keys while the server is already running, without needing it
to restart, and of course multiple queries a time are allowed, that's why the server
-is multithreaded. \ No newline at end of file
+is multithreaded.
diff --git a/dbconf.py b/dbconf.py
index 894efde..4d4ff0f 100755
--- a/dbconf.py
+++ b/dbconf.py
@@ -1,6 +1,28 @@
#!/usr/bin/python
-import sqlite, time, random, re
+import time, random, re, os
from sys import argv
+try:
+ import MySQLdb
+except ImportError:
+ pass
+try:
+ import sqlite
+except ImportError:
+ pass
+
+def parseConfigFile(): # Originally I wrote this function to parse PHP configuration files!
+ config = open(os.path.dirname(os.path.realpath(__file__)) + '/yubiserve.cfg', 'r').read().splitlines()
+ keys = {}
+ for line in config:
+ match = re.search('(.*?)=(.*);', line)
+ try: # Check if it's a string or a number
+ if ((match.group(2).strip()[0] != '"') and (match.group(2).strip()[0] != '\'')):
+ keys[match.group(1).strip()] = int(match.group(2).strip())
+ else:
+ keys[match.group(1).strip()] = match.group(2).strip('"\' ')
+ except:
+ pass
+ return keys
def randomChars(max):
retVal = ''
@@ -14,7 +36,30 @@ def randomChars(max):
retVal += chr(rand+47)
return retVal
-con = sqlite.connect('yubikeys.sqlite')
+config = parseConfigFile()
+try:
+ if MySQLdb != None:
+ isThereMysql = True
+except NameError:
+ isThereMysql = False
+try:
+ if sqlite != None:
+ isThereSqlite = True
+except NameError:
+ isThereSqlite = False
+if isThereMysql == isThereSqlite == False:
+ print "Cannot continue without any database support.\nPlease read README.\n\n"
+ quit()
+if config['yubiDB'] == 'mysql' and (config['yubiMySQLHost'] == '' or config['yubiMySQLUser'] == '' or config['yubiMySQLPass'] == '' or config['yubiMySQLName'] == ''):
+ print "Cannot continue without any MySQL configuration.\nPlease read README.\n\n"
+ quit()
+try:
+ if config['yubiDB'] == 'sqlite':
+ con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
+ elif config['yubiDB'] == 'mysql':
+ con = MySQLdb.connect(host=config['yubiMySQLHost'], user=config['yubiMySQLUser'], passwd=config['yubiMySQLPass'], db=config['yubiMySQLName'])
+except:
+ print "There's a problem with the database!\n"
cur = con.cursor()
if (len(argv)<2):
@@ -43,9 +88,9 @@ else:
if (cur.rowcount == 0):
print 'Key not found.'
else:
- cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = 'true'")
+ cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = '1'")
if (cur.rowcount == 1):
- cur.execute("UPDATE yubikeys SET active = 'false' WHERE nickname = '" + nickname + "'")
+ cur.execute("UPDATE yubikeys SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' disabled."
con.commit()
else:
@@ -57,9 +102,9 @@ else:
if (cur.rowcount == 0):
print 'Key not found.'
else:
- cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = 'false'")
+ cur.execute("SELECT * FROM yubikeys WHERE nickname = '" + nickname + "' AND active = '1'")
if (cur.rowcount == 1):
- cur.execute("UPDATE yubikeys SET active = 'true' WHERE nickname = '" + nickname + "'")
+ cur.execute("UPDATE yubikeys SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' enabled."
con.commit()
else:
@@ -107,9 +152,9 @@ else:
if (cur.rowcount == 0):
print 'Key not found.'
else:
- cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = 'true'")
+ cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = '1'")
if (cur.rowcount == 1):
- cur.execute("UPDATE oathtokens SET active = 'false' WHERE nickname = '" + nickname + "'")
+ cur.execute("UPDATE oathtokens SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' disabled."
con.commit()
else:
@@ -121,9 +166,9 @@ else:
if (cur.rowcount == 0):
print 'Key not found.'
else:
- cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = 'false'")
+ cur.execute("SELECT * FROM oathtokens WHERE nickname = '" + nickname + "' AND active = '1'")
if (cur.rowcount == 1):
- cur.execute("UPDATE oathtokens SET active = 'true' WHERE nickname = '" + nickname + "'")
+ cur.execute("UPDATE oathtokens SET active = '1' WHERE nickname = '" + nickname + "'")
print "Key '" + nickname + "' enabled."
con.commit()
else:
diff --git a/src/dump.mysql b/src/dump.mysql
new file mode 100644
index 0000000..f095a25
--- /dev/null
+++ b/src/dump.mysql
@@ -0,0 +1,61 @@
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+
+--
+-- Database: `yubikeys`
+--
+
+-- --------------------------------------------------------
+
+--
+-- Table `apikeys`
+--
+
+CREATE TABLE IF NOT EXISTS `apikeys` (
+ `nickname` varchar(16) default NULL,
+ `secret` varchar(28) default NULL,
+ `id` int(11) NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table `oathtokens`
+--
+
+CREATE TABLE IF NOT EXISTS `oathtokens` (
+ `nickname` varchar(16) NOT NULL,
+ `publicname` varchar(12) NOT NULL,
+ `created` varchar(24) NOT NULL,
+ `secret` varchar(40) NOT NULL,
+ `active` tinyint(1) default '1',
+ `counter` int(11) NOT NULL default '1',
+ UNIQUE KEY `nickname` (`nickname`),
+ UNIQUE KEY `publicname` (`publicname`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table `yubikeys`
+--
+
+CREATE TABLE IF NOT EXISTS `yubikeys` (
+ `nickname` varchar(16) NOT NULL,
+ `publicname` varchar(16) NOT NULL,
+ `created` varchar(24) NOT NULL,
+ `internalname` varchar(12) NOT NULL,
+ `aeskey` varchar(32) NOT NULL,
+ `active` tinyint(1) default '1',
+ `counter` int(11) NOT NULL default '1',
+ `time` int(11) NOT NULL default '1',
+ UNIQUE KEY `nickname` (`nickname`),
+ UNIQUE KEY `publicname` (`publicname`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
diff --git a/src/dump.sql b/src/dump.sqlite
index 7cb972e..7cb972e 100644..100755
--- a/src/dump.sql
+++ b/src/dump.sqlite
diff --git a/yubikeys.sqlite b/yubikeys.sqlite
index 0d4f75c..45334b5 100644
--- a/yubikeys.sqlite
+++ b/yubikeys.sqlite
Binary files differ
diff --git a/yubiserve.cfg b/yubiserve.cfg
new file mode 100644
index 0000000..06993bf
--- /dev/null
+++ b/yubiserve.cfg
@@ -0,0 +1,9 @@
+yubiservePORT = 8000;
+yubiserveSSLPORT = 8001;
+yubiserveHOST = '0.0.0.0';
+yubiDB = 'sqlite';
+#yubiDB = 'mysql';
+yubiMySQLHost = 'localhost';
+yubiMySQLUser = 'yubiserve';
+yubiMySQLPass = 'yubipass';
+yubiMySQLName = 'yubikeys';
diff --git a/yubiserve.py b/yubiserve.py
index 80bdecd..e77ff63 100755
--- a/yubiserve.py
+++ b/yubiserve.py
@@ -1,20 +1,40 @@
#!/usr/bin/python
-import sqlite, re, os, time, socket
+import re, os, time, socket
import urlparse, SocketServer, urllib, BaseHTTPServer
from Crypto.Cipher import AES
from OpenSSL import SSL
import hmac, hashlib
from threading import Thread
+try:
+ import MySQLdb
+except ImportError:
+ pass
+try:
+ import sqlite
+except ImportError:
+ pass
-yubiservePORT = 8000
-yubiserveSSLPORT = yubiservePORT + 1
-yubiserveHOST = '0.0.0.0' # You can use '127.0.0.1' to avoid
- # the server to receive queries from
- # the outside
+def parseConfigFile(): # Originally I wrote this function to parse PHP configuration files!
+ config = open(os.path.dirname(os.path.realpath(__file__)) + '/yubiserve.cfg', 'r').read().splitlines()
+ keys = {}
+ for line in config:
+ match = re.search('(.*?)=(.*);', line)
+ try: # Check if it's a string or a number
+ if ((match.group(2).strip()[0] != '"') and (match.group(2).strip()[0] != '\'')):
+ keys[match.group(1).strip()] = int(match.group(2).strip())
+ else:
+ keys[match.group(1).strip()] = match.group(2).strip('"\' ')
+ except:
+ pass
+ return keys
+
+config = parseConfigFile()
class OATHValidation():
- status = {'OK': 1, 'BAD_OTP': 2, 'NO_AUTH': 3, 'NO_CLIENT': 5}
- validationResult = 0
+ def __init__(self, connection):
+ self.status = {'OK': 1, 'BAD_OTP': 2, 'NO_AUTH': 3, 'NO_CLIENT': 5}
+ self.validationResult = 0
+ self.con = connection
def testHOTP(self, K, C, digits=6):
counter = ("%x"%C).rjust(16,'0').decode('hex') # Convert it into 8 bytes hex
HS = hmac.new(K, counter, hashlib.sha1).digest()
@@ -23,9 +43,8 @@ class OATHValidation():
bin_code = int((chr(ord(HS[offset]) & 0x7F) + HS[offset+1:offset+4]).encode('hex'),16)
return str(bin_code)[-digits:]
def validateOATH(self, OATH, publicID):
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
- cur.execute("SELECT counter, secret FROM oathtokens WHERE publicname = '" + publicID + "' AND active = 'true'")
+ cur = self.con.cursor()
+ cur.execute("SELECT counter, secret FROM oathtokens WHERE publicname = '" + publicID + "' AND active = '1'")
if (cur.rowcount != 1):
validationResult = self.status['BAD_OTP']
return validationResult
@@ -37,15 +56,16 @@ class OATHValidation():
K = key.decode('hex') # key
for C in range(actualcounter+1, actualcounter+256):
if OATH == self.testHOTP(K, C, len(OATH)):
- cur.execute("UPDATE oathtokens SET counter = " + str(C) + " WHERE publicname = '" + publicID + "' AND active = 'true'")
- con.commit()
+ cur.execute("UPDATE oathtokens SET counter = " + str(C) + " WHERE publicname = '" + publicID + "' AND active = '1'")
+ self.con.commit()
return self.status['OK']
return self.status['NO_AUTH']
class OTPValidation():
- status = {'OK': 1, 'BAD_OTP': 2, 'REPLAYED_OTP': 3, 'DELAYED_OTP': 4, 'NO_CLIENT': 5}
- validationResult = 0
-
+ def __init__(self, connection):
+ self.status = {'OK': 1, 'BAD_OTP': 2, 'REPLAYED_OTP': 3, 'DELAYED_OTP': 4, 'NO_CLIENT': 5}
+ self.validationResult = 0
+ self.con = connection
def hexdec(self, hex):
return int(hex, 16)
def modhex2hex(self, string):
@@ -89,56 +109,55 @@ class OTPValidation():
if match.group(1) and match.group(2):
self.userid = match.group(1)
self.token = self.modhex2hex(match.group(2))
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
- cur.execute('SELECT aeskey, internalname FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "true"')
+ cur = self.con.cursor()
+ cur.execute('SELECT aeskey, internalname FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "1"')
if (cur.rowcount != 1):
self.validationResult = self.status['BAD_OTP']
- con.close()
return self.validationResult
(self.aeskey, self.internalname) = cur.fetchone()
self.plaintext = self.aes128ecb_decrypt(self.aeskey, self.token)
uid = self.plaintext[:12]
if (self.internalname != uid):
self.validationResult = self.status['BAD_OTP']
- con.close()
return self.validationResult
if not (self.CRC() or self.isCRCValid()):
self.validationResult = self.status['BAD_OTP']
- con.close()
return self.validationResult
self.internalcounter = self.hexdec(self.plaintext[14:16] + self.plaintext[12:14] + self.plaintext[22:24])
self.timestamp = self.hexdec(self.plaintext[20:22] + self.plaintext[18:20] + self.plaintext[16:18])
- cur.execute('SELECT counter, time FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "true"')
+ cur.execute('SELECT counter, time FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "1"')
if (cur.rowcount != 1):
self.validationResult = self.status['BAD_OTP']
- con.close()
return self.validationResult
(self.counter, self.time) = cur.fetchone()
if (self.counter) >= (self.internalcounter):
self.validationResult = self.status['REPLAYED_OTP']
- con.close()
return self.validationResult
if (self.time >= self.timestamp) and ((self.counter >> 8) == (self.internalcounter >> 8)):
self.validationResult = self.status['DELAYED_OTP']
- con.close()
return self.validationResult
except IndexError:
self.validationResult = self.status['BAD_OTP']
- con.close()
return self.validationResult
self.validationResult = self.status['OK']
cur.execute('UPDATE yubikeys SET counter = ' + str(self.internalcounter) + ', time = ' + str(self.timestamp) + ' WHERE publicname = "' + self.userid + '"')
- con.commit()
- con.close()
+ self.con.commit()
return self.validationResult
class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
__base = BaseHTTPServer.BaseHTTPRequestHandler
__base_handle = __base.handle
- server_version = 'Yubiserve/2.9'
- print 'HTTP Server is running.'
-
+ server_version = 'Yubiserve/3.0'
+ global config
+ #try:
+ if config['yubiDB'] == 'sqlite':
+ con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
+ elif config['yubiDB'] == 'mysql':
+ con = MySQLdb.connect(host=config['yubiMySQLHost'], user=config['yubiMySQLUser'], passwd=config['yubiMySQLPass'], db=config['yubiMySQLName'])
+ #except:
+ # print "There's a problem with the database!\n"
+ # quit()
+
def getToDict(self, qs):
dict = {}
for singleValue in qs.split('&'):
@@ -170,7 +189,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
if len(query) > 0:
getData = self.getToDict(query)
- otpvalidation = OTPValidation()
+ otpvalidation = OTPValidation(self.con)
validation = otpvalidation.validateOTP(getData['otp'])
self.send_response(200)
self.send_header('Content-type', 'text/plain')
@@ -186,8 +205,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
+ cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
if cur.rowcount != 0:
api_key = cur.fetchone()[0]
@@ -210,8 +228,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
+ cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
if cur.rowcount != 0:
api_key = cur.fetchone()[0]
@@ -224,7 +241,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
getData = self.getToDict(query)
if (len(query) > 0) and ((len(getData['otp']) == 6) or (len(getData['otp']) == 8) or (len(getData['otp']) == 18) or (len(getData['otp']) == 20)):
- oathvalidation = OATHValidation()
+ oathvalidation = OATHValidation(self.con)
OTP = getData['otp']
if (len(OTP) == 18) or (len(OTP) == 20):
publicID = OTP[0:12]
@@ -245,8 +262,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
+ cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
if cur.rowcount != 0:
api_key = cur.fetchone()[0]
@@ -267,8 +283,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
+ cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
if cur.rowcount != 0:
api_key = cur.fetchone()[0]
@@ -288,8 +303,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
try:
if (getData['id'] != None):
apiID = re.escape(getData['id'])
- con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
- cur = con.cursor()
+ cur = self.con.cursor()
cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
if cur.rowcount != 0:
api_key = cur.fetchone()[0]
@@ -318,8 +332,25 @@ class SecureHTTPServer(BaseHTTPServer.HTTPServer):
class ThreadingHTTPServer (SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): pass
class ThreadingHTTPSServer (SocketServer.ThreadingMixIn, SecureHTTPServer): pass
-yubiserveHTTP = ThreadingHTTPServer((yubiserveHOST, yubiservePORT), YubiServeHandler)
-yubiserveSSL = ThreadingHTTPSServer((yubiserveHOST, yubiserveSSLPORT), YubiServeHandler)
+try:
+ if MySQLdb != None:
+ isThereMysql = True
+except NameError:
+ isThereMysql = False
+try:
+ if sqlite != None:
+ isThereSqlite = True
+except NameError:
+ isThereSqlite = False
+if isThereMysql == isThereSqlite == False:
+ print "Cannot continue without any database support.\nPlease read README.\n\n"
+ quit()
+if config['yubiDB'] == 'mysql' and (config['yubiMySQLHost'] == '' or config['yubiMySQLUser'] == '' or config['yubiMySQLPass'] == '' or config['yubiMySQLName'] == ''):
+ print "Cannot continue without any MySQL configuration.\nPlease read README.\n\n"
+ quit()
+
+yubiserveHTTP = ThreadingHTTPServer((config['yubiserveHOST'], config['yubiservePORT']), YubiServeHandler)
+yubiserveSSL = ThreadingHTTPSServer((config['yubiserveHOST'], config['yubiserveSSLPORT']), YubiServeHandler)
http_thread = Thread(target=yubiserveHTTP.serve_forever)
ssl_thread = Thread(target=yubiserveSSL.serve_forever)
@@ -330,16 +361,7 @@ ssl_thread.setDaemon(True)
http_thread.start()
ssl_thread.start()
-while 1:
- time.sleep(1)
-
-"""
+print "HTTP Server is running."
-try:
- yubiserve.serve_forever()
- yubiserveSSL.serve_forever()
-except KeyboardInterrupt:
- print ""
- yubiserve.server_close()
- yubiserveSSL.server_close()
-""" \ No newline at end of file
+while 1:
+ time.sleep(1) \ No newline at end of file