ulg.py 21.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
#!/usr/bin/env python
#
# ULG - Universal Looking Glass
# by Tomas Hlavacek (tomas.hlavacek@nic.cz)
# last udate: June 21 2012
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


# Imports
22
import os, sys
23 24
from genshi.template import TemplateLoader
from genshi.core import Markup
25 26 27
import cgi
import cgitb; cgitb.enable()
import pickle
28
import re
29
import fcntl
30
import traceback
31 32 33 34
import urllib
import md5
import time
import random
35

36
import config
37 38
import defaults

39
import ulgmodel
40

41
### CGI output handler
42

43
class Session(object):
44 45 46 47 48 49 50 51 52 53 54
    def __init__(self,sessionid=None,routerid=None,commandid=None,parameters=[],result=None,finished=False,error=None,resrange=None,copy=None):
        if(copy):
            self.sessionid=copy.sessionid
            self.routerid=copy.routerid
            self.commandid=copy.commandid
            self.parameters=copy.parameters
            self.error=copy.error
            self.finished=copy.finished
            self.range=copy.range
            self.resultlen=copy.resultlen

55
        else:
56 57 58 59
            if(sessionid == None):
                self.sessionid = self.__genSessionId__()
            else:
                self.sessionid=sessionid
60

61 62 63 64 65 66 67
            self.routerid=routerid
            self.commandid=commandid
            self.parameters=parameters
            self.error=error
            self.finished=finished
            self.range=resrange
            self.resultlen=0
68

69
        self.save()
70

71 72
    def __genSessionId__(self):
        return md5.new(str(time.time())+str(random.randint(1,1000000))).hexdigest()
73

74 75 76 77
    @staticmethod
    def getSessionFileName(sessionid):
        if(re.compile('^[a-zA-Z0-9]{10,128}$').match(sessionid)):
            return defaults.session_dir+'/'+'ulg-'+sessionid+'.session'
78
        else:
79
            raise Exception('Invalid session id passed. Value was: '+sessionid)
80

81 82 83 84 85 86 87
    @staticmethod
    def getSessionOutputFileName(sessionid):
        if(re.compile('^[a-zA-Z0-9]{10,128}$').match(sessionid)):
            return defaults.session_dir+'/'+'ulg-'+sessionid+'.out.session'
        else:
            raise Exception('Invalid session id passed. Value was: '+sessionid)

88 89 90 91
    @staticmethod
    def load(sessionid):
        if(sessionid == None):
            return None
92

93 94
        try:
            fn = Session.getSessionFileName(sessionid)
95

96 97 98 99 100 101 102 103 104 105
            if(os.path.isfile(fn)):
                f = open(fn, 'rb')
                s = pickle.load(f)
                f.close()
                return s
            else:
                return None
            
        except Exception:
            return None
106

107 108 109 110 111 112
    def save(self):
        try:
            fn = Session.getSessionFileName(self.getSessionId())
            f = open(fn,'wb')
            pickle.dump(self, f)
            f.close()
113 114
        except:
            ulgmodel.log("Saving session failed: " + traceback.format_exc())
115

116 117
    def getSessionId(self):
        return self.sessionid
118

119 120 121
    def setFinished(self):
        self.finished=True
        self.save()
122

123 124
    def isFinished(self):
        return self.finished
125

126 127 128
    def setRouterId(self,router):
        self.routerid=routerid
        self.save()
129

130 131
    def getRouterId(self):
        return self.routerid
132

133 134 135
    def setCommandId(self,command):
        self.commandid = commandid
        self.save()
136

137 138 139 140 141 142
    def getCommandId(self):
        return self.commandid

    def cleanParameters(self):
        self.parameters = []
        self.save()
143

144 145 146 147 148
    def addParameter(self,parameter):
        if(not self.parameters):
            self.parameters = []
        self.parameters.append(parameter)
        self.save()
149

150 151
    def getParameters(self):
        return self.parameters
152

153
    def setResult(self,result):
154 155 156 157 158
        fn = Session.getSessionOutputFileName(self.sessionid)

        f = open(fn, 'w')
        f.write(result)
        f.close()
159

160
    def getResult(self):
161 162 163 164 165 166 167 168 169 170 171 172
        try:
            fn = Session.getSessionOutputFileName(self.sessionid)

            if(os.path.isfile(fn)):
                f = open(fn, 'r')
                result = f.read()
                f.close()
                return result
            else:
                return None
        except:
            return None
173

174
    def getDecoratedResult(self,decorator_helper,resrange=0,finished=False):
175
        if(self.getError()):
176
            # TODO
177 178
            return decorator_helper.pre(self.getResult())
        else:
179 180
            result = self.getResult()
            if(result):
181
                dr = self.getCommand().decorateResult(self,decorator_helper)
182 183
                self.resultlines = dr[1]
                return dr[0]
184 185 186
            else:
                return None

187
    def appendResult(self,result_fragment):
188 189 190 191 192 193 194 195 196 197 198 199 200
        fn = Session.getSessionOutputFileName(self.sessionid)

        f = open(fn, 'a')
        f.write(result_fragment)
        f.close()

    def clearResult(self):
        try:
            fn = Session.getSessionOutputFileName(self.sessionid)

            if(os.path.isfile(fn)):
                f=open(fn,'w')
                f.close()
201 202

            self.resultlines = 0
203 204
        except:
            pass
205 206 207 208

    def getRouter(self):
        if(self.getRouterId()!=None):
            return config.routers[self.getRouterId()]
209 210 211
        else:
            return None

212 213 214 215 216
    def getCommand(self):
        if(self.getRouterId()!=None)and(self.getCommandId()!=None):
            return self.getRouter().listCommands()[self.getCommandId()]
        else:
            return None
217

218 219 220 221 222
    def getError(self):
        return self.error

    def setError(self,error=None):
        self.error=error
223 224 225 226 227 228 229 230 231
        self.save()

    def getRange(self):
        return self.range

    def setRange(self,resrange):
        self.range=resrange
        self.save()

232 233 234
    def showRange(self):
        return self.getCommand().showRange()

235 236
    def getMaxRange(self):
        return self.resultlines
237

238

239
class DecoratorHelper:
240 241
    def __init__(self):
        pass
242

243 244
    def getScriptURL(self):
        return os.path.basename(__file__)
245

246
    def getURL(self,action,parameters={}):
247
        url=self.getScriptURL() + ("?action=%s" % urllib.quote(action))
248
        for k in parameters.keys():
249
            url = url + '&' + k + '=' + urllib.quote(parameters[k])
250
        return url
251

252 253
    def getIndexURL(self):
        return self.getURL('index')
254

255 256
    def getRuncommandURL(self,parameters={}):
        return self.getURL('runcommand',parameters)
257

258 259 260 261 262
    def getDisplayURL(self,sessionid,resrange=None):
        if(resrange):
            return self.getURL('display',{'sessionid':sessionid,'resrange':resrange})
        else:
            return self.getURL('display',{'sessionid':sessionid})
263

264 265 266 267 268 269
    def getDebugURL(self,parameters={}):
        return self.getURL('debug',parameters)

    def getErrorURL(self,parameters={}):
        return self.getURL('error',parameters)

270 271 272
    def getSpecialContentURL(self,sessionid,parameters={}):
        return self.getURL('getfile',dict({'sessionid':sessionid},**parameters))

273
    def getRouterID(self,router):
274 275 276 277
        for ridx,r in enumerate(config.routers):
            if(r == router):
                return ridx

278
        return 0
279

280
    def getCommandID(self,router,command):
281 282 283 284
        for cidx,c in enumerate(router.listCommands()):
            if(c == command):
                return cidx

285
        return 0
286

287 288 289 290
    def pre(self,text):
        return ('<pre>%s</pre>' % text)

    def ahref(self,url,text):
291
        return ('<a href=%s>%s</a>' % (str(url),str(text)))
292

293 294 295
    def copy_session(self,session):
        return Session(copy=session)

296 297 298 299 300 301
    def img(self,url,alternative_text=None):
        if(alternative_text):
            return ('<img src="%s" alt="%s">' % (url,alternative_text))
        else:
            return ('<img src="%s">' % url)

302 303 304 305 306
    def mwin(self,url,label=None):
        if(label == None):
            label = url
        return """<span style="cursor: pointer" onclick="TINY.box.show({iframe:'%s',boxid:'frameless',fixed:false,width:750,height:450,closejs:function(){closeJS()}})"><u>%s</u></span>""" % (url,label)

307

308
class ULGCgi:
309
    def __init__(self):
310 311 312 313
        self.loader=TemplateLoader(
            os.path.join(os.path.dirname(__file__), defaults.template_dir),
            auto_reload=True
            )
314

315
        self.decorator_helper = DecoratorHelper()
316

317 318 319 320 321

    def print_text_html(self):
        print "Content-Type: text/html\n"


322 323 324 325 326 327 328 329 330
    def increaseUsage(self):
        u = 0
        try:
            # Open files
            if(os.path.isfile(defaults.usage_counter_file)):
                lf = open(defaults.usage_counter_file,'r+')
                u = int(lf.readline())
            else:
                lf = open(defaults.usage_counter_file,'w')
331

332 333
            # Acquire lock
            fcntl.lockf(lf, fcntl.LOCK_EX)
334
        except (IOError,ValueError) as e:
335
            ulgmodel.log("Locking mechanism failure: "+str(e))
336
            return False
337

338 339 340 341 342 343 344 345 346 347 348 349
        try:
            if(u < defaults.usage_limit):
                # write a new value and release lock
                lf.seek(0)
                lf.write(str(u+1)+'\n')
                lf.close()
                return True
            else:
                # release lock
                lf.close()
                return False
        except IOError as e:
350
            ulgmodel.log("Locking mechanism update failure: "+str(e))
351
            return False
352

353 354 355 356 357 358 359 360 361
    def decreaseUsage(self):
        u = 1
        try:
            # Open files
            if(os.path.isfile(defaults.usage_counter_file)):
                lf = open(defaults.usage_counter_file,'r+')
                u = int(lf.readline())
            else:
                lf = open(defaults.usage_counter_file,'w')
362

363 364 365
            # Acquire lock
            fcntl.lockf(lf, fcntl.LOCK_EX)
        except IOError,ValueError:
366
            ulgmodel.log("Locking mechanism failure: "+str(e))
367
            return False
368

369 370 371 372 373 374 375 376 377 378 379 380
        try:
            if(u>0):
                # write a new value and release lock
                lf.seek(0)
                lf.write(str(u-1)+'\n')
                lf.close()
                return
            else:
                # release lock
                lf.close()
                return
        except IOError as e:
381
            ulgmodel.log("Locking mechanism update failure: "+str(e))
382 383 384 385 386 387
            pass

    def stopSessionOverlimit(self,session):
        session.setResult(defaults.STRING_SESSION_OVERLIMIT)
        session.setFinished()

388 389
    def getRefreshInterval(self,datalength=None):
        if(datalength):
390
            return (datalength/(1024*100))*defaults.refresh_interval + defaults.refresh_interval
391 392 393
        else:
            return defaults.refresh_interval

394 395 396 397 398 399 400 401 402 403 404 405
    def HTTPRedirect(self,url):
        return """<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Redirect!</title>
<meta http-equiv="REFRESH" content="0;url=%s">
</head>
<body>
</body>
</html>""" % url

    def runCommand(self,session):
406 407 408 409 410 411 412
        class FakeSessionFile(object):
            def __init__(self,session):
                self.session = session

            def write(self,string):
                self.session.appendResult(string)

413 414 415 416 417 418 419 420 421 422 423 424 425
        # define trivial thread function
        def commandThreadBody(session,decreaseUsageMethod):
            ulgmodel.debug("Running command: "+session.getCommand().getName())
            try:
                session.getRouter().runAsyncCommand(session.getCommand(),session.getParameters(),FakeSessionFile(session))
            except Exception as e:
                ulgmodel.log("ERROR: Exception occured while running a command:" + traceback.format_exc())
                session.setResult("ERROR in commandThreadBody:\n"+traceback.format_exc())
            finally:
                ulgmodel.debug("Command finished: "+session.getCommand().getName())
                session.setFinished()
                decreaseUsageMethod()

426 427 428 429
        # try to increase usage counter
        if(self.increaseUsage()):
            # start new thread if needed
            if(defaults.always_start_thread or session.getRouter().getForkNeeded()):
430
                # fork a daemon process (fork two times to decouple with parent)
431 432 433 434 435 436 437 438 439 440 441 442 443
                sys.stdout.flush()
                child_pid = os.fork()
                if(child_pid == 0):
                    # detach process
                    devnull = open(os.devnull,'w')
                    os.dup2(devnull.fileno(),sys.stdout.fileno())
                    os.dup2(devnull.fileno(),sys.stderr.fileno())

                    # run the command
                    commandThreadBody(session,self.decreaseUsage)

                    # exit the child
                    sys.exit(0)
444

445
            else:
446
                # directly run the selected action, DEPRECATED
447 448 449 450
                commandThreadBody(session,self.decreaseUsage)
        else:
            # stop and report no-op
            self.stopSessionOverlimit(session)
451 452


453 454
    def renderULGIndex(self,routerid=0,commandid=0,sessionid=None):
        template = self.loader.load(defaults.index_template_file)
455

456 457 458 459 460
        return template.generate(defaults=defaults,
                                 routers=config.routers,
                                 default_routerid=routerid,
                                 default_commandid=commandid,
                                 default_sessionid=sessionid,
461
                                 getFormURL=self.decorator_helper.getRuncommandURL
462
                                 ).render('html', doctype='html')
463 464


465 466 467
    def renderULGAction(self,routerid=0,commandid=0,sessionid=None,**moreparams):
        routerid=int(routerid)
        commandid=int(commandid)
468

469 470
        # create and register session
        session = Session(sessionid=sessionid,routerid=routerid,commandid=commandid)
471
        session.clearResult()
472 473 474 475 476
    
        # extract parameters
        session.cleanParameters()
        for pidx,ps in enumerate(session.getCommand().getParamSpecs()):
            if('param'+str(pidx) in moreparams.keys()):
477
                session.addParameter(str(moreparams['param'+str(pidx)]))
478 479
            else:
                session.addParameter(ps.getDefault())
480

481
        # run the command (possibly in a separate process)
482
        self.runCommand(session)
483

484
        # redirect to the session display
485
        return self.HTTPRedirect(self.decorator_helper.getDisplayURL(session.getSessionId()))
486 487


488 489
    def renderULGResult(self,sessionid=None,resrange=0):
        def getRangeStepURLs(session,decorator_helper):
490 491 492
            if(not session.showRange()):
                return None

493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
            cur_range = session.getRange()
            max_range = session.getMaxRange()

            if(max_range < defaults.range_step):
                return None

            res = []
            if((cur_range - defaults.range_step * 100) >= 0):
                res.append(('<<',decorator_helper.getDisplayURL(session.getSessionId(),str(cur_range - defaults.range_step * 100))))
            if((cur_range - defaults.range_step * 10) >= 0):
                res.append(('<',decorator_helper.getDisplayURL(session.getSessionId(),str(cur_range - defaults.range_step * 10))))

            boundary_size = 8
            leftb = max(0,cur_range - boundary_size*defaults.range_step)
            rightb = min(leftb + 2*boundary_size*defaults.range_step,max_range)

            for rb in range(leftb,rightb,defaults.range_step):
                if(rb != cur_range):
                    res.append((str(rb),decorator_helper.getDisplayURL(session.getSessionId(),str(rb))))
                else:
                    res.append((str(rb),None))

            if((cur_range + defaults.range_step * 10) < max_range):
                res.append(('>',decorator_helper.getDisplayURL(session.getSessionId(),str(cur_range + defaults.range_step * 10))))
            if((cur_range + defaults.range_step * 100) < max_range):
                res.append(('>>',decorator_helper.getDisplayURL(session.getSessionId(),str(cur_range + defaults.range_step * 100))))

            return res

522
        if(sessionid==None):
523
            return self.HTTPRedirect(self.decorator_helper.getErrorURL())
524

525 526
        session = Session.load(sessionid)
        if(session == None):
527
            return self.HTTPRedirect(self.decorator_helper.getErrorURL())
528

529 530
        session.setRange(int(resrange))

531
        result_text = session.getDecoratedResult(self.decorator_helper,session.getRange(),session.isFinished())
532

533 534
        if(session.isFinished()):
            refresh=None
535
        else:
536 537 538 539
            if(result_text):
                refresh = self.getRefreshInterval(len(result_text))
            else:
                refresh = self.getRefreshInterval()
540 541 542 543 544 545 546 547

        template = self.loader.load(defaults.index_template_file)
        return template.generate(defaults=defaults,
                                 routers=config.routers,
                                 default_routerid=session.getRouterId(),
                                 default_commandid=session.getCommandId(),
                                 default_params=session.getParameters(),
                                 default_sessionid=sessionid,
548
                                 result=Markup(result_text) if(result_text) else None,
549
                                 refresh=refresh,
550 551 552
                                 getFormURL=self.decorator_helper.getRuncommandURL,
                                 resrange=str(session.getRange()),
                                 resrangeb=getRangeStepURLs(session,self.decorator_helper),
553 554 555 556 557 558 559 560 561
                                 ).render('html', doctype='html')

    def renderULGError(self,sessionid=None,**params):
        template = self.loader.load(defaults.index_template_file)

        result_text = defaults.STRING_ARBITRARY_ERROR

        session = Session.load(sessionid)
        if(session!=None):
562
            result_text=self.decorator_helper.pre(self.sessions[sessionid].getError())
563 564 565 566 567 568

        return template.generate(defaults=defaults,
                                 routers=config.routers,
                                 default_routerid=0,
                                 default_commandid=0,
                                 default_sessionid=None,
569
                                 result=Markup(result_text) if(result_text) else None,
570
                                 refresh=0,
571
                                 getFormURL=self.decorator_helper.getRuncommandURL
572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
                                 ).render('html', doctype='html')
    
    def renderULGDebug(self,**params):
        template = self.loader.load(defaults.index_template_file)

        result_text = "<h1>DEBUG</h1>\n<pre>\nPARAMS:\n"
        for k in params.keys():
            result_text = result_text + str(k) + "=" + str(params[k]) + "\n"
        result_test = result_text + "\n<pre>"

        return template.generate(defaults=defaults,
                                 routers=config.routers,
                                 default_routerid=0,
                                 default_commandid=0,
                                 default_sessionid=None,
587
                                 result=Markup(result_text) if(result_text) else None,
588
                                 refresh=0,
589
                                 getFormURL=self.decorator_helper.getRuncommandURL()
590 591
                                 ).render('html', doctype='html')

592 593 594 595 596 597 598 599 600

    def getULGSpecialContent(self,sessionid,**params):
        if(sessionid==None):
            return self.HTTPRedirect(self.decorator_helper.getErrorURL())

        session = Session.load(sessionid)
        if(session == None):
            return self.HTTPRedirect(self.decorator_helper.getErrorURL())

601 602
        # speciality here: the function is responsible for printing the output itself
        session.getCommand().getSpecialContent(session,**params)
603 604


605
    def index(self, **params):
606
        self.print_text_html()
607 608
        if('sessionid' in params.keys()):
            print self.renderULGIndex(sessionid=params['sessionid'])
609
        else:
610
            print self.renderULGIndex()
611

612
    def runcommand(self,routerid=0,commandid=0,sessionid=None,**params):
613
        self.print_text_html()
614
        print self.renderULGAction(routerid,commandid,sessionid,**params)
615

616
    def display(self,sessionid=None,**params):
617
        self.print_text_html()
618
        print self.renderULGResult(sessionid,**params)
619

620
    def getfile(self,sessionid=None,**params):
621
        self.getULGSpecialContent(sessionid,**params)
622

623
    def error(self,sessionid=None,**params):
624
        self.print_text_html()
625
        print self.renderULGError(sessionid,**params)
626

627
    def debug(self,**params):
628
        self.print_text_html()
629
        print self.renderULGDebug(**params)
630

631
# main
632

633
if __name__=="__main__":
634 635 636
    try:
        form = cgi.FieldStorage()
        handler = ULGCgi()
637

638 639
        action = form.getvalue('action',None)
        params = dict([(k,form.getvalue(k)) for k in form.keys() if k != 'action'])
640
    
641 642 643 644 645 646 647
        if(action):
            if(action == 'index'):
                handler.index(**params)
            elif(action == 'runcommand'):
                handler.runcommand(**params)
            elif(action == 'display'):
                handler.display(**params)
648 649
            elif(action == 'getfile'):
                handler.getfile(**params)
650
            elif(action == 'error'):
651
                handler.error(**params)
652 653 654 655 656 657 658
            elif(action == 'debug'):
                handler.debug(**params)
            else:
                ulgmodel.log('ERROR: Unknown action called: '+action+'\n')
                handler.display(**params)

        else:
659
            handler.index(**params)
660 661
    except Exception as e:
        ulgmodel.log("ERROR in CGI: "+traceback.format_exc())