Coverage for /root/GitHubProjects/impacket/impacket/tds.py : 14%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
2#
3# This software is provided under under a slightly modified version
4# of the Apache Software License. See the accompanying LICENSE file
5# for more information.
6#
7# Description: [MS-TDS] & [MC-SQLR] implementation.
8#
9# ToDo:
10# [ ] Add all the tokens left
11# [ ] parseRow should be rewritten and add support for all the SQL types in a
12# good way. Right now it just supports a few types.
13# [ ] printRows is crappy, just an easy way to print the rows. It should be
14# rewritten to output like a normal SQL client
15#
16# Author:
17# Alberto Solino (@agsolino)
18#
20from __future__ import division
21from __future__ import print_function
22import struct
23import socket
24import select
25import random
26import binascii
27import math
28import datetime
29import string
31from impacket import ntlm, uuid, LOG
32from impacket.structure import Structure
34try:
35 from OpenSSL import SSL
36except:
37 LOG.critical("pyOpenSSL is not installed, can't continue")
38 raise
40# We need to have a fake Logger to be compatible with the way Impact
41# prints information. Outside Impact it's just a print. Inside
42# we will receive the Impact logger instance to print row information
43# The rest it processed through the standard impacket logging mech.
44class DummyPrint:
45 def logMessage(self,message):
46 if message == '\n':
47 print(message)
48 else:
49 print(message, end=' ')
51# MC-SQLR Constants and Structures
52SQLR_PORT = 1434
53SQLR_CLNT_BCAST_EX = 0x02
54SQLR_CLNT_UCAST_EX = 0x03
55SQLR_CLNT_UCAST_INST= 0x04
56SQLR_CLNT_UCAST_DAC = 0x0f
59class SQLR(Structure):
60 commonHdr = (
61 ('OpCode','B'),
62 )
64class SQLR_UCAST_INST(SQLR):
65 structure = (
66 ('Instance',':')
67 )
68 def __init__(self, data = None):
69 SQLR.__init__(self,data)
70 if data is not None:
71 self['OpCode'] = SQLR_CLNT_UCAST_INST
73class SQLR_UCAST_DAC(SQLR):
74 structure = (
75 ('Protocol', 'B=1'),
76 ('Instance', ':'),
77 )
78 def __init__(self, data = None):
79 SQLR.__init__(self,data)
80 if data is not None:
81 self['OpCode'] = SQLR_CLNT_UCAST_DAC
83class SQLR_Response(SQLR):
84 structure = (
85 ('Size','<H'),
86 ('_Data','_-Data','self["Size"]'),
87 ('Data',':'),
88 )
90class SQLErrorException(Exception):
91 pass
93# TDS Constants and Structures
95# TYPE constants
96TDS_SQL_BATCH = 1
97TDS_PRE_TDS_LOGIN = 2
98TDS_RPC = 3
99TDS_TABULAR = 4
100TDS_ATTENTION = 6
101TDS_BULK_LOAD_DATA = 7
102TDS_TRANSACTION = 14
103TDS_LOGIN7 = 16
104TDS_SSPI = 17
105TDS_PRE_LOGIN = 18
107# Status constants
108TDS_STATUS_NORMAL = 0
109TDS_STATUS_EOM = 1
110TDS_STATUS_RESET_CONNECTION = 8
111TDS_STATUS_RESET_SKIPTRANS = 16
113# Encryption
114TDS_ENCRYPT_OFF = 0
115TDS_ENCRYPT_ON = 1
116TDS_ENCRYPT_NOT_SUP = 2
117TDS_ENCRYPT_REQ = 3
119# Option 2 Flags
120TDS_INTEGRATED_SECURITY_ON = 0x80
121TDS_INIT_LANG_FATAL = 0x01
122TDS_ODBC_ON = 0x02
124# Token Types
125TDS_ALTMETADATA_TOKEN = 0x88
126TDS_ALTROW_TOKEN = 0xD3
127TDS_COLMETADATA_TOKEN = 0x81
128TDS_COLINFO_TOKEN = 0xA5
129TDS_DONE_TOKEN = 0xFD
130TDS_DONEPROC_TOKEN = 0xFE
131TDS_DONEINPROC_TOKEN = 0xFF
132TDS_ENVCHANGE_TOKEN = 0xE3
133TDS_ERROR_TOKEN = 0xAA
134TDS_INFO_TOKEN = 0xAB
135TDS_LOGINACK_TOKEN = 0xAD
136TDS_NBCROW_TOKEN = 0xD2
137TDS_OFFSET_TOKEN = 0x78
138TDS_ORDER_TOKEN = 0xA9
139TDS_RETURNSTATUS_TOKEN = 0x79
140TDS_RETURNVALUE_TOKEN = 0xAC
141TDS_ROW_TOKEN = 0xD1
142TDS_SSPI_TOKEN = 0xED
143TDS_TABNAME_TOKEN = 0xA4
145# ENVCHANGE Types
146TDS_ENVCHANGE_DATABASE = 1
147TDS_ENVCHANGE_LANGUAGE = 2
148TDS_ENVCHANGE_CHARSET = 3
149TDS_ENVCHANGE_PACKETSIZE = 4
150TDS_ENVCHANGE_UNICODE = 5
151TDS_ENVCHANGE_UNICODE_DS = 6
152TDS_ENVCHANGE_COLLATION = 7
153TDS_ENVCHANGE_TRANS_START = 8
154TDS_ENVCHANGE_TRANS_COMMIT = 9
155TDS_ENVCHANGE_ROLLBACK = 10
156TDS_ENVCHANGE_DTC = 11
158# Column types
159# FIXED-LEN Data Types
160TDS_NULL_TYPE = 0x1F
161TDS_INT1TYPE = 0x30
162TDS_BITTYPE = 0x32
163TDS_INT2TYPE = 0x34
164TDS_INT4TYPE = 0x38
165TDS_DATETIM4TYPE = 0x3A
166TDS_FLT4TYPE = 0x3B
167TDS_MONEYTYPE = 0x3C
168TDS_DATETIMETYPE = 0x3D
169TDS_FLT8TYPE = 0x3E
170TDS_MONEY4TYPE = 0x7A
171TDS_INT8TYPE = 0x7F
173# VARIABLE-Len Data Types
174TDS_GUIDTYPE = 0x24
175TDS_INTNTYPE = 0x26
176TDS_DECIMALTYPE = 0x37
177TDS_NUMERICTYPE = 0x3F
178TDS_BITNTYPE = 0x68
179TDS_DECIMALNTYPE = 0x6A
180TDS_NUMERICNTYPE = 0x6C
181TDS_FLTNTYPE = 0x6D
182TDS_MONEYNTYPE = 0x6E
183TDS_DATETIMNTYPE = 0x6F
184TDS_DATENTYPE = 0x28
185TDS_TIMENTYPE = 0x29
186TDS_DATETIME2NTYPE = 0x2A
187TDS_DATETIMEOFFSETNTYPE = 0x2B
188TDS_CHARTYPE = 0x2F
189TDS_VARCHARTYPE = 0x27
190TDS_BINARYTYPE = 0x2D
191TDS_VARBINARYTYPE = 0x25
192TDS_BIGVARBINTYPE = 0xA5
193TDS_BIGVARCHRTYPE = 0xA7
194TDS_BIGBINARYTYPE = 0xAD
195TDS_BIGCHARTYPE = 0xAF
196TDS_NVARCHARTYPE = 0xE7
197TDS_NCHARTYPE = 0xEF
198TDS_XMLTYPE = 0xF1
199TDS_UDTTYPE = 0xF0
200TDS_TEXTTYPE = 0x23
201TDS_IMAGETYPE = 0x22
202TDS_NTEXTTYPE = 0x63
203TDS_SSVARIANTTYPE = 0x62
205class TDSPacket(Structure):
206 structure = (
207 ('Type','<B'),
208 ('Status','<B=1'),
209 ('Length','>H=8+len(Data)'),
210 ('SPID','>H=0'),
211 ('PacketID','<B=0'),
212 ('Window','<B=0'),
213 ('Data',':'),
214 )
216class TDS_PRELOGIN(Structure):
217 structure = (
218 ('VersionToken','>B=0'),
219 ('VersionOffset','>H'),
220 ('VersionLength','>H=len(self["Version"])'),
221 ('EncryptionToken','>B=0x1'),
222 ('EncryptionOffset','>H'),
223 ('EncryptionLength','>H=1'),
224 ('InstanceToken','>B=2'),
225 ('InstanceOffset','>H'),
226 ('InstanceLength','>H=len(self["Instance"])'),
227 ('ThreadIDToken','>B=3'),
228 ('ThreadIDOffset','>H'),
229 ('ThreadIDLength','>H=4'),
230 ('EndToken','>B=0xff'),
231 ('_Version','_-Version','self["VersionLength"]'),
232 ('Version',':'),
233 ('Encryption','B'),
234 ('_Instance','_-Instance','self["InstanceLength"]-1'),
235 ('Instance',':'),
236 ('ThreadID',':'),
237 )
239 def getData(self):
240 self['VersionOffset']=21
241 self['EncryptionOffset']=self['VersionOffset'] + len(self['Version'])
242 self['InstanceOffset']=self['EncryptionOffset'] + 1
243 self['ThreadIDOffset']=self['InstanceOffset'] + len(self['Instance'])
244 return Structure.getData(self)
246class TDS_LOGIN(Structure):
247 structure = (
248 ('Length','<L=0'),
249 ('TDSVersion','>L=0x71'),
250 ('PacketSize','<L=32764'),
251 ('ClientProgVer','>L=7'),
252 ('ClientPID','<L=0'),
253 ('ConnectionID','<L=0'),
254 ('OptionFlags1','<B=0xe0'),
255 ('OptionFlags2','<B'),
256 ('TypeFlags','<B=0'),
257 ('OptionFlags3','<B=0'),
258 ('ClientTimeZone','<L=0'),
259 ('ClientLCID','<L=0'),
260 ('HostNameOffset','<H'),
261 ('HostNameLength','<H=len(self["HostName"])//2'),
262 ('UserNameOffset','<H=0'),
263 ('UserNameLength','<H=len(self["UserName"])//2'),
264 ('PasswordOffset','<H=0'),
265 ('PasswordLength','<H=len(self["Password"])//2'),
266 ('AppNameOffset','<H'),
267 ('AppNameLength','<H=len(self["AppName"])//2'),
268 ('ServerNameOffset','<H'),
269 ('ServerNameLength','<H=len(self["ServerName"])//2'),
270 ('UnusedOffset','<H=0'),
271 ('UnusedLength','<H=0'),
272 ('CltIntNameOffset','<H'),
273 ('CltIntNameLength','<H=len(self["CltIntName"])//2'),
274 ('LanguageOffset','<H=0'),
275 ('LanguageLength','<H=0'),
276 ('DatabaseOffset','<H=0'),
277 ('DatabaseLength','<H=len(self["Database"])//2'),
278 ('ClientID','6s=b"\x01\x02\x03\x04\x05\x06"'),
279 ('SSPIOffset','<H'),
280 ('SSPILength','<H=len(self["SSPI"])'),
281 ('AtchDBFileOffset','<H'),
282 ('AtchDBFileLength','<H=len(self["AtchDBFile"])//2'),
283 ('HostName',':'),
284 ('UserName',':'),
285 ('Password',':'),
286 ('AppName',':'),
287 ('ServerName',':'),
288 ('CltIntName',':'),
289 ('Database',':'),
290 ('SSPI',':'),
291 ('AtchDBFile',':'),
292 )
293 def __init__(self,data=None):
294 Structure.__init__(self,data)
295 if data is None:
296 self['UserName'] = ''
297 self['Password'] = ''
298 self['Database'] = ''
299 self['AtchDBFile'] = ''
301 def fromString(self, data):
302 Structure.fromString(self, data)
303 if self['HostNameLength'] > 0:
304 self['HostName'] = data[self['HostNameOffset']:][:self['HostNameLength']*2]
306 if self['UserNameLength'] > 0:
307 self['UserName'] = data[self['UserNameOffset']:][:self['UserNameLength']*2]
309 if self['PasswordLength'] > 0:
310 self['Password'] = data[self['PasswordOffset']:][:self['PasswordLength']*2]
312 if self['AppNameLength'] > 0:
313 self['AppName'] = data[self['AppNameOffset']:][:self['AppNameLength']*2]
315 if self['ServerNameLength'] > 0:
316 self['ServerName'] = data[self['ServerNameOffset']:][:self['ServerNameLength']*2]
318 if self['CltIntNameLength'] > 0:
319 self['CltIntName'] = data[self['CltIntNameOffset']:][:self['CltIntNameLength']*2]
321 if self['DatabaseLength'] > 0:
322 self['Database'] = data[self['DatabaseOffset']:][:self['DatabaseLength']*2]
324 if self['SSPILength'] > 0:
325 self['SSPI'] = data[self['SSPIOffset']:][:self['SSPILength']*2]
327 if self['AtchDBFileLength'] > 0:
328 self['AtchDBFile'] = data[self['AtchDBFileOffset']:][:self['AtchDBFileLength']*2]
330 def getData(self):
331 index = 36+50
332 self['HostNameOffset']= index
334 index += len(self['HostName'])
336 if self['UserName'] != '':
337 self['UserNameOffset'] = index
338 else:
339 self['UserNameOffset'] = 0
341 index += len(self['UserName'])
343 if self['Password'] != '':
344 self['PasswordOffset'] = index
345 else:
346 self['PasswordOffset'] = 0
348 index += len(self['Password'])
350 self['AppNameOffset']= index
351 self['ServerNameOffset']=self['AppNameOffset'] + len(self['AppName'])
352 self['CltIntNameOffset']=self['ServerNameOffset'] + len(self['ServerName'])
353 self['LanguageOffset']=self['CltIntNameOffset'] + len(self['CltIntName'])
354 self['DatabaseOffset']=self['LanguageOffset']
355 self['SSPIOffset']=self['DatabaseOffset'] + len(self['Database'])
356 self['AtchDBFileOffset']=self['SSPIOffset'] + len(self['SSPI'])
357 return Structure.getData(self)
359class TDS_LOGIN_ACK(Structure):
360 structure = (
361 ('TokenType','<B'),
362 ('Length','<H'),
363 ('Interface','<B'),
364 ('TDSVersion','<L'),
365 ('ProgNameLen','<B'),
366 ('_ProgNameLen','_-ProgName','self["ProgNameLen"]*2'),
367 ('ProgName',':'),
368 ('MajorVer','<B'),
369 ('MinorVer','<B'),
370 ('BuildNumHi','<B'),
371 ('BuildNumLow','<B'),
372 )
374class TDS_RETURNSTATUS(Structure):
375 structure = (
376 ('TokenType','<B'),
377 ('Value','<L'),
378 )
380class TDS_INFO_ERROR(Structure):
381 structure = (
382 ('TokenType','<B'),
383 ('Length','<H'),
384 ('Number','<L'),
385 ('State','<B'),
386 ('Class','<B'),
387 ('MsgTextLen','<H'),
388 ('_MsgTextLen','_-MsgText','self["MsgTextLen"]*2'),
389 ('MsgText',':'),
390 ('ServerNameLen','<B'),
391 ('_ServerNameLen','_-ServerName','self["ServerNameLen"]*2'),
392 ('ServerName',':'),
393 ('ProcNameLen','<B'),
394 ('_ProcNameLen','_-ProcName','self["ProcNameLen"]*2'),
395 ('ProcName',':'),
396 ('LineNumber','<H'),
397 )
399class TDS_ENVCHANGE(Structure):
400 structure = (
401 ('TokenType','<B'),
402 ('Length','<H=4+len(Data)'),
403 ('Type','<B'),
404 ('_Data','_-Data','self["Length"]-1'),
405 ('Data',':'),
406 )
408class TDS_DONEINPROC(Structure):
409 structure = (
410 ('TokenType','<B'),
411 ('Status','<H'),
412 ('CurCmd','<H'),
413 ('DoneRowCount','<L'),
414 )
416class TDS_ORDER(Structure):
417 structure = (
418 ('TokenType','<B'),
419 ('Length','<H'),
420 ('_Data','_-Data','self["Length"]'),
421 ('Data',':'),
422 )
425class TDS_ENVCHANGE_VARCHAR(Structure):
426 structure = (
427 ('NewValueLen','<B=len(NewValue)'),
428 ('_NewValue','_-NewValue','self["NewValueLen"]*2'),
429 ('NewValue',':'),
430 ('OldValueLen','<B=len(OldValue)'),
431 ('_OldValue','_-OldValue','self["OldValueLen"]*2'),
432 ('OldValue',':'),
433 )
435class TDS_ROW(Structure):
436 structure = (
437 ('TokenType','<B'),
438 ('Data',':'),
439 )
441class TDS_DONE(Structure):
442 structure = (
443 ('TokenType','<B'),
444 ('Status','<H'),
445 ('CurCmd','<H'),
446 ('DoneRowCount','<L'),
447 )
449class TDS_COLMETADATA(Structure):
450 structure = (
451 ('TokenType','<B'),
452 ('Count','<H'),
453 ('Data',':'),
454 )
456class MSSQL:
457 def __init__(self, address, port=1433, rowsPrinter=DummyPrint()):
458 #self.packetSize = 32764
459 self.packetSize = 32763
460 self.server = address
461 self.port = port
462 self.socket = 0
463 self.replies = {}
464 self.colMeta = []
465 self.rows = []
466 self.currentDB = ''
467 self.COL_SEPARATOR = ' '
468 self.MAX_COL_LEN = 255
469 self.lastError = False
470 self.tlsSocket = None
471 self.__rowsPrinter = rowsPrinter
473 def getInstances(self, timeout = 5):
474 packet = SQLR()
475 packet['OpCode'] = SQLR_CLNT_UCAST_EX
477 # Open the connection
478 af, socktype, proto, canonname, sa = socket.getaddrinfo(self.server, SQLR_PORT, 0, socket.SOCK_DGRAM)[0]
479 s = socket.socket(af, socktype, proto)
481 s.sendto(packet.getData(), 0, ( self.server, SQLR_PORT ))
482 ready, _, _ = select.select([ s.fileno() ], [ ] , [ ], timeout)
483 if not ready:
484 return []
485 else:
486 data, _ = s.recvfrom(65536, 0)
488 s.close()
489 resp = SQLR_Response(data)
491 # Now parse the results
492 entries = resp['Data'].split(b';;')
494 # We don't want the last one, it's empty
495 entries.pop()
497 # the answer to send back
498 resp = []
500 for i, entry in enumerate(entries):
501 fields = entry.split(b';')
502 ret = {}
503 for j, field in enumerate(fields):
504 if (j & 0x1) == 0:
505 ret[field.decode('utf-8')] = fields[j+1].decode('utf-8')
506 resp.append(ret)
508 return resp
511 def preLogin(self):
512 prelogin = TDS_PRELOGIN()
513 prelogin['Version'] = b"\x08\x00\x01\x55\x00\x00"
514 #prelogin['Encryption'] = TDS_ENCRYPT_NOT_SUP
515 prelogin['Encryption'] = TDS_ENCRYPT_OFF
516 prelogin['ThreadID'] = struct.pack('<L',random.randint(0,65535))
517 prelogin['Instance'] = b'MSSQLServer\x00'
519 self.sendTDS(TDS_PRE_LOGIN, prelogin.getData(), 0)
520 tds = self.recvTDS()
522 return TDS_PRELOGIN(tds['Data'])
524 def encryptPassword(self, password ):
525 return bytes(bytearray([((x & 0x0f) << 4) + ((x & 0xf0) >> 4) ^ 0xa5 for x in bytearray(password)]))
527 def connect(self):
528 af, socktype, proto, canonname, sa = socket.getaddrinfo(self.server, self.port, 0, socket.SOCK_STREAM)[0]
529 sock = socket.socket(af, socktype, proto)
531 try:
532 sock.connect(sa)
533 except Exception:
534 #import traceback
535 #traceback.print_exc()
536 raise
538 self.socket = sock
539 return sock
541 def disconnect(self):
542 if self.socket:
543 return self.socket.close()
545 def setPacketSize(self, packetSize):
546 self.packetSize = packetSize
548 def getPacketSize(self):
549 return self.packetSize
551 def socketSendall(self,data):
552 if self.tlsSocket is None:
553 return self.socket.sendall(data)
554 else:
555 self.tlsSocket.sendall(data)
556 dd = self.tlsSocket.bio_read(self.packetSize)
557 return self.socket.sendall(dd)
559 def sendTDS(self, packetType, data, packetID = 1):
560 if (len(data)-8) > self.packetSize:
561 remaining = data[self.packetSize-8:]
562 tds = TDSPacket()
563 tds['Type'] = packetType
564 tds['Status'] = TDS_STATUS_NORMAL
565 tds['PacketID'] = packetID
566 tds['Data'] = data[:self.packetSize-8]
567 self.socketSendall(tds.getData())
569 while len(remaining) > (self.packetSize-8):
570 packetID += 1
571 tds['PacketID'] = packetID
572 tds['Data'] = remaining[:self.packetSize-8]
573 self.socketSendall(tds.getData())
574 remaining = remaining[self.packetSize-8:]
575 data = remaining
576 packetID+=1
578 tds = TDSPacket()
579 tds['Type'] = packetType
580 tds['Status'] = TDS_STATUS_EOM
581 tds['PacketID'] = packetID
582 tds['Data'] = data
583 self.socketSendall(tds.getData())
585 def socketRecv(self, packetSize):
586 data = self.socket.recv(packetSize)
587 if self.tlsSocket is not None:
588 dd = ''
589 self.tlsSocket.bio_write(data)
590 while True:
591 try:
592 dd += self.tlsSocket.read(packetSize)
593 except SSL.WantReadError:
594 data2 = self.socket.recv(packetSize - len(data) )
595 self.tlsSocket.bio_write(data2)
596 pass
597 else:
598 data = dd
599 break
600 return data
602 def recvTDS(self, packetSize = None):
603 # Do reassembly here
604 if packetSize is None:
605 packetSize = self.packetSize
606 packet = TDSPacket(self.socketRecv(packetSize))
607 status = packet['Status']
608 packetLen = packet['Length']-8
609 while packetLen > len(packet['Data']):
610 data = self.socketRecv(packetSize)
611 packet['Data'] += data
613 remaining = None
614 if packetLen < len(packet['Data']):
615 remaining = packet['Data'][packetLen:]
616 packet['Data'] = packet['Data'][:packetLen]
618 #print "REMAINING ",
619 #if remaining is None:
620 # print None
621 #else:
622 # print len(remaining)
624 while status != TDS_STATUS_EOM:
625 if remaining is not None:
626 tmpPacket = TDSPacket(remaining)
627 else:
628 tmpPacket = TDSPacket(self.socketRecv(packetSize))
630 packetLen = tmpPacket['Length'] - 8
631 while packetLen > len(tmpPacket['Data']):
632 data = self.socketRecv(packetSize)
633 tmpPacket['Data'] += data
635 remaining = None
636 if packetLen < len(tmpPacket['Data']):
637 remaining = tmpPacket['Data'][packetLen:]
638 tmpPacket['Data'] = tmpPacket['Data'][:packetLen]
640 status = tmpPacket['Status']
641 packet['Data'] += tmpPacket['Data']
642 packet['Length'] += tmpPacket['Length'] - 8
644 #print packet['Length']
645 return packet
647 def kerberosLogin(self, database, username, password='', domain='', hashes=None, aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True):
649 if hashes is not None:
650 lmhash, nthash = hashes.split(':')
651 lmhash = binascii.a2b_hex(lmhash)
652 nthash = binascii.a2b_hex(nthash)
653 else:
654 lmhash = ''
655 nthash = ''
657 resp = self.preLogin()
658 # Test this!
659 if resp['Encryption'] == TDS_ENCRYPT_REQ or resp['Encryption'] == TDS_ENCRYPT_OFF:
660 LOG.info("Encryption required, switching to TLS")
662 # Switching to TLS now
663 ctx = SSL.Context(SSL.TLSv1_METHOD)
664 ctx.set_cipher_list('RC4, AES256')
665 tls = SSL.Connection(ctx,None)
666 tls.set_connect_state()
667 while True:
668 try:
669 tls.do_handshake()
670 except SSL.WantReadError:
671 data = tls.bio_read(4096)
672 self.sendTDS(TDS_PRE_LOGIN, data,0)
673 tds = self.recvTDS()
674 tls.bio_write(tds['Data'])
675 else:
676 break
678 # SSL and TLS limitation: Secure Socket Layer (SSL) and its replacement,
679 # Transport Layer Security(TLS), limit data fragments to 16k in size.
680 self.packetSize = 16*1024-1
681 self.tlsSocket = tls
684 login = TDS_LOGIN()
686 login['HostName'] = (''.join([random.choice(string.ascii_letters) for _ in range(8)])).encode('utf-16le')
687 login['AppName'] = (''.join([random.choice(string.ascii_letters) for _ in range(8)])).encode('utf-16le')
688 login['ServerName'] = self.server.encode('utf-16le')
689 login['CltIntName'] = login['AppName']
690 login['ClientPID'] = random.randint(0,1024)
691 login['PacketSize'] = self.packetSize
692 if database is not None:
693 login['Database'] = database.encode('utf-16le')
694 login['OptionFlags2'] = TDS_INIT_LANG_FATAL | TDS_ODBC_ON
696 from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
697 # Importing down here so pyasn1 is not required if kerberos is not used.
698 from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set
699 from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, KerberosError
700 from impacket.krb5 import constants
701 from impacket.krb5.types import Principal, KerberosTime, Ticket
702 from pyasn1.codec.der import decoder, encoder
703 from pyasn1.type.univ import noValue
704 from impacket.krb5.ccache import CCache
705 import os
706 import datetime
708 if useCache is True:
709 try:
710 ccache = CCache.loadFile(os.getenv('KRB5CCNAME'))
711 except:
712 # No cache present
713 pass
714 else:
715 # retrieve domain information from CCache file if needed
716 if domain == '':
717 domain = ccache.principal.realm['data'].decode('utf-8')
718 LOG.debug('Domain retrieved from CCache: %s' % domain)
720 LOG.debug("Using Kerberos Cache: %s" % os.getenv('KRB5CCNAME'))
721 principal = 'MSSQLSvc/%s.%s:%d@%s' % (self.server.split('.')[0], domain, self.port, domain.upper())
722 creds = ccache.getCredential(principal)
724 if creds is not None:
725 TGS = creds.toTGS(principal)
726 LOG.debug('Using TGS from cache')
727 else:
728 # search for the port's instance name instead (instance name based SPN)
729 LOG.debug('Searching target\'s instances to look for port number %s' % self.port)
730 instances = self.getInstances()
731 instanceName = None
732 for i in instances:
733 try:
734 if int(i['tcp']) == self.port:
735 instanceName = i['InstanceName']
736 except:
737 pass
739 if instanceName:
740 principal = 'MSSQLSvc/%s.%s:%s@%s' % (self.server, domain, instanceName, domain.upper())
741 creds = ccache.getCredential(principal)
743 if creds is not None:
744 TGS = creds.toTGS(principal)
745 LOG.debug('Using TGS from cache')
746 else:
747 # Let's try for the TGT and go from there
748 principal = 'krbtgt/%s@%s' % (domain.upper(),domain.upper())
749 creds = ccache.getCredential(principal)
750 if creds is not None:
751 TGT = creds.toTGT()
752 LOG.debug('Using TGT from cache')
753 else:
754 LOG.debug("No valid credentials found in cache. ")
756 # retrieve user information from CCache file if needed
757 if username == '' and creds is not None:
758 username = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8')
759 LOG.debug('Username retrieved from CCache: %s' % username)
760 elif username == '' and len(ccache.principal.components) > 0:
761 username = ccache.principal.components[0]['data'].decode('utf-8')
762 LOG.debug('Username retrieved from CCache: %s' % username)
764 # First of all, we need to get a TGT for the user
765 userName = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
766 while True:
767 if TGT is None:
768 if TGS is None:
769 try:
770 tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost)
771 except KerberosError as e:
772 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
773 # We might face this if the target does not support AES
774 # So, if that's the case we'll force using RC4 by converting
775 # the password to lm/nt hashes and hope for the best. If that's already
776 # done, byebye.
777 if lmhash == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None:
778 from impacket.ntlm import compute_lmhash, compute_nthash
779 LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4')
780 lmhash = compute_lmhash(password)
781 nthash = compute_nthash(password)
782 continue
783 else:
784 raise
785 else:
786 raise
787 else:
788 tgt = TGT['KDC_REP']
789 cipher = TGT['cipher']
790 sessionKey = TGT['sessionKey']
792 if TGS is None:
793 # From https://msdn.microsoft.com/en-us/library/ms191153.aspx?f=255&MSPPError=-2147217396
794 # Beginning with SQL Server 2008, the SPN format is changed in order to support Kerberos authentication
795 # on TCP/IP, named pipes, and shared memory. The supported SPN formats for named and default instances
796 # are as follows.
797 # Named instance
798 # MSSQLSvc/FQDN:[port | instancename], where:
799 # MSSQLSvc is the service that is being registered.
800 # FQDN is the fully qualified domain name of the server.
801 # port is the TCP port number.
802 # instancename is the name of the SQL Server instance.
803 serverName = Principal('MSSQLSvc/%s.%s:%d' % (self.server.split('.')[0], domain, self.port), type=constants.PrincipalNameType.NT_SRV_INST.value)
804 try:
805 tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey)
806 except KerberosError as e:
807 if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value:
808 # We might face this if the target does not support AES
809 # So, if that's the case we'll force using RC4 by converting
810 # the password to lm/nt hashes and hope for the best. If that's already
811 # done, byebye.
812 if lmhash == '' and nthash == '' and (aesKey == '' or aesKey is None) and TGT is None and TGS is None:
813 from impacket.ntlm import compute_lmhash, compute_nthash
814 LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4')
815 lmhash = compute_lmhash(password)
816 nthash = compute_nthash(password)
817 else:
818 raise
819 else:
820 raise
821 else:
822 break
823 else:
824 tgs = TGS['KDC_REP']
825 cipher = TGS['cipher']
826 sessionKey = TGS['sessionKey']
827 break
829 # Let's build a NegTokenInit with a Kerberos REQ_AP
831 blob = SPNEGO_NegTokenInit()
833 # Kerberos
834 blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
836 # Let's extract the ticket from the TGS
837 tgs = decoder.decode(tgs, asn1Spec = TGS_REP())[0]
838 ticket = Ticket()
839 ticket.from_asn1(tgs['ticket'])
841 # Now let's build the AP_REQ
842 apReq = AP_REQ()
843 apReq['pvno'] = 5
844 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
846 opts = list()
847 apReq['ap-options'] = constants.encodeFlags(opts)
848 seq_set(apReq,'ticket', ticket.to_asn1)
850 authenticator = Authenticator()
851 authenticator['authenticator-vno'] = 5
852 authenticator['crealm'] = domain
853 seq_set(authenticator, 'cname', userName.components_to_asn1)
854 now = datetime.datetime.utcnow()
856 authenticator['cusec'] = now.microsecond
857 authenticator['ctime'] = KerberosTime.to_asn1(now)
859 encodedAuthenticator = encoder.encode(authenticator)
861 # Key Usage 11
862 # AP-REQ Authenticator (includes application authenticator
863 # subkey), encrypted with the application session key
864 # (Section 5.5.1)
865 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None)
867 apReq['authenticator'] = noValue
868 apReq['authenticator']['etype'] = cipher.enctype
869 apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
871 blob['MechToken'] = encoder.encode(apReq)
873 login['OptionFlags2'] |= TDS_INTEGRATED_SECURITY_ON
875 login['SSPI'] = blob.getData()
876 login['Length'] = len(login.getData())
878 # Send the NTLMSSP Negotiate or SQL Auth Packet
879 self.sendTDS(TDS_LOGIN7, login.getData())
881 # According to the specs, if encryption is not required, we must encrypt just
882 # the first Login packet :-o
883 if resp['Encryption'] == TDS_ENCRYPT_OFF:
884 self.tlsSocket = None
886 tds = self.recvTDS()
888 self.replies = self.parseReply(tds['Data'])
890 if TDS_LOGINACK_TOKEN in self.replies:
891 return True
892 else:
893 return False
895 def login(self, database, username, password='', domain='', hashes = None, useWindowsAuth = False):
897 if hashes is not None:
898 lmhash, nthash = hashes.split(':')
899 lmhash = binascii.a2b_hex(lmhash)
900 nthash = binascii.a2b_hex(nthash)
901 else:
902 lmhash = ''
903 nthash = ''
905 resp = self.preLogin()
906 # Test this!
907 if resp['Encryption'] == TDS_ENCRYPT_REQ or resp['Encryption'] == TDS_ENCRYPT_OFF:
908 LOG.info("Encryption required, switching to TLS")
910 # Switching to TLS now
911 ctx = SSL.Context(SSL.TLSv1_METHOD)
912 ctx.set_cipher_list('RC4, AES256')
913 tls = SSL.Connection(ctx,None)
914 tls.set_connect_state()
915 while True:
916 try:
917 tls.do_handshake()
918 except SSL.WantReadError:
919 data = tls.bio_read(4096)
920 self.sendTDS(TDS_PRE_LOGIN, data,0)
921 tds = self.recvTDS()
922 tls.bio_write(tds['Data'])
923 else:
924 break
926 # SSL and TLS limitation: Secure Socket Layer (SSL) and its replacement,
927 # Transport Layer Security(TLS), limit data fragments to 16k in size.
928 self.packetSize = 16*1024-1
929 self.tlsSocket = tls
932 login = TDS_LOGIN()
934 login['HostName'] = (''.join([random.choice(string.ascii_letters) for i in range(8)])).encode('utf-16le')
935 login['AppName'] = (''.join([random.choice(string.ascii_letters) for i in range(8)])).encode('utf-16le')
936 login['ServerName'] = self.server.encode('utf-16le')
937 login['CltIntName'] = login['AppName']
938 login['ClientPID'] = random.randint(0,1024)
939 login['PacketSize'] = self.packetSize
940 if database is not None:
941 login['Database'] = database.encode('utf-16le')
942 login['OptionFlags2'] = TDS_INIT_LANG_FATAL | TDS_ODBC_ON
944 if useWindowsAuth is True:
945 login['OptionFlags2'] |= TDS_INTEGRATED_SECURITY_ON
946 # NTLMSSP Negotiate
947 auth = ntlm.getNTLMSSPType1('','')
948 login['SSPI'] = auth.getData()
949 else:
950 login['UserName'] = username.encode('utf-16le')
951 login['Password'] = self.encryptPassword(password.encode('utf-16le'))
952 login['SSPI'] = ''
955 login['Length'] = len(login.getData())
957 # Send the NTLMSSP Negotiate or SQL Auth Packet
958 self.sendTDS(TDS_LOGIN7, login.getData())
960 # According to the specs, if encryption is not required, we must encrypt just
961 # the first Login packet :-o
962 if resp['Encryption'] == TDS_ENCRYPT_OFF:
963 self.tlsSocket = None
965 tds = self.recvTDS()
968 if useWindowsAuth is True:
969 serverChallenge = tds['Data'][3:]
971 # Generate the NTLM ChallengeResponse AUTH
972 type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, serverChallenge, username, password, domain, lmhash, nthash)
974 self.sendTDS(TDS_SSPI, type3.getData())
975 tds = self.recvTDS()
977 self.replies = self.parseReply(tds['Data'])
979 if TDS_LOGINACK_TOKEN in self.replies:
980 return True
981 else:
982 return False
985 def processColMeta(self):
986 for col in self.colMeta:
987 if col['Type'] in [TDS_NVARCHARTYPE, TDS_NCHARTYPE, TDS_NTEXTTYPE]:
988 col['Length'] = col['TypeData']//2
989 fmt = '%%-%ds'
990 elif col['Type'] in [TDS_GUIDTYPE]:
991 col['Length'] = 36
992 fmt = '%%%ds'
993 elif col['Type'] in [TDS_DECIMALNTYPE,TDS_NUMERICNTYPE]:
994 col['Length'] = ord(col['TypeData'][0:1])
995 fmt = '%%%ds'
996 elif col['Type'] in [TDS_DATETIMNTYPE]:
997 col['Length'] = 19
998 fmt = '%%-%ds'
999 elif col['Type'] in [TDS_INT4TYPE, TDS_INTNTYPE]:
1000 col['Length'] = 11
1001 fmt = '%%%ds'
1002 elif col['Type'] in [TDS_FLTNTYPE, TDS_MONEYNTYPE]:
1003 col['Length'] = 25
1004 fmt = '%%%ds'
1005 elif col['Type'] in [TDS_BITNTYPE, TDS_BIGCHARTYPE]:
1006 col['Length'] = col['TypeData']
1007 fmt = '%%%ds'
1008 elif col['Type'] in [TDS_BIGBINARYTYPE, TDS_BIGVARBINTYPE]:
1009 col['Length'] = col['TypeData'] * 2
1010 fmt = '%%%ds'
1011 elif col['Type'] in [TDS_TEXTTYPE, TDS_BIGVARCHRTYPE]:
1012 col['Length'] = col['TypeData']
1013 fmt = '%%-%ds'
1014 else:
1015 col['Length'] = 10
1016 fmt = '%%%ds'
1018 if len(col['Name']) > col['Length']:
1019 col['Length'] = len(col['Name'])
1020 elif col['Length'] > self.MAX_COL_LEN:
1021 col['Length'] = self.MAX_COL_LEN
1023 col['Format'] = fmt % col['Length']
1026 def printColumnsHeader(self):
1027 if len(self.colMeta) == 0:
1028 return
1029 for col in self.colMeta:
1030 self.__rowsPrinter.logMessage(col['Format'] % col['Name'] + self.COL_SEPARATOR)
1031 self.__rowsPrinter.logMessage('\n')
1032 for col in self.colMeta:
1033 self.__rowsPrinter.logMessage('-'*col['Length'] + self.COL_SEPARATOR)
1034 self.__rowsPrinter.logMessage('\n')
1037 def printRows(self):
1038 if self.lastError is True:
1039 return
1040 self.processColMeta()
1041 self.printColumnsHeader()
1042 for row in self.rows:
1043 for col in self.colMeta:
1044 self.__rowsPrinter.logMessage(col['Format'] % row[col['Name']] + self.COL_SEPARATOR)
1045 self.__rowsPrinter.logMessage('\n')
1047 def printReplies(self):
1048 for keys in list(self.replies.keys()):
1049 for i, key in enumerate(self.replies[keys]):
1050 if key['TokenType'] == TDS_ERROR_TOKEN:
1051 error = "ERROR(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le'))
1052 self.lastError = SQLErrorException("ERROR: Line %d: %s" % (key['LineNumber'], key['MsgText'].decode('utf-16le')))
1053 LOG.error(error)
1055 elif key['TokenType'] == TDS_INFO_TOKEN:
1056 LOG.info("INFO(%s): Line %d: %s" % (key['ServerName'].decode('utf-16le'), key['LineNumber'], key['MsgText'].decode('utf-16le')))
1058 elif key['TokenType'] == TDS_LOGINACK_TOKEN:
1059 LOG.info("ACK: Result: %s - %s (%d%d %d%d) " % (key['Interface'], key['ProgName'].decode('utf-16le'), key['MajorVer'], key['MinorVer'], key['BuildNumHi'], key['BuildNumLow']))
1061 elif key['TokenType'] == TDS_ENVCHANGE_TOKEN:
1062 if key['Type'] in (TDS_ENVCHANGE_DATABASE, TDS_ENVCHANGE_LANGUAGE, TDS_ENVCHANGE_CHARSET, TDS_ENVCHANGE_PACKETSIZE):
1063 record = TDS_ENVCHANGE_VARCHAR(key['Data'])
1064 if record['OldValue'] == '':
1065 record['OldValue'] = 'None'.encode('utf-16le')
1066 elif record['NewValue'] == '':
1067 record['NewValue'] = 'None'.encode('utf-16le')
1068 if key['Type'] == TDS_ENVCHANGE_DATABASE:
1069 _type = 'DATABASE'
1070 elif key['Type'] == TDS_ENVCHANGE_LANGUAGE:
1071 _type = 'LANGUAGE'
1072 elif key['Type'] == TDS_ENVCHANGE_CHARSET:
1073 _type = 'CHARSET'
1074 elif key['Type'] == TDS_ENVCHANGE_PACKETSIZE:
1075 _type = 'PACKETSIZE'
1076 else:
1077 _type = "%d" % key['Type']
1078 LOG.info("ENVCHANGE(%s): Old Value: %s, New Value: %s" % (_type,record['OldValue'].decode('utf-16le'), record['NewValue'].decode('utf-16le')))
1080 def parseRow(self,token,tuplemode=False):
1081 # TODO: This REALLY needs to be improved. Right now we don't support correctly all the data types
1082 # help would be appreciated ;)
1083 if len(token) == 1:
1084 return 0
1086 row = [] if tuplemode else {}
1088 origDataLen = len(token['Data'])
1089 data = token['Data']
1090 for col in self.colMeta:
1091 _type = col['Type']
1092 if (_type == TDS_NVARCHARTYPE) |\
1093 (_type == TDS_NCHARTYPE):
1094 #print "NVAR 0x%x" % _type
1095 charLen = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1096 data = data[struct.calcsize('<H'):]
1097 if charLen != 0xFFFF:
1098 value = data[:charLen].decode('utf-16le')
1099 data = data[charLen:]
1100 else:
1101 value = 'NULL'
1103 elif _type == TDS_BIGVARCHRTYPE:
1104 charLen = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1105 data = data[struct.calcsize('<H'):]
1106 if charLen != 0xFFFF:
1107 value = data[:charLen]
1108 data = data[charLen:]
1109 else:
1110 value = 'NULL'
1112 elif _type == TDS_GUIDTYPE:
1113 uuidLen = ord(data[0:1])
1114 data = data[1:]
1115 if uuidLen > 0:
1116 uu = data[:uuidLen]
1117 value = uuid.bin_to_string(uu)
1118 data = data[uuidLen:]
1119 else:
1120 value = 'NULL'
1122 elif (_type == TDS_NTEXTTYPE) |\
1123 (_type == TDS_IMAGETYPE) :
1124 # Skip the pointer data
1125 charLen = ord(data[0:1])
1126 if charLen == 0:
1127 value = 'NULL'
1128 data = data[1:]
1129 else:
1130 data = data[1+charLen+8:]
1131 charLen = struct.unpack('<L',data[:struct.calcsize('<L')])[0]
1132 data = data[struct.calcsize('<L'):]
1133 if charLen != 0xFFFF:
1134 if _type == TDS_NTEXTTYPE:
1135 value = data[:charLen].decode('utf-16le')
1136 else:
1137 value = binascii.b2a_hex(data[:charLen])
1138 data = data[charLen:]
1139 else:
1140 value = 'NULL'
1142 elif _type == TDS_TEXTTYPE:
1143 # Skip the pointer data
1144 charLen = ord(data[0:1])
1145 if charLen == 0:
1146 value = 'NULL'
1147 data = data[1:]
1148 else:
1149 data = data[1+charLen+8:]
1150 charLen = struct.unpack('<L',data[:struct.calcsize('<L')])[0]
1151 data = data[struct.calcsize('<L'):]
1152 if charLen != 0xFFFF:
1153 value = data[:charLen]
1154 data = data[charLen:]
1155 else:
1156 value = 'NULL'
1158 elif (_type == TDS_BIGVARBINTYPE) |\
1159 (_type == TDS_BIGBINARYTYPE):
1160 charLen = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1161 data = data[struct.calcsize('<H'):]
1162 if charLen != 0xFFFF:
1163 value = binascii.b2a_hex(data[:charLen])
1164 data = data[charLen:]
1165 else:
1166 value = 'NULL'
1168 elif (_type == TDS_DATETIM4TYPE) |\
1169 (_type == TDS_DATETIMNTYPE) |\
1170 (_type == TDS_DATETIMETYPE):
1171 value = ''
1172 if _type == TDS_DATETIMNTYPE:
1173 # For DATETIMNTYPE, the only valid lengths are 0x04 and 0x08, which map to smalldatetime and
1174 # datetime SQL data _types respectively.
1175 if ord(data[0:1]) == 4:
1176 _type = TDS_DATETIM4TYPE
1177 elif ord(data[0:1]) == 8:
1178 _type = TDS_DATETIMETYPE
1179 else:
1180 value = 'NULL'
1181 data = data[1:]
1182 if _type == TDS_DATETIMETYPE:
1183 # datetime is represented in the following sequence:
1184 # * One 4-byte signed integer that represents the number of days since January 1, 1900. Negative
1185 # numbers are allowed to represents dates since January 1, 1753.
1186 # * One 4-byte unsigned integer that represents the number of one three-hundredths of a second
1187 # (300 counts per second) elapsed since 12 AM that day.
1188 dateValue = struct.unpack('<l',data[:4])[0]
1189 data = data[4:]
1190 if dateValue < 0:
1191 baseDate = datetime.date(1753,1,1)
1192 else:
1193 baseDate = datetime.date(1900,1,1)
1194 timeValue = struct.unpack('<L',data[:4])[0]
1195 data = data[4:]
1196 elif _type == TDS_DATETIM4TYPE:
1197 # Small datetime
1198 # 2.2.5.5.1.8
1199 # Date/Times
1200 # smalldatetime is represented in the following sequence:
1201 # * One 2-byte unsigned integer that represents the number of days since January 1, 1900.
1202 # * One 2-byte unsigned integer that represents the number of minutes elapsed since 12 AM that
1203 # day.
1204 dateValue = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1205 data = data[struct.calcsize('<H'):]
1206 timeValue = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1207 data = data[struct.calcsize('<H'):]
1208 baseDate = datetime.date(1900,1,1)
1209 if value != 'NULL':
1210 dateValue = datetime.date.fromordinal(baseDate.toordinal() + dateValue)
1211 hours, mod = divmod(timeValue//300, 60*60)
1212 minutes, second = divmod(mod, 60)
1213 value = datetime.datetime(dateValue.year, dateValue.month, dateValue.day, hours, minutes, second)
1215 elif (_type == TDS_INT4TYPE) |\
1216 (_type == TDS_MONEY4TYPE) |\
1217 (_type == TDS_FLT4TYPE):
1218 #print "INT4"
1219 value = struct.unpack('<l',data[:struct.calcsize('<l')])[0]
1220 data = data[struct.calcsize('<l'):]
1222 elif _type == TDS_FLTNTYPE:
1223 valueSize = ord(data[:1])
1224 if valueSize == 4:
1225 fmt = '<f'
1226 elif valueSize == 8:
1227 fmt = '<d'
1229 data = data[1:]
1231 if valueSize > 0:
1232 value = struct.unpack(fmt,data[:valueSize])[0]
1233 data = data[valueSize:]
1234 else:
1235 value = 'NULL'
1237 elif _type == TDS_MONEYNTYPE:
1238 valueSize = ord(data[:1])
1239 if valueSize == 4:
1240 fmt = '<l'
1241 elif valueSize == 8:
1242 fmt = '<q'
1244 data = data[1:]
1246 if valueSize > 0:
1247 value = struct.unpack(fmt,data[:valueSize])[0]
1248 if valueSize == 4:
1249 value = float(value) // math.pow(10,4)
1250 else:
1251 value = float(value >> 32) // math.pow(10,4)
1252 data = data[valueSize:]
1253 else:
1254 value = 'NULL'
1257 elif _type == TDS_BIGCHARTYPE:
1258 #print "BIGC"
1259 charLen = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1260 data = data[struct.calcsize('<H'):]
1261 value = data[:charLen]
1262 data = data[charLen:]
1264 elif (_type == TDS_INT8TYPE) |\
1265 (_type == TDS_FLT8TYPE) |\
1266 (_type == TDS_MONEYTYPE):
1267 #print "DATETIME"
1268 value = struct.unpack('<q',data[:struct.calcsize('<q')])[0]
1269 data = data[struct.calcsize('<q'):]
1272 elif _type == TDS_INT2TYPE:
1273 #print "INT2TYPE"
1274 value = struct.unpack('<H',(data[:2]))[0]
1275 data = data[2:]
1277 elif _type == TDS_DATENTYPE:
1278 # date is represented as one 3-byte unsigned integer that represents the number of days since
1279 # January 1, year 1.
1280 valueSize = ord(data[:1])
1281 data = data[1:]
1282 if valueSize > 0:
1283 dateBytes = data[:valueSize]
1284 dateValue = struct.unpack('<L','\x00'+dateBytes)[0]
1285 value = datetime.date.fromtimestamp(dateValue)
1286 data = data[valueSize:]
1287 else:
1288 value = 'NULL'
1290 elif (_type == TDS_BITTYPE) |\
1291 (_type == TDS_INT1TYPE):
1292 #print "BITTYPE"
1293 value = ord(data[:1])
1294 data = data[1:]
1296 elif (_type == TDS_NUMERICNTYPE) |\
1297 (_type == TDS_DECIMALNTYPE):
1298 valueLen = ord(data[:1])
1299 data = data[1:]
1300 value = data[:valueLen]
1301 data = data[valueLen:]
1302 precision = ord(col['TypeData'][1:2])
1303 scale = ord(col['TypeData'][2:3])
1304 if valueLen > 0:
1305 isPositiveSign = ord(value[0:1])
1306 if (valueLen-1) == 2:
1307 fmt = '<H'
1308 elif (valueLen-1) == 4:
1309 fmt = '<L'
1310 elif (valueLen-1) == 8:
1311 fmt = '<Q'
1312 else:
1313 # Still don't know how to handle higher values
1314 value = "TODO: Interpret TDS_NUMERICNTYPE correctly"
1315 number = struct.unpack(fmt, value[1:])[0]
1316 number //= math.pow(precision, scale)
1317 if isPositiveSign == 0:
1318 number *= -1
1319 value = number
1320 else:
1321 value = 'NULL'
1323 elif _type == TDS_BITNTYPE:
1324 #print "BITNTYPE"
1325 valueSize = ord(data[:1])
1326 data = data[1:]
1327 if valueSize > 0:
1328 if valueSize == 1:
1329 value = ord(data[:valueSize])
1330 else:
1331 value = data[:valueSize]
1332 else:
1333 value = 'NULL'
1334 data = data[valueSize:]
1336 elif _type == TDS_INTNTYPE:
1337 valueSize = ord(data[:1])
1338 if valueSize == 1:
1339 fmt = '<B'
1340 elif valueSize == 2:
1341 fmt = '<h'
1342 elif valueSize == 4:
1343 fmt = '<l'
1344 elif valueSize == 8:
1345 fmt = '<q'
1346 else:
1347 fmt = ''
1349 data = data[1:]
1351 if valueSize > 0:
1352 value = struct.unpack(fmt,data[:valueSize])[0]
1353 data = data[valueSize:]
1354 else:
1355 value = 'NULL'
1356 elif _type == TDS_SSVARIANTTYPE:
1357 raise Exception("ParseRow: SQL Variant type not yet supported :(")
1358 else:
1359 raise Exception("ParseROW: Unsupported data type: 0%x" % _type)
1361 if tuplemode:
1362 row.append(value)
1363 else:
1364 row[col['Name']] = value
1367 self.rows.append(row)
1369 return origDataLen - len(data)
1371 def parseColMetaData(self, token):
1372 # TODO Add support for more data types!
1373 count = token['Count']
1374 if count == 0xFFFF:
1375 return 0
1377 self.colMeta = []
1378 origDataLen = len(token['Data'])
1379 data = token['Data']
1380 for i in range(count):
1381 column = {}
1382 userType = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1383 data = data[struct.calcsize('<H'):]
1384 flags = struct.unpack('<H',data[:struct.calcsize('<H')])[0]
1385 data = data[struct.calcsize('<H'):]
1386 colType = struct.unpack('<B',data[:struct.calcsize('<B')])[0]
1387 data = data[struct.calcsize('<B'):]
1388 if (colType == TDS_BITTYPE) |\
1389 (colType == TDS_INT1TYPE) |\
1390 (colType == TDS_INT2TYPE) |\
1391 (colType == TDS_INT8TYPE) |\
1392 (colType == TDS_DATETIMETYPE) |\
1393 (colType == TDS_DATETIM4TYPE) |\
1394 (colType == TDS_FLT4TYPE) |\
1395 (colType == TDS_FLT8TYPE) |\
1396 (colType == TDS_MONEYTYPE) |\
1397 (colType == TDS_MONEY4TYPE) |\
1398 (colType == TDS_DATENTYPE) |\
1399 (colType == TDS_INT4TYPE):
1400 typeData = ''
1401 elif (colType == TDS_INTNTYPE) |\
1402 (colType == TDS_TIMENTYPE) |\
1403 (colType == TDS_DATETIME2NTYPE) |\
1404 (colType == TDS_DATETIMEOFFSETNTYPE) |\
1405 (colType == TDS_FLTNTYPE) |\
1406 (colType == TDS_MONEYNTYPE) |\
1407 (colType == TDS_GUIDTYPE) |\
1408 (colType == TDS_BITNTYPE):
1409 typeData = ord(data[0:1])
1410 data = data[1:]
1412 elif colType == TDS_DATETIMNTYPE:
1413 # For DATETIMNTYPE, the only valid lengths are 0x04 and 0x08, which map to smalldatetime and
1414 # datetime SQL data types respectively.
1415 typeData = ord(data[0:1])
1416 data = data[1:]
1418 elif (colType == TDS_BIGVARBINTYPE) |\
1419 (colType == TDS_BIGBINARYTYPE) |\
1420 (colType == TDS_NCHARTYPE) |\
1421 (colType == TDS_NVARCHARTYPE) |\
1422 (colType == TDS_BIGVARCHRTYPE) |\
1423 (colType == TDS_BIGCHARTYPE):
1424 typeData = struct.unpack('<H',data[:2])[0]
1425 data = data[2:]
1426 elif (colType == TDS_DECIMALNTYPE) |\
1427 (colType == TDS_NUMERICNTYPE) |\
1428 (colType == TDS_DECIMALTYPE):
1429 typeData = data[:3]
1430 data = data[3:]
1431 elif (colType == TDS_IMAGETYPE) |\
1432 (colType == TDS_TEXTTYPE) |\
1433 (colType == TDS_XMLTYPE) |\
1434 (colType == TDS_SSVARIANTTYPE) |\
1435 (colType == TDS_NTEXTTYPE):
1436 typeData = struct.unpack('<L',data[:4])[0]
1437 data = data[4:]
1438 else:
1439 raise Exception("Unsupported data type: 0x%x" % colType)
1441 # Collation exceptions:
1442 if (colType == TDS_NTEXTTYPE) |\
1443 (colType == TDS_BIGCHARTYPE) |\
1444 (colType == TDS_BIGVARCHRTYPE) |\
1445 (colType == TDS_NCHARTYPE) |\
1446 (colType == TDS_NVARCHARTYPE) |\
1447 (colType == TDS_TEXTTYPE):
1448 # Skip collation
1449 data = data[5:]
1451 # PartTableName exceptions:
1452 if (colType == TDS_IMAGETYPE) |\
1453 (colType == TDS_TEXTTYPE) |\
1454 (colType == TDS_NTEXTTYPE):
1455 # This types have Table Elements, we just discard them for now.
1456 # ToDo parse this correctly!
1457 # Get the Length
1458 dataLen = struct.unpack('<H',data[:2])[0]
1459 data = data[2:]
1460 # skip the text
1461 data = data[dataLen*2:]
1463 colNameLength = struct.unpack('<B',data[:struct.calcsize('<B')])[0]
1464 data = data[struct.calcsize('<B'):]
1465 colName = data[:colNameLength*2].decode('utf-16le')
1466 data = data[colNameLength*2:]
1467 column['Name'] = colName
1468 column['Type'] = colType
1469 column['TypeData'] = typeData
1470 column['Flags'] = flags
1471 self.colMeta.append(column)
1473 return origDataLen - len(data)
1475 def parseReply(self, tokens,tuplemode=False):
1476 if len(tokens) == 0:
1477 return False
1479 replies = {}
1480 while len(tokens) > 0:
1481 tokenID = struct.unpack('B',tokens[0:1])[0]
1482 if tokenID == TDS_ERROR_TOKEN:
1483 token = TDS_INFO_ERROR(tokens)
1484 elif tokenID == TDS_RETURNSTATUS_TOKEN:
1485 token = TDS_RETURNSTATUS(tokens)
1486 elif tokenID == TDS_INFO_TOKEN:
1487 token = TDS_INFO_ERROR(tokens)
1488 elif tokenID == TDS_LOGINACK_TOKEN:
1489 token = TDS_LOGIN_ACK(tokens)
1490 elif tokenID == TDS_ENVCHANGE_TOKEN:
1491 token = TDS_ENVCHANGE(tokens)
1492 if token['Type'] is TDS_ENVCHANGE_PACKETSIZE:
1493 record = TDS_ENVCHANGE_VARCHAR(token['Data'])
1494 self.packetSize = int( record['NewValue'].decode('utf-16le') )
1495 elif token['Type'] is TDS_ENVCHANGE_DATABASE:
1496 record = TDS_ENVCHANGE_VARCHAR(token['Data'])
1497 self.currentDB = record['NewValue'].decode('utf-16le')
1499 elif (tokenID == TDS_DONEINPROC_TOKEN) |\
1500 (tokenID == TDS_DONEPROC_TOKEN):
1501 token = TDS_DONEINPROC(tokens)
1502 elif tokenID == TDS_ORDER_TOKEN:
1503 token = TDS_ORDER(tokens)
1504 elif tokenID == TDS_ROW_TOKEN:
1505 #print "ROW"
1506 token = TDS_ROW(tokens)
1507 tokenLen = self.parseRow(token,tuplemode)
1508 token['Data'] = token['Data'][:tokenLen]
1509 elif tokenID == TDS_COLMETADATA_TOKEN:
1510 #print "COLMETA"
1511 token = TDS_COLMETADATA(tokens)
1512 tokenLen = self.parseColMetaData(token)
1513 token['Data'] = token['Data'][:tokenLen]
1514 elif tokenID == TDS_DONE_TOKEN:
1515 token = TDS_DONE(tokens)
1516 else:
1517 LOG.error("Unknown Token %x" % tokenID)
1518 return replies
1520 if (tokenID in replies) is not True:
1521 replies[tokenID] = list()
1523 replies[tokenID].append(token)
1524 tokens = tokens[len(token):]
1525 #print "TYPE 0x%x, LEN: %d" %(tokenID, len(token))
1526 #print repr(tokens[:10])
1528 return replies
1530 def batch(self, cmd,tuplemode=False,wait=True):
1531 # First of all we clear the rows, colMeta and lastError
1532 self.rows = []
1533 self.colMeta = []
1534 self.lastError = False
1535 self.sendTDS(TDS_SQL_BATCH, (cmd+'\r\n').encode('utf-16le'))
1536 if wait:
1537 tds = self.recvTDS()
1538 self.replies = self.parseReply(tds['Data'],tuplemode)
1539 return self.rows
1540 else:
1541 return True
1544 def batchStatement(self, cmd,tuplemode=False):
1545 # First of all we clear the rows, colMeta and lastError
1546 self.rows = []
1547 self.colMeta = []
1548 self.lastError = False
1549 self.sendTDS(TDS_SQL_BATCH, (cmd+'\r\n').encode('utf-16le'))
1550 #self.recvTDS()
1553 # Handy alias
1554 sql_query = batch
1556 def changeDB(self, db):
1557 if db != self.currentDB:
1558 chdb = 'use %s' % db
1559 self.batch(chdb)
1560 self.printReplies()
1562 def RunSQLQuery(self,db,sql_query,tuplemode=False,wait=True,**kwArgs):
1563 db = db or 'master'
1564 self.changeDB(db)
1565 self.printReplies()
1566 ret = self.batch(sql_query,tuplemode,wait)
1567 if wait:
1568 self.printReplies()
1569 if self.lastError:
1570 raise self.lastError
1571 if self.lastError:
1572 raise self.lastError
1573 return ret
1575 def RunSQLStatement(self,db,sql_query,wait=True,**kwArgs):
1576 self.RunSQLQuery(db,sql_query,wait=wait)
1577 if self.lastError:
1578 raise self.lastError
1579 return True