Hide keyboard shortcuts

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 

20from __future__ import division 

21from __future__ import print_function 

22import struct 

23import socket 

24import select 

25import random 

26import binascii 

27import math 

28import datetime 

29import string 

30 

31from impacket import ntlm, uuid, LOG 

32from impacket.structure import Structure 

33 

34try: 

35 from OpenSSL import SSL 

36except: 

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. 

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

52SQLR_PORT = 1434 

53SQLR_CLNT_BCAST_EX = 0x02 

54SQLR_CLNT_UCAST_EX = 0x03 

55SQLR_CLNT_UCAST_INST= 0x04 

56SQLR_CLNT_UCAST_DAC = 0x0f 

57 

58 

59class SQLR(Structure): 

60 commonHdr = ( 

61 ('OpCode','B'), 

62 ) 

63 

64class SQLR_UCAST_INST(SQLR): 

65 structure = ( 

66 ('Instance',':') 

67 ) 

68 def __init__(self, data = None): 

69 SQLR.__init__(self,data) 

70 if data is not None: 

71 self['OpCode'] = SQLR_CLNT_UCAST_INST 

72 

73class SQLR_UCAST_DAC(SQLR): 

74 structure = ( 

75 ('Protocol', 'B=1'), 

76 ('Instance', ':'), 

77 ) 

78 def __init__(self, data = None): 

79 SQLR.__init__(self,data) 

80 if data is not None: 

81 self['OpCode'] = SQLR_CLNT_UCAST_DAC 

82 

83class SQLR_Response(SQLR): 

84 structure = ( 

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

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

87 ('Data',':'), 

88 ) 

89 

90class SQLErrorException(Exception): 

91 pass 

92 

93# TDS Constants and Structures 

94 

95# TYPE constants 

96TDS_SQL_BATCH = 1 

97TDS_PRE_TDS_LOGIN = 2 

98TDS_RPC = 3 

99TDS_TABULAR = 4 

100TDS_ATTENTION = 6 

101TDS_BULK_LOAD_DATA = 7 

102TDS_TRANSACTION = 14 

103TDS_LOGIN7 = 16 

104TDS_SSPI = 17 

105TDS_PRE_LOGIN = 18 

106 

107# Status constants 

108TDS_STATUS_NORMAL = 0 

109TDS_STATUS_EOM = 1 

110TDS_STATUS_RESET_CONNECTION = 8 

111TDS_STATUS_RESET_SKIPTRANS = 16 

112 

113# Encryption 

114TDS_ENCRYPT_OFF = 0 

115TDS_ENCRYPT_ON = 1 

116TDS_ENCRYPT_NOT_SUP = 2 

117TDS_ENCRYPT_REQ = 3 

118 

119# Option 2 Flags 

120TDS_INTEGRATED_SECURITY_ON = 0x80 

121TDS_INIT_LANG_FATAL = 0x01 

122TDS_ODBC_ON = 0x02 

123 

124# Token Types 

125TDS_ALTMETADATA_TOKEN = 0x88 

126TDS_ALTROW_TOKEN = 0xD3 

127TDS_COLMETADATA_TOKEN = 0x81 

128TDS_COLINFO_TOKEN = 0xA5 

129TDS_DONE_TOKEN = 0xFD 

130TDS_DONEPROC_TOKEN = 0xFE 

131TDS_DONEINPROC_TOKEN = 0xFF 

132TDS_ENVCHANGE_TOKEN = 0xE3 

133TDS_ERROR_TOKEN = 0xAA 

134TDS_INFO_TOKEN = 0xAB 

135TDS_LOGINACK_TOKEN = 0xAD 

136TDS_NBCROW_TOKEN = 0xD2 

137TDS_OFFSET_TOKEN = 0x78 

138TDS_ORDER_TOKEN = 0xA9 

139TDS_RETURNSTATUS_TOKEN = 0x79 

140TDS_RETURNVALUE_TOKEN = 0xAC 

141TDS_ROW_TOKEN = 0xD1 

142TDS_SSPI_TOKEN = 0xED 

143TDS_TABNAME_TOKEN = 0xA4 

144 

145# ENVCHANGE Types 

146TDS_ENVCHANGE_DATABASE = 1 

147TDS_ENVCHANGE_LANGUAGE = 2 

148TDS_ENVCHANGE_CHARSET = 3 

149TDS_ENVCHANGE_PACKETSIZE = 4 

150TDS_ENVCHANGE_UNICODE = 5 

151TDS_ENVCHANGE_UNICODE_DS = 6 

152TDS_ENVCHANGE_COLLATION = 7 

153TDS_ENVCHANGE_TRANS_START = 8 

154TDS_ENVCHANGE_TRANS_COMMIT = 9 

155TDS_ENVCHANGE_ROLLBACK = 10 

156TDS_ENVCHANGE_DTC = 11 

157 

158# Column types 

159# FIXED-LEN Data Types 

160TDS_NULL_TYPE = 0x1F 

161TDS_INT1TYPE = 0x30 

162TDS_BITTYPE = 0x32 

163TDS_INT2TYPE = 0x34 

164TDS_INT4TYPE = 0x38 

165TDS_DATETIM4TYPE = 0x3A 

166TDS_FLT4TYPE = 0x3B 

167TDS_MONEYTYPE = 0x3C 

168TDS_DATETIMETYPE = 0x3D 

169TDS_FLT8TYPE = 0x3E 

170TDS_MONEY4TYPE = 0x7A 

171TDS_INT8TYPE = 0x7F 

172 

173# VARIABLE-Len Data Types 

174TDS_GUIDTYPE = 0x24 

175TDS_INTNTYPE = 0x26 

176TDS_DECIMALTYPE = 0x37 

177TDS_NUMERICTYPE = 0x3F 

178TDS_BITNTYPE = 0x68 

179TDS_DECIMALNTYPE = 0x6A 

180TDS_NUMERICNTYPE = 0x6C 

181TDS_FLTNTYPE = 0x6D 

182TDS_MONEYNTYPE = 0x6E 

183TDS_DATETIMNTYPE = 0x6F 

184TDS_DATENTYPE = 0x28 

185TDS_TIMENTYPE = 0x29 

186TDS_DATETIME2NTYPE = 0x2A 

187TDS_DATETIMEOFFSETNTYPE = 0x2B 

188TDS_CHARTYPE = 0x2F 

189TDS_VARCHARTYPE = 0x27 

190TDS_BINARYTYPE = 0x2D 

191TDS_VARBINARYTYPE = 0x25 

192TDS_BIGVARBINTYPE = 0xA5 

193TDS_BIGVARCHRTYPE = 0xA7 

194TDS_BIGBINARYTYPE = 0xAD 

195TDS_BIGCHARTYPE = 0xAF 

196TDS_NVARCHARTYPE = 0xE7 

197TDS_NCHARTYPE = 0xEF 

198TDS_XMLTYPE = 0xF1 

199TDS_UDTTYPE = 0xF0 

200TDS_TEXTTYPE = 0x23 

201TDS_IMAGETYPE = 0x22 

202TDS_NTEXTTYPE = 0x63 

203TDS_SSVARIANTTYPE = 0x62 

204 

205class TDSPacket(Structure): 

206 structure = ( 

207 ('Type','<B'), 

208 ('Status','<B=1'), 

209 ('Length','>H=8+len(Data)'), 

210 ('SPID','>H=0'), 

211 ('PacketID','<B=0'), 

212 ('Window','<B=0'), 

213 ('Data',':'), 

214 ) 

215 

216class TDS_PRELOGIN(Structure): 

217 structure = ( 

218 ('VersionToken','>B=0'), 

219 ('VersionOffset','>H'), 

220 ('VersionLength','>H=len(self["Version"])'), 

221 ('EncryptionToken','>B=0x1'), 

222 ('EncryptionOffset','>H'), 

223 ('EncryptionLength','>H=1'), 

224 ('InstanceToken','>B=2'), 

225 ('InstanceOffset','>H'), 

226 ('InstanceLength','>H=len(self["Instance"])'), 

227 ('ThreadIDToken','>B=3'), 

228 ('ThreadIDOffset','>H'), 

229 ('ThreadIDLength','>H=4'), 

230 ('EndToken','>B=0xff'), 

231 ('_Version','_-Version','self["VersionLength"]'), 

232 ('Version',':'), 

233 ('Encryption','B'), 

234 ('_Instance','_-Instance','self["InstanceLength"]-1'), 

235 ('Instance',':'), 

236 ('ThreadID',':'), 

237 ) 

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 

246class TDS_LOGIN(Structure): 

247 structure = ( 

248 ('Length','<L=0'), 

249 ('TDSVersion','>L=0x71'), 

250 ('PacketSize','<L=32764'), 

251 ('ClientProgVer','>L=7'), 

252 ('ClientPID','<L=0'), 

253 ('ConnectionID','<L=0'), 

254 ('OptionFlags1','<B=0xe0'), 

255 ('OptionFlags2','<B'), 

256 ('TypeFlags','<B=0'), 

257 ('OptionFlags3','<B=0'), 

258 ('ClientTimeZone','<L=0'), 

259 ('ClientLCID','<L=0'), 

260 ('HostNameOffset','<H'), 

261 ('HostNameLength','<H=len(self["HostName"])//2'), 

262 ('UserNameOffset','<H=0'), 

263 ('UserNameLength','<H=len(self["UserName"])//2'), 

264 ('PasswordOffset','<H=0'), 

265 ('PasswordLength','<H=len(self["Password"])//2'), 

266 ('AppNameOffset','<H'), 

267 ('AppNameLength','<H=len(self["AppName"])//2'), 

268 ('ServerNameOffset','<H'), 

269 ('ServerNameLength','<H=len(self["ServerName"])//2'), 

270 ('UnusedOffset','<H=0'), 

271 ('UnusedLength','<H=0'), 

272 ('CltIntNameOffset','<H'), 

273 ('CltIntNameLength','<H=len(self["CltIntName"])//2'), 

274 ('LanguageOffset','<H=0'), 

275 ('LanguageLength','<H=0'), 

276 ('DatabaseOffset','<H=0'), 

277 ('DatabaseLength','<H=len(self["Database"])//2'), 

278 ('ClientID','6s=b"\x01\x02\x03\x04\x05\x06"'), 

279 ('SSPIOffset','<H'), 

280 ('SSPILength','<H=len(self["SSPI"])'), 

281 ('AtchDBFileOffset','<H'), 

282 ('AtchDBFileLength','<H=len(self["AtchDBFile"])//2'), 

283 ('HostName',':'), 

284 ('UserName',':'), 

285 ('Password',':'), 

286 ('AppName',':'), 

287 ('ServerName',':'), 

288 ('CltIntName',':'), 

289 ('Database',':'), 

290 ('SSPI',':'), 

291 ('AtchDBFile',':'), 

292 ) 

293 def __init__(self,data=None): 

294 Structure.__init__(self,data) 

295 if data is None: 

296 self['UserName'] = '' 

297 self['Password'] = '' 

298 self['Database'] = '' 

299 self['AtchDBFile'] = '' 

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 

359class TDS_LOGIN_ACK(Structure): 

360 structure = ( 

361 ('TokenType','<B'), 

362 ('Length','<H'), 

363 ('Interface','<B'), 

364 ('TDSVersion','<L'), 

365 ('ProgNameLen','<B'), 

366 ('_ProgNameLen','_-ProgName','self["ProgNameLen"]*2'), 

367 ('ProgName',':'), 

368 ('MajorVer','<B'), 

369 ('MinorVer','<B'), 

370 ('BuildNumHi','<B'), 

371 ('BuildNumLow','<B'), 

372 ) 

373 

374class TDS_RETURNSTATUS(Structure): 

375 structure = ( 

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

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

378 ) 

379 

380class TDS_INFO_ERROR(Structure): 

381 structure = ( 

382 ('TokenType','<B'), 

383 ('Length','<H'), 

384 ('Number','<L'), 

385 ('State','<B'), 

386 ('Class','<B'), 

387 ('MsgTextLen','<H'), 

388 ('_MsgTextLen','_-MsgText','self["MsgTextLen"]*2'), 

389 ('MsgText',':'), 

390 ('ServerNameLen','<B'), 

391 ('_ServerNameLen','_-ServerName','self["ServerNameLen"]*2'), 

392 ('ServerName',':'), 

393 ('ProcNameLen','<B'), 

394 ('_ProcNameLen','_-ProcName','self["ProcNameLen"]*2'), 

395 ('ProcName',':'), 

396 ('LineNumber','<H'), 

397 ) 

398 

399class TDS_ENVCHANGE(Structure): 

400 structure = ( 

401 ('TokenType','<B'), 

402 ('Length','<H=4+len(Data)'), 

403 ('Type','<B'), 

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

405 ('Data',':'), 

406 ) 

407 

408class TDS_DONEINPROC(Structure): 

409 structure = ( 

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

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

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

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

414 ) 

415 

416class TDS_ORDER(Structure): 

417 structure = ( 

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

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

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

421 ('Data',':'), 

422 ) 

423 

424 

425class TDS_ENVCHANGE_VARCHAR(Structure): 

426 structure = ( 

427 ('NewValueLen','<B=len(NewValue)'), 

428 ('_NewValue','_-NewValue','self["NewValueLen"]*2'), 

429 ('NewValue',':'), 

430 ('OldValueLen','<B=len(OldValue)'), 

431 ('_OldValue','_-OldValue','self["OldValueLen"]*2'), 

432 ('OldValue',':'), 

433 ) 

434 

435class TDS_ROW(Structure): 

436 structure = ( 

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

438 ('Data',':'), 

439 ) 

440 

441class TDS_DONE(Structure): 

442 structure = ( 

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

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

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

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

447 ) 

448 

449class TDS_COLMETADATA(Structure): 

450 structure = ( 

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

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

453 ('Data',':'), 

454 ) 

455 

456class MSSQL: 

457 def __init__(self, address, port=1433, rowsPrinter=DummyPrint()): 

458 #self.packetSize = 32764 

459 self.packetSize = 32763 

460 self.server = address 

461 self.port = port 

462 self.socket = 0 

463 self.replies = {} 

464 self.colMeta = [] 

465 self.rows = [] 

466 self.currentDB = '' 

467 self.COL_SEPARATOR = ' ' 

468 self.MAX_COL_LEN = 255 

469 self.lastError = False 

470 self.tlsSocket = None 

471 self.__rowsPrinter = rowsPrinter 

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