Skip to content

Commit 0c3da5c

Browse files
committed
code refactoring and first time logger is handled by a separate file descriptor (issue sqlmapproject#297)
1 parent 2f6a316 commit 0c3da5c

File tree

3 files changed

+63
-41
lines changed

3 files changed

+63
-41
lines changed

_sqlmap.py

+20-8
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,16 @@
3737
from lib.core.exception import SqlmapUserQuitException
3838
from lib.core.log import FORMATTER
3939
from lib.core.log import LOGGER_HANDLER
40+
from lib.core.log import LOGGER_OUTPUT
4041
from lib.core.option import init
4142
from lib.core.profiling import profile
4243
from lib.core.settings import LEGAL_DISCLAIMER
4344
from lib.core.settings import RESTAPI_SERVER_PORT
4445
from lib.core.testing import smokeTest
4546
from lib.core.testing import liveTest
4647
from lib.parse.cmdline import cmdLineParser
47-
from lib.utils.restapi import restAPIrun
48-
from lib.utils.restapi import restAPIsetup
48+
from lib.utils.restapi import restAPIRun
49+
from lib.utils.restapi import restAPISetup
4950

5051
def modulePath():
5152
"""
@@ -55,18 +56,29 @@ def modulePath():
5556

5657
return os.path.dirname(getUnicode(sys.executable if weAreFrozen() else __file__, sys.getfilesystemencoding()))
5758

58-
def restApiServe():
59-
logger.setLevel(logging.INFO)
59+
def restAPIServe():
60+
# Increase default logging level to debug for RESTful API
61+
logger.setLevel(logging.DEBUG)
62+
63+
# Enforce batch mode and disable coloring for RESTful API
6064
cmdLineOptions.batch = True
6165
cmdLineOptions.disableColoring = True
62-
restAPIsetup(port=cmdLineOptions.restApiPort or RESTAPI_SERVER_PORT)
66+
67+
# Setup RESTful API
68+
restAPISetup(port=cmdLineOptions.restApiPort or RESTAPI_SERVER_PORT)
69+
70+
# Wrap logger stdout onto a custom file descriptor (LOGGER_OUTPUT)
6371
def emit(self, record):
6472
message = stdoutencode(FORMATTER.format(record))
65-
sys.stdout.write("%s\n" % message.strip('\r'))
73+
print >>LOGGER_OUTPUT, message.strip('\r')
6674
LOGGER_HANDLER.emit = types.MethodType(emit, LOGGER_HANDLER, type(LOGGER_HANDLER))
75+
76+
# Wrap standard output onto a custom file descriptor
6777
sys.stdout = StringIO.StringIO()
6878
#sys.stderr = StringIO.StringIO()
69-
restAPIrun(port=cmdLineOptions.restApiPort or RESTAPI_SERVER_PORT)
79+
80+
# Run RESTful API
81+
restAPIRun(port=cmdLineOptions.restApiPort or RESTAPI_SERVER_PORT)
7082

7183
def main():
7284
"""
@@ -85,7 +97,7 @@ def main():
8597
cmdLineOptions.update(cmdLineParser().__dict__)
8698

8799
if cmdLineOptions.restApi:
88-
restApiServe()
100+
restAPIServe()
89101
else:
90102
init(cmdLineOptions)
91103

lib/core/log.py

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77

88
import logging
9+
import StringIO
910
import sys
1011

1112
from lib.core.enums import CUSTOM_LOGGING
@@ -32,3 +33,6 @@
3233
LOGGER_HANDLER.setFormatter(FORMATTER)
3334
LOGGER.addHandler(LOGGER_HANDLER)
3435
LOGGER.setLevel(logging.WARN)
36+
37+
# to handle logger with the RESTful API
38+
LOGGER_OUTPUT = StringIO.StringIO()

lib/utils/restapi.py

+39-33
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from lib.core.data import cmdLineOptions
3333
from lib.core.data import kb
3434
from lib.core.data import logger
35+
from lib.core.log import LOGGER_OUTPUT
3536
from lib.core.exception import SqlmapMissingDependence
3637
from lib.core.option import init
3738
from lib.core.settings import UNICODE_ENCODING
@@ -99,10 +100,7 @@ def task_new():
99100
global tasks
100101

101102
taskid = hexencode(os.urandom(16))
102-
103-
tasks[taskid] = AttribDict()
104-
tasks[taskid].options = AttribDict(cmdLineOptions)
105-
tasks[taskid].output = ""
103+
tasks[taskid] = AttribDict(cmdLineOptions)
106104

107105
return jsonize({"taskid": taskid})
108106

@@ -171,9 +169,9 @@ def cleanup(taskid):
171169
global tasks
172170

173171
if is_admin(taskid):
174-
for task, taskdata in tasks.items():
175-
if "oDir" in taskdata.options and taskdata.options.oDir is not None:
176-
shutil.rmtree(taskdata.options.oDir)
172+
for task, options in tasks.items():
173+
if "oDir" in options and options.oDir is not None:
174+
shutil.rmtree(options.oDir)
177175

178176
admin_task = tasks[adminid]
179177
tasks = AttribDict()
@@ -192,7 +190,7 @@ def option_list(taskid):
192190
if taskid not in tasks:
193191
abort(500, "Invalid task ID")
194192

195-
return jsonize(tasks[taskid].options)
193+
return jsonize(tasks[taskid])
196194

197195
@post("/option/<taskid>/get")
198196
def option_get(taskid):
@@ -204,8 +202,8 @@ def option_get(taskid):
204202

205203
option = request.json.get("option", "")
206204

207-
if option in tasks[taskid].options:
208-
return jsonize({option: tasks[taskid].options[option]})
205+
if option in tasks[taskid]:
206+
return jsonize({option: tasks[taskid][option]})
209207
else:
210208
return jsonize({option: None})
211209

@@ -220,7 +218,7 @@ def option_set(taskid):
220218
abort(500, "Invalid task ID")
221219

222220
for key, value in request.json.items():
223-
tasks[taskid].options[key] = value
221+
tasks[taskid][key] = value
224222

225223
return jsonize({"success": True})
226224

@@ -238,12 +236,12 @@ def scan(taskid):
238236
# Initialize sqlmap engine's options with user's provided options
239237
# within the JSON request
240238
for key, value in request.json.items():
241-
tasks[taskid].options[key] = value
239+
tasks[taskid][key] = value
242240

243-
# Overwrite oDir value to a temporary directory
244-
tasks[taskid].options.oDir = tempfile.mkdtemp(prefix="sqlmap-")
241+
# Overwrite output directory (oDir) value to a temporary directory
242+
tasks[taskid].oDir = tempfile.mkdtemp(prefix="sqlmap-")
245243

246-
init(tasks[taskid].options, True)
244+
init(tasks[taskid], True)
247245

248246
# Launch sqlmap engine in a separate thread
249247
thread = threading.Thread(target=start)
@@ -262,11 +260,12 @@ def scan_output(taskid):
262260
if taskid not in tasks:
263261
abort(500, "Invalid task ID")
264262

265-
sys.stdout.seek(len(tasks[taskid]["output"]))
266-
tasks[taskid]["output"] = sys.stdout.read()
263+
sys.stdout.seek(0)
264+
output = sys.stdout.read()
265+
sys.stdout.flush()
267266
sys.stdout.truncate(0)
268267

269-
return jsonize({"output": tasks[taskid]["output"]})
268+
return jsonize({"output": output})
270269

271270
@get("/scan/<taskid>/delete")
272271
def scan_delete(taskid):
@@ -278,21 +277,26 @@ def scan_delete(taskid):
278277
if taskid not in tasks:
279278
abort(500, "Invalid task ID")
280279

281-
if "oDir" in tasks[taskid].options and tasks[taskid].options.oDir is not None:
282-
shutil.rmtree(tasks[taskid].options.oDir)
280+
if "oDir" in tasks[taskid] and tasks[taskid].oDir is not None:
281+
shutil.rmtree(tasks[taskid].oDir)
283282

284283
return jsonize({"success": True})
285284

286285
# Function to handle scans' logs
287-
@get("/log/<taskid>/info")
288-
def log_info(taskid):
286+
@get("/scan/<taskid>/log")
287+
def scan_log(taskid):
289288
"""
290289
Read the informational log messages
291290
"""
292291
if taskid not in tasks:
293292
abort(500, "Invalid task ID")
294293

295-
pass
294+
LOGGER_OUTPUT.seek(0)
295+
output = LOGGER_OUTPUT.read()
296+
LOGGER_OUTPUT.flush()
297+
LOGGER_OUTPUT.truncate(0)
298+
299+
return jsonize({"log": output})
296300

297301
# Function to handle files inside the output directory
298302
@get("/download/<taskid>/<target>/<filename:path>")
@@ -313,29 +317,31 @@ def download(taskid, target, filename):
313317
else:
314318
abort(500)
315319

316-
def restAPIsetup(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
320+
def restAPISetup(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
317321
"""
318-
Initiate REST-JSON API
322+
Setup REST-JSON API
319323
"""
320324
global adminid
321325
global tasks
322326

323327
adminid = hexencode(os.urandom(16))
324-
tasks[adminid] = AttribDict()
325-
tasks[adminid].options = AttribDict(cmdLineOptions)
326-
tasks[adminid].output = ""
327-
logger.info("Running REST-JSON API server at '%s:%d'.." % (host, port))
328-
logger.info("The admin task ID is: %s" % adminid)
328+
tasks[adminid] = AttribDict(cmdLineOptions)
329+
330+
logger.info("running REST-JSON API server at '%s:%d'.." % (host, port))
331+
logger.info("the admin task ID is: %s" % adminid)
329332

330-
def restAPIrun(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
333+
def restAPIRun(host="0.0.0.0", port=RESTAPI_SERVER_PORT):
334+
"""
335+
Run REST-JSON API
336+
"""
331337
run(host=host, port=port, quiet=False, debug=False)
332338

333339
def client(host, port):
334340
addr = "http://%s:%d" % (host, port)
335-
print "[INFO] Starting debug REST-JSON client to '%s'..." % addr
341+
print "[*] starting debug REST-JSON client to '%s'..." % addr
336342

337343
# TODO: write a simple client with urllib2, for now use curl from command line
338-
print "[ERROR] Not yet implemented, use curl from command line instead for now, for example:"
344+
print "[!] not yet implemented, use curl from command line instead for now, for example:"
339345
print "\n\t$ curl --proxy http://127.0.0.1:8080 http://127.0.0.1:%s/task/new" % port
340346
print "\t$ curl --proxy http://127.0.0.1:8080 -H \"Content-Type: application/json\" -X POST -d '{\"url\": \"http://testphp.vulnweb.com/artists.php?artist=1\"}' http://127.0.0.1:%d/scan/<taskID>/start" % port
341347
print "\t$ curl --proxy http://127.0.0.1:8080 http://127.0.0.1:8775/scan/<taskID>/output\n"

0 commit comments

Comments
 (0)