$OpenBSD: patch-yubiserve_py,v 1.6 2015/08/25 12:24:02 sthen Exp $

sqlite3 support from
http://code.google.com/p/yubico-yubiserve/source/list r39

remove bogus timestamp checking code; that isn't what the timestamps are for,
and they wrap after between 0 and 24-and-a-bit days uptime causing failures.

--- yubiserve.py.orig	Tue Aug 25 13:20:55 2015
+++ yubiserve.py	Tue Aug 25 13:21:29 2015
@@ -1,4 +1,4 @@
-#!/usr/bin/python
+#!${MODPY_BIN}
 import re, os, time, socket
 import urlparse, SocketServer, urllib, BaseHTTPServer
 from Crypto.Cipher import AES
@@ -10,12 +10,16 @@ try:
 except ImportError:
 	pass
 try:
+	import sqlite3
+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()
+	config = open('${SYSCONFDIR}/yubiserve/yubiserve.cfg', 'r').read().splitlines()
 	keys = {}
 	for line in config:
 		match = re.search('(.*?)=(.*);', line)
@@ -45,7 +49,7 @@ class OATHValidation():
 	def validateOATH(self, OATH, publicID):
 		cur = self.con.cursor()
 		cur.execute("SELECT counter, secret FROM oathtokens WHERE publicname = '" + publicID + "' AND active = '1'")
-		if (cur.rowcount != 1):
+		if cur:
 			validationResult = self.status['BAD_OTP']
 			return validationResult
 		(actualcounter, key) = cur.fetchone()
@@ -99,6 +103,7 @@ class OTPValidation():
 	def getResponse(self):
 		return self.validationResponse
 	def validateOTP(self, OTP):
+		global config
 		self.OTP = re.escape(OTP)
 		self.validationResult = 0
 		if (len(OTP) <= 32) or (len(OTP) > 48):
@@ -109,15 +114,21 @@ class OTPValidation():
 			if match.group(1) and match.group(2):
 				self.userid = match.group(1)
 				self.token = self.modhex2hex(match.group(2))
+				# pdb.set_trace()
 				cur = self.con.cursor()
 				cur.execute('SELECT aeskey, internalname FROM yubikeys WHERE publicname = "' + self.userid + '" AND active = "1"')
-				if (cur.rowcount != 1):
+				if not cur:
+					if config['yubiserveDebugLevel'] > 0:
+						print "Yubikey rejected because it is not found in the database, using the query: 'SELECT aeskey, internalname FROM yubikeys WHERE publicname = \"%s\" AND active = \"1\"'" % (self.userid)
 					self.validationResult = self.status['BAD_OTP']
 					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):
+					if config['yubiserveDebugLevel'] > 0:
+						print "Yubikey rejected because the uid (6 byte secret) in the decrypted AES key (set with with ykpersonalise -ouid) does not match the secret key (internalname) in the database"
+						print "Decrypted AES: %s\n Username from yubikey: %s shoould equal the database username: %s" % (self.plaintext, uid, self.internalname)
 					self.validationResult = self.status['BAD_OTP']
 					return self.validationResult
 				if not (self.CRC() or self.isCRCValid()):
@@ -126,16 +137,13 @@ class OTPValidation():
 				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 = "1"')
-				if (cur.rowcount != 1):
+				if not cur:
 					self.validationResult = self.status['BAD_OTP']
 					return self.validationResult
 				(self.counter, self.time) = cur.fetchone()
 				if (self.counter) >= (self.internalcounter):
 					self.validationResult = self.status['REPLAYED_OTP']
 					return self.validationResult
-				if (self.time >= self.timestamp) and ((self.counter >> 8) == (self.internalcounter >> 8)):
-					self.validationResult = self.status['DELAYED_OTP']
-					return self.validationResult
 		except IndexError:
 			self.validationResult = self.status['BAD_OTP']
 			return self.validationResult
@@ -147,11 +155,13 @@ class OTPValidation():
 class YubiServeHandler (BaseHTTPServer.BaseHTTPRequestHandler):
 	__base = BaseHTTPServer.BaseHTTPRequestHandler
 	__base_handle = __base.handle
-	server_version = 'Yubiserve/3.0'
+	server_version = 'Yubiserve/3.1'
 	global config
 	#try:
-	if config['yubiDB'] == 'sqlite':
-		con = sqlite.connect(os.path.dirname(os.path.realpath(__file__)) + '/yubikeys.sqlite')
+	if config['yubiDB'] == 'sqlite3':
+		con = sqlite3.connect('/var/db/yubiserve/yubikeys.sqlite3', check_same_thread = False)
+	elif config['yubiDB'] == 'sqlite':
+		con = sqlite.connect('/var/db/yubiserve/yubikeys.sqlite', check_same_thread = False)
 	elif config['yubiDB'] == 'mysql':
 		con = MySQLdb.connect(host=config['yubiMySQLHost'], user=config['yubiMySQLUser'], passwd=config['yubiMySQLPass'], db=config['yubiMySQLName'])
 	#except:
@@ -190,7 +200,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
 				if len(query) > 0:
 					getData = self.getToDict(query)
 					otpvalidation = OTPValidation(self.con)
-					validation = otpvalidation.validateOTP(getData['otp'])
+					validation = otpvalidation.validateOTP(getData['otp'].lower())
 					self.send_response(200)
 					self.send_header('Content-type', 'text/plain')
 					self.end_headers()
@@ -200,16 +210,16 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
 						orderedResult = 'nonce=' + getData['nonce'] + '&otp=' + getData['otp'] + '&sl=100&status=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
 					except KeyError:
 						result = 't=' + iso_time + '\r\notp=' + getData['otp'] + '\r\nnonce=\r\nsl=100\r\nstatus=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '\r\n'
-						orderedResult = 'nonce=&otp=' + getData['otp'] + 'sl=100&status=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
+						orderedResult = 'nonce=&otp=' + getData['otp'] + '&sl=100&status=' + [k for k, v in otpvalidation.status.iteritems() if v == validation][0] + '&t=' + iso_time
 					otp_hmac = ''
 					try:
 						if (getData['id'] != None):
 							apiID = re.escape(getData['id'])
 							cur = self.con.cursor()
 							cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
-							if cur.rowcount != 0:
+							if cur:
 								api_key = cur.fetchone()[0]
-								otp_hmac = hmac.new(api_key, msg=orderedResult, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
+								otp_hmac = hmac.new(str(api_key), msg=str(orderedResult), digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
 							else:
 								result = 't=' + iso_time + '\r\notp=' + getData['otp'] + '\r\nstatus=NO_CLIENT\r\n'
 					except KeyError:
@@ -230,7 +240,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
 					apiID = re.escape(getData['id'])
 					cur = self.con.cursor()
 					cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
-					if cur.rowcount != 0:
+					if cur:
 						api_key = cur.fetchone()[0]
 						otp_hmac = hmac.new(api_key, msg=orderedResult, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
 			except KeyError:
@@ -264,7 +274,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
 							apiID = re.escape(getData['id'])
 							cur = self.con.cursor()
 							cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
-							if cur.rowcount != 0:
+							if cur:
 								api_key = cur.fetchone()[0]
 								otp_hmac = hmac.new(api_key, msg=result, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
 							else:
@@ -285,7 +295,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
 							apiID = re.escape(getData['id'])
 							cur = self.con.cursor()
 							cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
-							if cur.rowcount != 0:
+							if cur:
 								api_key = cur.fetchone()[0]
 								otp_hmac = hmac.new(api_key, msg=result, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
 					except KeyError:
@@ -305,7 +315,7 @@ class YubiServeHandler (BaseHTTPServer.BaseHTTPRequest
 					apiID = re.escape(getData['id'])
 					cur = self.con.cursor()
 					cur.execute("SELECT secret from apikeys WHERE id = '" + apiID + "'")
-					if cur.rowcount != 0:
+					if cur:
 						api_key = cur.fetchone()[0]
 						otp_hmac = hmac.new(api_key, msg=result, digestmod=hashlib.sha1).hexdigest().decode('hex').encode('base64').strip()
 			except KeyError:
@@ -322,9 +332,10 @@ class SecureHTTPServer(BaseHTTPServer.HTTPServer):
 	def __init__(self, server_address, HandlerClass):
 		BaseHTTPServer.HTTPServer.__init__(self, server_address, HandlerClass)
 		ctx = SSL.Context(SSL.SSLv23_METHOD)
-		fpem = os.path.dirname(os.path.realpath(__file__)) + '/yubiserve.pem'
+		fpem = '${SYSCONFDIR}/yubiserve/yubiserve.pem'
 		ctx.use_privatekey_file (fpem)
 		ctx.use_certificate_file(fpem)
+		ctx.use_certificate_chain_file(fpem)
 		self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))
 		self.server_bind()
 		self.server_activate()
