Skip to content

Commit 979c80d

Browse files
committed
Merge pull request #93 from tvincentNuoDB/master
More Python 3 support and beginning of unencrypted session handling
2 parents 048ca4e + cc51ca7 commit 979c80d

File tree

6 files changed

+151
-72
lines changed

6 files changed

+151
-72
lines changed

pynuodb/connection.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@
1515
from .util import getCloudEntry
1616

1717
import time
18-
import string
19-
20-
# http://www.python.org/dev/peps/pep-0249
2118

2219
apilevel = "2.0"
2320
threadsafety = 1
2421
paramstyle = "qmark"
25-
#schema='user', auto_commit=False
2622

2723
def connect(database, host, user, password, options=None):
2824
"""Creates a connection object.
@@ -97,10 +93,12 @@ def __init__(self, dbName, broker, username, password, options):
9793
parameters = {'user' : username, 'timezone' : time.strftime('%Z')}
9894
if options:
9995
parameters.update(options)
96+
if 'cipher' in options and options['cipher'] == 'None':
97+
self.__session.set_encryption(False)
10098

10199
version, serverKey, salt = self.__session.open_database(dbName, parameters, cp)
102-
103-
sessionKey = cp.computeSessionKey(string.upper(username), password, salt, serverKey)
100+
101+
sessionKey = cp.computeSessionKey(username.upper(), password, salt, serverKey)
104102
self.__session.setCiphers(RC4Cipher(sessionKey), RC4Cipher(sessionKey))
105103

106104
self.__session.check_auth()

pynuodb/crypt.py

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,22 @@
2121

2222
import hashlib
2323
import random
24-
import string
2524
import binascii
25+
import sys
26+
27+
systemVersion = sys.version[0]
2628

2729
def toHex(bigInt):
28-
hexStr = (hex(bigInt)[2:])[:-1]
30+
#Python 3 will no longer insert an L for type formatting
31+
if systemVersion is '3':
32+
hexStr = (hex(bigInt)[2:])
33+
else:
34+
hexStr = (hex(bigInt)[2:])[:-1]
2935
# if the number is the right size then the hex string will be missing one
3036
# character that some platforms assume, so force an even length encoding
3137
if len(hexStr) % 2 == 1:
3238
hexStr = "0" + hexStr
33-
return string.upper(hexStr)
39+
return hexStr.upper()
3440

3541
def fromHex(hexStr):
3642
return int(hexStr, 16)
@@ -87,6 +93,9 @@ def toByteString(bigInt):
8793
def fromByteString(byteStr):
8894
result = 0
8995
shiftCount = 0
96+
if systemVersion == '3':
97+
if type(byteStr) is bytes:
98+
byteStr = byteStr.decode('latin-1')
9099
for b in reversed(byteStr):
91100
result = result | ((ord(b) & 0xff) << shiftCount)
92101
shiftCount = shiftCount + 8
@@ -111,11 +120,17 @@ def __init__(self, primeStr=defaultPrime, generatorStr=defaultGenerator):
111120
primeBytes = toByteString(self.__primeInt)
112121
generatorBytes = toByteString(self.__generatorInt)
113122
paddingLength = len(primeBytes) - len(generatorBytes)
123+
paddingBuffer = chr(0) * paddingLength
114124

115125
md = hashlib.sha1()
126+
if systemVersion == '3':
127+
primeBytes = primeBytes.encode('latin-1')
128+
generatorBytes = generatorBytes.encode('latin-1')
129+
paddingBuffer = paddingBuffer.encode('latin-1')
130+
116131
md.update(primeBytes)
117132
if paddingLength > 0:
118-
md.update(chr(0) * paddingLength)
133+
md.update(paddingBuffer)
119134
md.update(generatorBytes)
120135

121136
self.__k = fromByteString(md.digest())
@@ -134,9 +149,13 @@ class RemotePassword:
134149
def __init__(self):
135150
self.__group = RemoteGroup()
136151

152+
137153
def _getUserHash(self, account, password, salt):
138154
md = hashlib.sha1()
139-
md.update(account + ":" + password)
155+
userInfo = account + ":" + password
156+
if systemVersion == '3':
157+
userInfo = userInfo.encode('latin-1')
158+
md.update(userInfo)
140159
hash1 = md.digest()
141160

142161
md = hashlib.sha1()
@@ -150,6 +169,11 @@ def _computeScramble(self, clientPublicKey, serverPublicKey):
150169
serverBytes = toByteString(serverPublicKey)
151170

152171
md = hashlib.sha1()
172+
173+
if systemVersion == '3':
174+
clientBytes = clientBytes.encode('latin-1')
175+
serverBytes = serverBytes.encode('latin-1')
176+
153177
md.update(clientBytes)
154178
md.update(serverBytes)
155179

@@ -186,6 +210,8 @@ def computeSessionKey(self, account, password, salt, serverKey):
186210
secretBytes = toByteString(sessionSecret)
187211

188212
md = hashlib.sha1()
213+
if systemVersion == '3':
214+
secretBytes = secretBytes.encode('latin-1')
189215
md.update(secretBytes)
190216

191217
return md.digest()
@@ -235,27 +261,42 @@ def computeSessionKey(self, clientKey, verifier):
235261
class RC4Cipher:
236262

237263
def __init__(self, key):
238-
self.__S = range(256)
239-
self.__s1 = 0
240-
self.__s2 = 0
264+
if systemVersion == '3':
265+
self.__state = list(range(256))
266+
key = key.decode('latin-1')
267+
else:
268+
self.__state = range(256)
269+
self.__idx1 = 0
270+
self.__idx2 = 0
241271

242-
state = self.__S
272+
state = self.__state
243273

244274
j = 0
245275
for i in range(256):
246-
j = (j + state[i] + ord(key[i % len(key)])) % 256
276+
byteString = key[i % len(key)]
277+
byteString = ord(byteString)
278+
279+
j = (j + state[i] + byteString) % 256
247280
state[i], state[j] = state[j], state[i]
248281

249282
def transform(self, data):
250283
transformed = []
251-
state = self.__S
252-
284+
state = self.__state
285+
if type(data) is bytes:
286+
data.decode("latin-1")
253287
for char in data:
254-
self.__s1 = (self.__s1 + 1) % 256
255-
self.__s2 = (self.__s2 + state[self.__s1]) % 256
256-
state[self.__s1], state[self.__s2] = state[self.__s2], state[self.__s1]
257-
cipherByte = ord(char) ^ state[(state[self.__s1] + state[self.__s2]) % 256]
258-
transformed.append(chr(cipherByte))
259288

289+
self.__idx1 = (self.__idx1 + 1) % 256
290+
self.__idx2 = (self.__idx2 + state[self.__idx1]) % 256
291+
state[self.__idx1], state[self.__idx2] = state[self.__idx2], state[self.__idx1]
292+
cipherByte = ord(char) ^ state[(state[self.__idx1] + state[self.__idx2]) % 256]
293+
transformed.append(chr(cipherByte))
260294
return ''.join(transformed)
261295

296+
class NoCipher:
297+
298+
def __init__(self):
299+
pass
300+
301+
def transform(self, data):
302+
return data

pynuodb/encodedsession.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import struct
1111
import decimal
1212

13-
from .crypt import toByteString, fromByteString, toSignedByteString, fromSignedByteString
13+
from .crypt import toByteString, fromByteString, toSignedByteString, fromSignedByteString, NoCipher
1414
from .session import Session, SessionException
1515
from . import protocol
1616
from . import datatype
@@ -59,7 +59,7 @@ class EncodedSession(Session):
5959
supporting function.
6060
exchangeMessages -- Exchange the pending message for an optional response from the server.
6161
setCiphers -- Re-sets the incoming and outgoing ciphers for the session.
62-
62+
set_encryption -- Takes a value of type boolean. Setting encryption to False will result in disabling encryption after the handshake
6363
Private Functions:
6464
__init__ -- Constructor for the EncodedSession class.
6565
_peekTypeCode -- Looks at the next Type Code off the session. (Does not move inpos)
@@ -80,6 +80,8 @@ def __init__(self, host, port, service='SQL2'):
8080
self.__inpos = 0
8181
""" @type : int """
8282
self.closed = False
83+
""" @type : boolean """
84+
self.__encryption = True
8385

8486
# Mostly for connections
8587
def open_database(self, db_name, parameters, cp):
@@ -91,20 +93,21 @@ def open_database(self, db_name, parameters, cp):
9193
self._putMessageId(protocol.OPENDATABASE).putInt(protocol.CURRENT_PROTOCOL_VERSION).putString(db_name).putInt(len(parameters))
9294
for (k, v) in parameters.items():
9395
self.putString(k).putString(v)
96+
9497
self.putNull().putString(cp.genClientKey())
9598

9699
self._exchangeMessages()
97-
98100
version = self.getInt()
99101
serverKey = self.getString()
100102
salt = self.getString()
101-
102103
return version, serverKey, salt
103104

104105
def check_auth(self):
105106
try:
106107
self._putMessageId(protocol.AUTHENTICATION).putString(protocol.AUTH_TEST_STR)
107108
self._exchangeMessages()
109+
if self.__encryption is False:
110+
self._setCiphers(NoCipher(), NoCipher())
108111
except SessionException as e:
109112
raise ProgrammingError('Failed to authenticate: ' + str(e))
110113

@@ -136,6 +139,9 @@ def send_rollback(self):
136139
self._putMessageId(protocol.ROLLBACKTRANSACTION)
137140
self._exchangeMessages()
138141

142+
def set_encryption(self, value):
143+
self.__encryption = value
144+
139145
def test_connection(self):
140146
# Create a statement handle
141147
self._putMessageId(protocol.CREATE)
@@ -776,7 +782,7 @@ def getValue(self):
776782
def _exchangeMessages(self, getResponse=True):
777783
"""Exchange the pending message for an optional response from the server."""
778784
try:
779-
# print "message to server: %s" % (self.__output)
785+
#print("message to server: %s" % (self.__output))
780786
self.send(self.__output)
781787
finally:
782788
self.__output = None

pynuodb/protocol.py

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
NULL = 1
77
TRUE = 2
88
FALSE = 3
9-
UUID = 200
10-
SCALEDCOUNT1 = 199
11-
SCALEDCOUNT2 = 225
129
INTMINUS10 = 10
1310
INTMINUS1 = 19
1411
INT0 = 20
@@ -17,33 +14,35 @@
1714
INTLEN8 = 59
1815
SCALEDLEN0 = 60
1916
SCALEDLEN8 = 68
20-
DOUBLELEN0 = 77
21-
DOUBLELEN8 = 85
2217
UTF8COUNT1 = 69
2318
UTF8COUNT4 = 72
24-
UTF8LEN0 = 109
25-
UTF8LEN39 = 148
2619
OPAQUECOUNT1 = 73
2720
OPAQUECOUNT4 = 76
28-
OPAQUELEN0 = 149
29-
OPAQUELEN39 = 188
30-
BLOBLEN0 = 189
31-
BLOBLEN4 = 193
32-
CLOBLEN0 = 194
33-
CLOBLEN4 = 198
21+
DOUBLELEN0 = 77
22+
DOUBLELEN8 = 85
3423
MILLISECLEN0 = 86 # milliseconds since January 1, 1970
3524
MILLISECLEN8 = 94
3625
NANOSECLEN0 = 95 # nanoseconds since January 1, 1970
3726
NANOSECLEN8 = 103
3827
TIMELEN0 = 104 # milliseconds since midnight
3928
TIMELEN4 = 108
29+
UTF8LEN0 = 109
30+
UTF8LEN39 = 148
31+
OPAQUELEN0 = 149
32+
OPAQUELEN39 = 188
33+
BLOBLEN0 = 189
34+
BLOBLEN4 = 193
35+
CLOBLEN0 = 194
36+
CLOBLEN4 = 198
37+
SCALEDCOUNT1 = 199
38+
UUID = 200
39+
SCALEDDATELEN1 = 201
40+
SCALEDDATELEN8 = 208
4041
SCALEDTIMELEN1 = 209
4142
SCALEDTIMELEN8 = 216
4243
SCALEDTIMESTAMPLEN1 = 217
4344
SCALEDTIMESTAMPLEN8 = 224
44-
SCALEDDATELEN1 = 201
45-
SCALEDDATELEN8 = 208
46-
45+
SCALEDCOUNT2 = 225
4746

4847
# Protocol Messages
4948
FAILURE = 0
@@ -126,8 +125,8 @@
126125
ATTACHDEBUGGER = 77
127126
DEBUGREQUEST = 78
128127
GETSEQUENCEVALUE2 = 79
129-
GETLIMIT = 80
130-
SETLIMIT = 81
128+
GETCONNECTIONLIMIT = 80
129+
SETCONNECTIONLIMIT = 81
131130
DELETEBLOBDATA = 82
132131
EXECUTEBATCH = 83
133132
EXECUTEBATCHPREPAREDSTATEMENT = 84
@@ -149,6 +148,27 @@
149148
SUPPORTSTRANSACTIONISOLATION = 100
150149
GETCATALOG = 101
151150
GETCURRENTSCHEMA = 102
151+
PREPARECALL = 103
152+
EXECUTECALLABLESTATEMENT = 104
153+
SETQUERYTIMEOUT = 105
154+
GETPROCEDURES = 106
155+
GETPROCEDURECOLUMNS = 107
156+
GETSUPERTABLES = 108
157+
GETSUPERTYPES = 109
158+
GETFUNCTIONS = 110
159+
GETFUNCTIONCOLUMNS = 111
160+
GETTABLEPRIVILEGES = 112
161+
GETCOLUMNPRIVILEGES = 113
162+
GETCROSSREFERENCE = 114
163+
ALLPROCEDURESARECALLABLE = 115
164+
ALLTABLESARESELECTABLE = 116
165+
GETATTRIBUTES = 117
166+
GETUDTS = 118
167+
GETVERSIONCOLUMNS = 119
168+
GETLOBCHUNK = 120
169+
GETLASTSTATEMENTTIMEMICROS = 121
170+
171+
152172

153173

154174
# Error code values
@@ -320,6 +340,10 @@ def lookup_code(error_code):
320340
PROTOCOL_VERSION13 = 13 # 01/24/2013 Added some JDBC methods
321341
PROTOCOL_VERSION14 = 14 # 02/18/2014 Changed strings in Types/TypeUtil.cpp - affected Python driver
322342
PROTOCOL_VERSION15 = 15 # 04/22/2014 openDatabase returns server-side connection id in response
343+
PROTOCOL_VERSION16 = 16 # 07/01/2014
344+
PROTOCOL_VERSION17 = 17 # 11/03/2014 Support for client sending last commit info
345+
PROTOCOL_VERSION18 = 18 # 02/19/2015 JDBC metadata updates
346+
PROTOCOL_VERSION19 = 19 # 05/01/2015 Server timing of statements
323347
#
324348
# The current protocol version of THIS driver. The server will negotiate the lowest compatible version.
325349
#

0 commit comments

Comments
 (0)