#!/usr/bin/python #Copyright 2011 William Stearns #Released under the GPL #Dedicated to Matthew Hathaway, who left us too soon. import sys import socket import re import subprocess import os import urllib2 import syslog ## -------------------------------------------------------- ## constants ## -------------------------------------------------------- YubicoAuthSrvURLprefix = 'http://api.yubico.com/wsapi/verify?id=' AuthSrvRespRegex = re.compile('^status=(?P\w{2})') KeyRegex = re.compile('[bcdefghijklnrtuv]{44}') #======== Program Options and defaults ======== ListenPort=8975 ClientId='' AuthCommand="/usr/bin/yubiknock-authorize" #======== Functions ======== def Debug(DebugStr): """Prints a note to stderr""" sys.stderr.write(DebugStr + '\n') def ReceiveKey (socket): InText = '' #FIXME - also check to see if we've already accumulated 16K of input; if so, stop accepting data and close connection. while KeyRegex.search(InText) == None: InData = socket.recv(4096) if not InData: #OK, instead of exiting, just return an empty string so we can go back to listen for more connections. return "" #raise EOFError('Socket closed before we received the entire key') InText += InData Match = KeyRegex.search(InText) return InText[Match.start():Match.end()] def Usage(): if (len(sys.argv) > 0): Debug(str(sys.argv[0]) + " command line options:") else: Debug("yubiknock.py command line options:") Debug("[-h]\t\t\tShow this help screen.") Debug("-c ClientId\t\tYubico Client API Key, required, no default. Get from https://api.yubico.com/get-api-key/ or https://upgrade.yubico.com/getapikey/ .") Debug("[-e externalauthprogram]\t\tExternal program we should call to make system changes for a valid key (default: " + AuthCommand + " ).") Debug("[-p listeningport]\t\tTCP port on which we should listen for incoming keys (default: " + str(ListenPort) + " ).") quit() #Copied and modified from YubicoAuthClient.py def VerifyOTP(clientId,otp): YubicoAuthSrvURL = YubicoAuthSrvURLprefix + str(clientId) + "&otp=" + otp fh = urllib2.urlopen(YubicoAuthSrvURL) # URL response assigned to a file handle/object for line in fh: AuthSrvRespMatch = AuthSrvRespRegex.search(line.strip('\n')) if AuthSrvRespMatch: if AuthSrvRespMatch.group('rc') == 'OK': return True else: return False break #======== Process command line options ======== ParamPointer = 1 while (ParamPointer < len(sys.argv)): #Debug("Processing: " + sys.argv[ParamPointer]) if (sys.argv[ParamPointer] == "-h"): Usage() elif (sys.argv[ParamPointer] == "-c"): if (ParamPointer + 1 >= len(sys.argv)): Debug("'-c' command line option requested, but no ClientId following it, exiting.") Usage() else: ClientId = int(sys.argv[ParamPointer + 1]) ParamPointer += 1 #Debug("ClientId is " + str(ClientId)) elif (sys.argv[ParamPointer] == "-e"): if (ParamPointer + 1 >= len(sys.argv)): Debug("'-e' command line option requested, but no external program following it, exiting.") Usage() else: AuthCommand = sys.argv[ParamPointer + 1] ParamPointer += 1 #Debug("Authorization command is " + str(AuthCommand)) elif (sys.argv[ParamPointer] == "-p"): if (ParamPointer + 1 >= len(sys.argv)): Debug("'-p' command line option requested, but no port following it, exiting.") Usage() else: ListenPort = int(sys.argv[ParamPointer + 1]) ParamPointer += 1 #Debug("Listening Port is " + str(ListenPort)) else: Debug("Unknown parameter, exiting.") Usage() ParamPointer += 1 #FIXME - check executable if (not(os.path.isfile(AuthCommand))): Debug(str(AuthCommand) + " does not appear to be an external program, exiting.") Usage() #======== Set up server ======== syslog.openlog('Yubiknock ') ListenSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ListenSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: ListenSocket.bind(("", ListenPort)) except: Debug("Unable to Listen to port " + str(ListenPort) + ", exiting.") quit() ListenSocket.listen(1) #Increase for larger queue of backlogged connection requests #Continuous loop, accept a connection and process input. while True: #print 'Listening at', ListenSocket.getsockname() sc, sockname = ListenSocket.accept() #print 'Remote IP is', sc.getpeername()[0] InKey = ReceiveKey(sc) if InKey == '': #Feedback to the tcp client is deliberately minimal until we have a valid OTP. sc.sendall('Finished.\n') else: syslog.syslog('Received yubikey ' + InKey + ' from ' + sc.getpeername()[0]) #For debugging. #sc.sendall('Yubikey received.\n') if VerifyOTP(ClientId, InKey): syslog.syslog('Received yubikey ' + InKey + ' from ' + sc.getpeername()[0] + ', key verified.') sc.sendall('Yubikey verified.\n') try: RetCode = subprocess.call([AuthCommand, sc.getpeername()[0], InKey[0:12]]) syslog.syslog('Received yubikey ' + InKey + ' from ' + sc.getpeername()[0] + ', auth program returned ' + str(RetCode)) sc.sendall('Return code is ' + str(RetCode) + '\n') except: syslog.syslog('Unable to execute ' + AuthCommand) sc.sendall('Unable to continue, please see server logs.') else: syslog.syslog('Not able to validate yubikey ' + InKey + ' from ' + sc.getpeername()[0]) sc.sendall('Nope.\n') sc.close() #print 'Socket closed'