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

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

## 974 statements

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#

19

20 from __future__ import division

21 from __future__ import print_function

22 import struct

23 import socket

24 import select

25 import random

26 import binascii

27 import math

28 import datetime

29 import string

30

31 from impacket import ntlm, uuid, LOG

32 from impacket.structure import Structure

33

34 try:

35 from OpenSSL import SSL

36 except:

37 LOG.critical("pyOpenSSL is not installed, can't continue")

38 raise

39

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.

44 class DummyPrint:

45 def logMessage(self,message):

46 if message == '\n':

47 print(message)

48 else:

49 print(message, end=' ')

50

51# MC-SQLR Constants and Structures

52 SQLR_PORT = 1434

53 SQLR_CLNT_BCAST_EX = 0x02

54 SQLR_CLNT_UCAST_EX = 0x03

55 SQLR_CLNT_UCAST_INST= 0x04

56 SQLR_CLNT_UCAST_DAC = 0x0f

57

58

59 class SQLR(Structure):

60 commonHdr = (

61 ('OpCode','B'),

62 )

63

64 class 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

72

73 class 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

82

83 class SQLR_Response(SQLR):

84 structure = (

85 ('Size','<H'),

86 ('_Data','_-Data','self["Size"]'),

87 ('Data',':'),

88 )

89

90 class SQLErrorException(Exception):

91 pass

92

93# TDS Constants and Structures

94

95# TYPE constants

96 TDS_SQL_BATCH = 1

97 TDS_PRE_TDS_LOGIN = 2

98 TDS_RPC = 3

99 TDS_TABULAR = 4

100 TDS_ATTENTION = 6

101 TDS_BULK_LOAD_DATA = 7

102 TDS_TRANSACTION = 14

103 TDS_LOGIN7 = 16

104 TDS_SSPI = 17

105 TDS_PRE_LOGIN = 18

106

107# Status constants

108 TDS_STATUS_NORMAL = 0

109 TDS_STATUS_EOM = 1

110 TDS_STATUS_RESET_CONNECTION = 8

111 TDS_STATUS_RESET_SKIPTRANS = 16

112

113# Encryption

114 TDS_ENCRYPT_OFF = 0

115 TDS_ENCRYPT_ON = 1

116 TDS_ENCRYPT_NOT_SUP = 2

117 TDS_ENCRYPT_REQ = 3

118

119# Option 2 Flags

120 TDS_INTEGRATED_SECURITY_ON = 0x80

121 TDS_INIT_LANG_FATAL = 0x01

122 TDS_ODBC_ON = 0x02

123

124# Token Types

125 TDS_ALTMETADATA_TOKEN = 0x88

126 TDS_ALTROW_TOKEN = 0xD3

127 TDS_COLMETADATA_TOKEN = 0x81

128 TDS_COLINFO_TOKEN = 0xA5

129 TDS_DONE_TOKEN = 0xFD

130 TDS_DONEPROC_TOKEN = 0xFE

131 TDS_DONEINPROC_TOKEN = 0xFF

132 TDS_ENVCHANGE_TOKEN = 0xE3

133 TDS_ERROR_TOKEN = 0xAA

134 TDS_INFO_TOKEN = 0xAB

135 TDS_LOGINACK_TOKEN = 0xAD

136 TDS_NBCROW_TOKEN = 0xD2

137 TDS_OFFSET_TOKEN = 0x78

138 TDS_ORDER_TOKEN = 0xA9

139 TDS_RETURNSTATUS_TOKEN = 0x79

140 TDS_RETURNVALUE_TOKEN = 0xAC

141 TDS_ROW_TOKEN = 0xD1

142 TDS_SSPI_TOKEN = 0xED

143 TDS_TABNAME_TOKEN = 0xA4

144

145# ENVCHANGE Types

146 TDS_ENVCHANGE_DATABASE = 1

147 TDS_ENVCHANGE_LANGUAGE = 2

148 TDS_ENVCHANGE_CHARSET = 3

149 TDS_ENVCHANGE_PACKETSIZE = 4

150 TDS_ENVCHANGE_UNICODE = 5

151 TDS_ENVCHANGE_UNICODE_DS = 6

152 TDS_ENVCHANGE_COLLATION = 7

153 TDS_ENVCHANGE_TRANS_START = 8

154 TDS_ENVCHANGE_TRANS_COMMIT = 9

155 TDS_ENVCHANGE_ROLLBACK = 10

156 TDS_ENVCHANGE_DTC = 11

157

158# Column types

159# FIXED-LEN Data Types

160 TDS_NULL_TYPE = 0x1F

161 TDS_INT1TYPE = 0x30

162 TDS_BITTYPE = 0x32

163 TDS_INT2TYPE = 0x34

164 TDS_INT4TYPE = 0x38

165 TDS_DATETIM4TYPE = 0x3A

166 TDS_FLT4TYPE = 0x3B

167 TDS_MONEYTYPE = 0x3C

168 TDS_DATETIMETYPE = 0x3D

169 TDS_FLT8TYPE = 0x3E

170 TDS_MONEY4TYPE = 0x7A

171 TDS_INT8TYPE = 0x7F

172

173# VARIABLE-Len Data Types

174 TDS_GUIDTYPE = 0x24

175 TDS_INTNTYPE = 0x26

176 TDS_DECIMALTYPE = 0x37

177 TDS_NUMERICTYPE = 0x3F

178 TDS_BITNTYPE = 0x68

179 TDS_DECIMALNTYPE = 0x6A

180 TDS_NUMERICNTYPE = 0x6C

181 TDS_FLTNTYPE = 0x6D

182 TDS_MONEYNTYPE = 0x6E

183 TDS_DATETIMNTYPE = 0x6F

184 TDS_DATENTYPE = 0x28

185 TDS_TIMENTYPE = 0x29

186 TDS_DATETIME2NTYPE = 0x2A

187 TDS_DATETIMEOFFSETNTYPE = 0x2B

188 TDS_CHARTYPE = 0x2F

189 TDS_VARCHARTYPE = 0x27

190 TDS_BINARYTYPE = 0x2D

191 TDS_VARBINARYTYPE = 0x25

192 TDS_BIGVARBINTYPE = 0xA5

193 TDS_BIGVARCHRTYPE = 0xA7

194 TDS_BIGBINARYTYPE = 0xAD

195 TDS_BIGCHARTYPE = 0xAF

196 TDS_NVARCHARTYPE = 0xE7

197 TDS_NCHARTYPE = 0xEF

198 TDS_XMLTYPE = 0xF1

199 TDS_UDTTYPE = 0xF0

200 TDS_TEXTTYPE = 0x23

201 TDS_IMAGETYPE = 0x22

202 TDS_NTEXTTYPE = 0x63

203 TDS_SSVARIANTTYPE = 0x62

204

205 class 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 )

215

216 class 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 )

238

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)

245

246 class 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'] = ''

300

301 def fromString(self, data):

302 Structure.fromString(self, data)

303 if self['HostNameLength'] > 0:

304 self['HostName'] = data[self['HostNameOffset']:][:self['HostNameLength']*2]

305

306 if self['UserNameLength'] > 0:

307 self['UserName'] = data[self['UserNameOffset']:][:self['UserNameLength']*2]

308

309 if self['PasswordLength'] > 0:

310 self['Password'] = data[self['PasswordOffset']:][:self['PasswordLength']*2]

311

312 if self['AppNameLength'] > 0:

313 self['AppName'] = data[self['AppNameOffset']:][:self['AppNameLength']*2]

314

315 if self['ServerNameLength'] > 0:

316 self['ServerName'] = data[self['ServerNameOffset']:][:self['ServerNameLength']*2]

317

318 if self['CltIntNameLength'] > 0:

319 self['CltIntName'] = data[self['CltIntNameOffset']:][:self['CltIntNameLength']*2]

320

321 if self['DatabaseLength'] > 0:

322 self['Database'] = data[self['DatabaseOffset']:][:self['DatabaseLength']*2]

323

324 if self['SSPILength'] > 0:

325 self['SSPI'] = data[self['SSPIOffset']:][:self['SSPILength']*2]

326

327 if self['AtchDBFileLength'] > 0:

328 self['AtchDBFile'] = data[self['AtchDBFileOffset']:][:self['AtchDBFileLength']*2]

329

330 def getData(self):

331 index = 36+50

332 self['HostNameOffset']= index

333

334 index += len(self['HostName'])

335

336 if self['UserName'] != '':

337 self['UserNameOffset'] = index

338 else:

339 self['UserNameOffset'] = 0

340

341 index += len(self['UserName'])

342

343 if self['Password'] != '':

344 self['PasswordOffset'] = index

345 else:

346 self['PasswordOffset'] = 0

347

348 index += len(self['Password'])

349

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)

358

359 class 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 )

373

374 class TDS_RETURNSTATUS(Structure):

375 structure = (

376 ('TokenType','<B'),

377 ('Value','<L'),

378 )

379

380 class 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 )

398

399 class 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 )

407

408 class TDS_DONEINPROC(Structure):

409 structure = (

410 ('TokenType','<B'),

411 ('Status','<H'),

412 ('CurCmd','<H'),

413 ('DoneRowCount','<L'),

414 )

415

416 class TDS_ORDER(Structure):

417 structure = (

418 ('TokenType','<B'),

419 ('Length','<H'),

420 ('_Data','_-Data','self["Length"]'),

421 ('Data',':'),

422 )

423

424

425 class 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 )

434

435 class TDS_ROW(Structure):

436 structure = (

437 ('TokenType','<B'),

438 ('Data',':'),

439 )

440

441 class TDS_DONE(Structure):

442 structure = (

443 ('TokenType','<B'),

444 ('Status','<H'),

445 ('CurCmd','<H'),

446 ('DoneRowCount','<L'),

447 )

448

449 class TDS_COLMETADATA(Structure):

450 structure = (

451 ('TokenType','<B'),

452 ('Count','<H'),

453 ('Data',':'),

454 )

455

456 class 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

472

473 def getInstances(self, timeout = 5):

474 packet = SQLR()

475 packet['OpCode'] = SQLR_CLNT_UCAST_EX

476

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)

480

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)

487

488 s.close()

489 resp = SQLR_Response(data)

490

491 # Now parse the results

492 entries = resp['Data'].split(b';;')

493

494 # We don't want the last one, it's empty

495 entries.pop()

496

497 # the answer to send back

498 resp = []

499

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)

507

508 return resp

509

510

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'

518

519 self.sendTDS(TDS_PRE_LOGIN, prelogin.getData(), 0)

520 tds = self.recvTDS()

521

522 return TDS_PRELOGIN(tds['Data'])

523

524 def encryptPassword(self, password ):

525 return bytes(bytearray([((x & 0x0f) << 4) + ((x & 0xf0) >> 4) ^ 0xa5 for x in bytearray(password)]))

526

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)

530

531 try:

532 sock.connect(sa)

533 except Exception:

534 #import traceback

535 #traceback.print_exc()

536 raise

537

538 self.socket = sock

539 return sock

540

541 def disconnect(self):

542 if self.socket:

543 return self.socket.close()

544

545 def setPacketSize(self, packetSize):

546 self.packetSize = packetSize

547

548 def getPacketSize(self):

549 return self.packetSize

550

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)

558

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())

568

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

577

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())

584

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

601

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

612

613 remaining = None

614 if packetLen < len(packet['Data']):

615 remaining = packet['Data'][packetLen:]

616 packet['Data'] = packet['Data'][:packetLen]

617

618 #print "REMAINING ",

619 #if remaining is None:

620 # print None

621 #else:

622 # print len(remaining)

623

624 while status != TDS_STATUS_EOM:

625 if remaining is not None:

626 tmpPacket = TDSPacket(remaining)

627 else:

628 tmpPacket = TDSPacket(self.socketRecv(packetSize))

629

630 packetLen = tmpPacket['Length'] - 8

631 while packetLen > len(tmpPacket['Data']):

632 data = self.socketRecv(packetSize)

633 tmpPacket['Data'] += data

634

635 remaining = None

636 if packetLen < len(tmpPacket['Data']):

637 remaining = tmpPacket['Data'][packetLen:]

638 tmpPacket['Data'] = tmpPacket['Data'][:packetLen]

639

640 status = tmpPacket['Status']

641 packet['Data'] += tmpPacket['Data']

642 packet['Length'] += tmpPacket['Length'] - 8

643

644 #print packet['Length']

645 return packet

646

647 def kerberosLogin(self, database, username, password='', domain='', hashes=None, aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True):

648

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 = ''

656

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")

661

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

677

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

682

683

684 login = TDS_LOGIN()

685

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

695

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

707

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)

719

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)

723

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

738

739 if instanceName:

740 principal = 'MSSQLSvc/%s.%s:%s@%s' % (self.server, domain, instanceName, domain.upper())

741 creds = ccache.getCredential(principal)

742

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. ")

755

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)

763

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']

791

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

828

829 # Let's build a NegTokenInit with a Kerberos REQ_AP

830

831 blob = SPNEGO_NegTokenInit()

832

833 # Kerberos

834 blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]

835

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'])

840

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)

845

846 opts = list()

847 apReq['ap-options'] = constants.encodeFlags(opts)

848 seq_set(apReq,'ticket', ticket.to_asn1)

849

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()

855

856 authenticator['cusec'] = now.microsecond

857 authenticator['ctime'] = KerberosTime.to_asn1(now)

858

859 encodedAuthenticator = encoder.encode(authenticator)

860

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)

866

867 apReq['authenticator'] = noValue

868 apReq['authenticator']['etype'] = cipher.enctype

869 apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator

870

871 blob['MechToken'] = encoder.encode(apReq)

872

873 login['OptionFlags2'] |= TDS_INTEGRATED_SECURITY_ON

874

875 login['SSPI'] = blob.getData()

876 login['Length'] = len(login.getData())

877

878 # Send the NTLMSSP Negotiate or SQL Auth Packet

879 self.sendTDS(TDS_LOGIN7, login.getData())

880

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

885

886 tds = self.recvTDS()

887

888 self.replies = self.parseReply(tds['Data'])

889

890 if TDS_LOGINACK_TOKEN in self.replies:

891 return True

892 else:

893 return False

894

895 def login(self, database, username, password='', domain='', hashes = None, useWindowsAuth = False):

896

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 = ''

904

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")

909

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

925

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

930

931

932 login = TDS_LOGIN()

933

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

943

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'] = ''

953

954

955 login['Length'] = len(login.getData())

956

957 # Send the NTLMSSP Negotiate or SQL Auth Packet

958 self.sendTDS(TDS_LOGIN7, login.getData())

959

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

964

965 tds = self.recvTDS()

966

967

968 if useWindowsAuth is True:

969 serverChallenge = tds['Data'][3:]

970

971 # Generate the NTLM ChallengeResponse AUTH

972 type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, serverChallenge, username, password, domain, lmhash, nthash)

973

974 self.sendTDS(TDS_SSPI, type3.getData())

975 tds = self.recvTDS()

976

977 self.replies = self.parseReply(tds['Data'])

978

979 if TDS_LOGINACK_TOKEN in self.replies:

980 return True

981 else:

982 return False

983

984

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'

1017

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

1022

1023 col['Format'] = fmt % col['Length']

1024

1025

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')

1035

1036

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')

1046

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)

1054

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')))

1057

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']))

1060

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')))

1079

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

1085

1086 row = [] if tuplemode else {}

1087

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'

1102

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'

1111

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'

1121

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'

1141

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'

1157

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'

1167

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)

1214

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'):]

1221

1222 elif _type == TDS_FLTNTYPE:

1223 valueSize = ord(data[:1])

1224 if valueSize == 4:

1225 fmt = '<f'

1226 elif valueSize == 8:

1227 fmt = '<d'

1228

1229 data = data[1:]

1230

1231 if valueSize > 0:

1232 value = struct.unpack(fmt,data[:valueSize])[0]

1233 data = data[valueSize:]

1234 else:

1235 value = 'NULL'

1236

1237 elif _type == TDS_MONEYNTYPE:

1238 valueSize = ord(data[:1])

1239 if valueSize == 4:

1240 fmt = '<l'

1241 elif valueSize == 8:

1242 fmt = '<q'

1243

1244 data = data[1:]

1245

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'

1255

1256

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:]

1263

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'):]

1270

1271

1272 elif _type == TDS_INT2TYPE:

1273 #print "INT2TYPE"

1274 value = struct.unpack('<H',(data[:2]))[0]

1275 data = data[2:]

1276

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'

1289

1290 elif (_type == TDS_BITTYPE) |\

1291 (_type == TDS_INT1TYPE):

1292 #print "BITTYPE"

1293 value = ord(data[:1])

1294 data = data[1:]

1295

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'

1322

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:]

1335

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 = ''

1348

1349 data = data[1:]

1350

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)

1360

1361 if tuplemode:

1362 row.append(value)

1363 else:

1364 row[col['Name']] = value

1365

1366

1367 self.rows.append(row)

1368

1369 return origDataLen - len(data)

1370

1371 def parseColMetaData(self, token):

1372 # TODO Add support for more data types!

1373 count = token['Count']

1374 if count == 0xFFFF:

1375 return 0

1376

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:]

1411

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:]

1417

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)

1440

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:]

1450

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:]

1462

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)

1472

1473 return origDataLen - len(data)

1474

1475 def parseReply(self, tokens,tuplemode=False):

1476 if len(tokens) == 0:

1477 return False

1478

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')

1498

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

1519

1520 if (tokenID in replies) is not True:

1521 replies[tokenID] = list()

1522

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])

1527

1528 return replies

1529

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

1542

1543

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()

1551

1552

1553 # Handy alias

1554 sql_query = batch

1555

1556 def changeDB(self, db):

1557 if db != self.currentDB:

1558 chdb = 'use %s' % db

1559 self.batch(chdb)

1560 self.printReplies()

1561

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

1574

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