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