source: package_branches/invirt-web/ajaxterm-rebased/code/ajaxterm.py @ 3035

Last change on this file since 3035 was 2764, checked in by broder, 15 years ago

Ensure Kerberos tickets get passed to the ssh that ajaxterm spawns

File size: 10.6 KB
RevLine 
[2747]1#!/usr/bin/env python
2
3""" Ajaxterm """
4
[2762]5import array,atexit,cgi,fcntl,glob,hashlib,mimetypes,optparse,os,pty,random,re,signal,select,sys,threading,time,termios,struct,pwd
[2747]6
7class Terminal:
8        def __init__(self,width=80,height=24):
9                self.width=width
10                self.height=height
11                self.init()
12                self.reset()
13        def init(self):
14                self.esc_seq={
15                        "\x00": None,
16                        "\x05": self.esc_da,
17                        "\x07": None,
18                        "\x08": self.esc_0x08,
19                        "\x09": self.esc_0x09,
20                        "\x0a": self.esc_0x0a,
21                        "\x0b": self.esc_0x0a,
22                        "\x0c": self.esc_0x0a,
23                        "\x0d": self.esc_0x0d,
24                        "\x0e": None,
25                        "\x0f": None,
26                        "\x1b#8": None,
27                        "\x1b=": None,
28                        "\x1b>": None,
29                        "\x1b(0": None,
30                        "\x1b(A": None,
31                        "\x1b(B": None,
32                        "\x1b[c": self.esc_da,
33                        "\x1b[0c": self.esc_da,
34                        "\x1b]R": None,
35                        "\x1b7": self.esc_save,
36                        "\x1b8": self.esc_restore,
37                        "\x1bD": None,
38                        "\x1bE": None,
39                        "\x1bH": None,
40                        "\x1bM": self.esc_ri,
41                        "\x1bN": None,
42                        "\x1bO": None,
43                        "\x1bZ": self.esc_da,
44                        "\x1ba": None,
45                        "\x1bc": self.reset,
46                        "\x1bn": None,
47                        "\x1bo": None,
48                }
49                for k,v in self.esc_seq.items():
50                        if v==None:
51                                self.esc_seq[k]=self.esc_ignore
52                # regex
53                d={
54                        r'\[\??([0-9;]*)([@ABCDEFGHJKLMPXacdefghlmnqrstu`])' : self.csi_dispatch,
55                        r'\]([^\x07]+)\x07' : self.esc_ignore,
56                }
57                self.esc_re=[]
58                for k,v in d.items():
59                        self.esc_re.append((re.compile('\x1b'+k),v))
60                # define csi sequences
61                self.csi_seq={
62                        '@': (self.csi_at,[1]),
63                        '`': (self.csi_G,[1]),
64                        'J': (self.csi_J,[0]),
65                        'K': (self.csi_K,[0]),
66                }
67                for i in [i[4] for i in dir(self) if i.startswith('csi_') and len(i)==5]:
68                        if not self.csi_seq.has_key(i):
69                                self.csi_seq[i]=(getattr(self,'csi_'+i),[1])
70                # Init 0-256 to latin1 and html translation table
71                self.trl1=""
72                for i in range(256):
73                        if i<32:
74                                self.trl1+=" "
75                        elif i<127 or i>160:
76                                self.trl1+=chr(i)
77                        else:
78                                self.trl1+="?"
79                self.trhtml=""
80                for i in range(256):
81                        if i==0x0a or (i>32 and i<127) or i>160:
82                                self.trhtml+=chr(i)
83                        elif i<=32:
84                                self.trhtml+="\xa0"
85                        else:
86                                self.trhtml+="?"
87        def reset(self,s=""):
88                self.scr=array.array('i',[0x000700]*(self.width*self.height))
89                self.st=0
90                self.sb=self.height-1
91                self.cx_bak=self.cx=0
92                self.cy_bak=self.cy=0
93                self.cl=0
94                self.sgr=0x000700
95                self.buf=""
96                self.outbuf=""
97        def peek(self,y1,x1,y2,x2):
98                return self.scr[self.width*y1+x1:self.width*y2+x2]
99        def poke(self,y,x,s):
100                pos=self.width*y+x
101                self.scr[pos:pos+len(s)]=s
102        def zero(self,y1,x1,y2,x2):
103                w=self.width*(y2-y1)+x2-x1+1
104                z=array.array('i',[0x000700]*w)
105                self.scr[self.width*y1+x1:self.width*y2+x2+1]=z
106        def scroll_up(self,y1,y2):
107                self.poke(y1,0,self.peek(y1+1,0,y2,self.width))
108                self.zero(y2,0,y2,self.width-1)
109        def scroll_down(self,y1,y2):
110                self.poke(y1+1,0,self.peek(y1,0,y2-1,self.width))
111                self.zero(y1,0,y1,self.width-1)
112        def scroll_right(self,y,x):
113                self.poke(y,x+1,self.peek(y,x,y,self.width))
114                self.zero(y,x,y,x)
115        def cursor_down(self):
116                if self.cy>=self.st and self.cy<=self.sb:
117                        self.cl=0
118                        q,r=divmod(self.cy+1,self.sb+1)
119                        if q:
120                                self.scroll_up(self.st,self.sb)
121                                self.cy=self.sb
122                        else:
123                                self.cy=r
124        def cursor_right(self):
125                q,r=divmod(self.cx+1,self.width)
126                if q:
127                        self.cl=1
128                else:
129                        self.cx=r
130        def echo(self,c):
131                if self.cl:
132                        self.cursor_down()
133                        self.cx=0
134                self.scr[(self.cy*self.width)+self.cx]=self.sgr|ord(c)
135                self.cursor_right()
136        def esc_0x08(self,s):
137                self.cx=max(0,self.cx-1)
138        def esc_0x09(self,s):
139                x=self.cx+8
140                q,r=divmod(x,8)
141                self.cx=(q*8)%self.width
142        def esc_0x0a(self,s):
143                self.cursor_down()
144        def esc_0x0d(self,s):
145                self.cl=0
146                self.cx=0
147        def esc_save(self,s):
148                self.cx_bak=self.cx
149                self.cy_bak=self.cy
150        def esc_restore(self,s):
151                self.cx=self.cx_bak
152                self.cy=self.cy_bak
153                self.cl=0
154        def esc_da(self,s):
155                self.outbuf="\x1b[?6c"
156        def esc_ri(self,s):
157                self.cy=max(self.st,self.cy-1)
158                if self.cy==self.st:
159                        self.scroll_down(self.st,self.sb)
160        def esc_ignore(self,*s):
161                pass
162#               print "term:ignore: %s"%repr(s)
163        def csi_dispatch(self,seq,mo):
164        # CSI sequences
165                s=mo.group(1)
166                c=mo.group(2)
167                f=self.csi_seq.get(c,None)
168                if f:
169                        try:
170                                l=[min(int(i),1024) for i in s.split(';') if len(i)<4]
171                        except ValueError:
172                                l=[]
173                        if len(l)==0:
174                                l=f[1]
175                        f[0](l)
176#               else:
177#                       print 'csi ignore',c,l
178        def csi_at(self,l):
179                for i in range(l[0]):
180                        self.scroll_right(self.cy,self.cx)
181        def csi_A(self,l):
182                self.cy=max(self.st,self.cy-l[0])
183        def csi_B(self,l):
184                self.cy=min(self.sb,self.cy+l[0])
185        def csi_C(self,l):
186                self.cx=min(self.width-1,self.cx+l[0])
187                self.cl=0
188        def csi_D(self,l):
189                self.cx=max(0,self.cx-l[0])
190                self.cl=0
191        def csi_E(self,l):
192                self.csi_B(l)
193                self.cx=0
194                self.cl=0
195        def csi_F(self,l):
196                self.csi_A(l)
197                self.cx=0
198                self.cl=0
199        def csi_G(self,l):
200                self.cx=min(self.width,l[0])-1
201        def csi_H(self,l):
202                if len(l)<2: l=[1,1]
203                self.cx=min(self.width,l[1])-1
204                self.cy=min(self.height,l[0])-1
205                self.cl=0
206        def csi_J(self,l):
207                if l[0]==0:
208                        self.zero(self.cy,self.cx,self.height-1,self.width-1)
209                elif l[0]==1:
210                        self.zero(0,0,self.cy,self.cx)
211                elif l[0]==2:
212                        self.zero(0,0,self.height-1,self.width-1)
213        def csi_K(self,l):
214                if l[0]==0:
215                        self.zero(self.cy,self.cx,self.cy,self.width-1)
216                elif l[0]==1:
217                        self.zero(self.cy,0,self.cy,self.cx)
218                elif l[0]==2:
219                        self.zero(self.cy,0,self.cy,self.width-1)
220        def csi_L(self,l):
221                for i in range(l[0]):
222                        if self.cy<self.sb:
223                                self.scroll_down(self.cy,self.sb)
224        def csi_M(self,l):
225                if self.cy>=self.st and self.cy<=self.sb:
226                        for i in range(l[0]):
227                                self.scroll_up(self.cy,self.sb)
228        def csi_P(self,l):
229                w,cx,cy=self.width,self.cx,self.cy
230                end=self.peek(cy,cx,cy,w)
231                self.csi_K([0])
232                self.poke(cy,cx,end[l[0]:])
233        def csi_X(self,l):
234                self.zero(self.cy,self.cx,self.cy,self.cx+l[0])
235        def csi_a(self,l):
236                self.csi_C(l)
237        def csi_c(self,l):
238                #'\x1b[?0c' 0-8 cursor size
239                pass
240        def csi_d(self,l):
241                self.cy=min(self.height,l[0])-1
242        def csi_e(self,l):
243                self.csi_B(l)
244        def csi_f(self,l):
245                self.csi_H(l)
246        def csi_h(self,l):
247                if l[0]==4:
248                        pass
249#                       print "insert on"
250        def csi_l(self,l):
251                if l[0]==4:
252                        pass
253#                       print "insert off"
254        def csi_m(self,l):
255                for i in l:
256                        if i==0 or i==39 or i==49 or i==27:
257                                self.sgr=0x000700
258                        elif i==1:
259                                self.sgr=(self.sgr|0x000800)
260                        elif i==7:
261                                self.sgr=0x070000
262                        elif i>=30 and i<=37:
263                                c=i-30
264                                self.sgr=(self.sgr&0xff08ff)|(c<<8)
265                        elif i>=40 and i<=47:
266                                c=i-40
267                                self.sgr=(self.sgr&0x00ffff)|(c<<16)
268#                       else:
269#                               print "CSI sgr ignore",l,i
270#               print 'sgr: %r %x'%(l,self.sgr)
271        def csi_r(self,l):
272                if len(l)<2: l=[0,self.height]
273                self.st=min(self.height-1,l[0]-1)
274                self.sb=min(self.height-1,l[1]-1)
275                self.sb=max(self.st,self.sb)
276        def csi_s(self,l):
277                self.esc_save(0)
278        def csi_u(self,l):
279                self.esc_restore(0)
280        def escape(self):
281                e=self.buf
282                if len(e)>32:
283#                       print "error %r"%e
284                        self.buf=""
285                elif e in self.esc_seq:
286                        self.esc_seq[e](e)
287                        self.buf=""
288                else:
289                        for r,f in self.esc_re:
290                                mo=r.match(e)
291                                if mo:
292                                        f(e,mo)
293                                        self.buf=""
294                                        break
295#               if self.buf=='': print "ESC %r\n"%e
296        def write(self,s):
297                for i in s:
298                        if len(self.buf) or (i in self.esc_seq):
299                                self.buf+=i
300                                self.escape()
301                        elif i == '\x1b':
302                                self.buf+=i
303                        else:
304                                self.echo(i)
305        def read(self):
306                b=self.outbuf
307                self.outbuf=""
308                return b
309        def dump(self):
310                r=''
311                for i in self.scr:
312                        r+=chr(i&255)
313                return r
314        def dumplatin1(self):
315                return self.dump().translate(self.trl1)
[2761]316        def dumphtml(self,color=1,last_hash=None):
[2747]317                h=self.height
318                w=self.width
319                r=""
320                span=""
321                span_bg,span_fg=-1,-1
322                for i in range(h*w):
323                        q,c=divmod(self.scr[i],256)
324                        if color:
325                                bg,fg=divmod(q,256)
326                        else:
327                                bg,fg=0,7
328                        if i==self.cy*w+self.cx:
329                                bg,fg=1,7
330                        if (bg!=span_bg or fg!=span_fg or i==h*w-1):
331                                if len(span):
332                                        r+='<span class="f%d b%d">%s</span>'%(span_fg,span_bg,cgi.escape(span.translate(self.trhtml)))
333                                span=""
334                                span_bg,span_fg=bg,fg
335                        span+=chr(c)
336                        if i%w==w-1:
337                                span+='\n'
[2761]338                hash = hashlib.md5(r).hexdigest()
339                r='<?xml version="1.0" encoding="ISO-8859-1"?><pre class="term" id="%s">%s</pre>'% (hash,r)
340                if last_hash == hash:
[2747]341                        return '<?xml version="1.0"?><idem></idem>'
342                else:
343                        return r
344        def __repr__(self):
345                d=self.dumplatin1()
346                r=""
347                for i in range(self.height):
348                        r+="|%s|\n"%d[self.width*i:self.width*(i+1)]
349                return r
350
351class SynchronizedMethod:
352        def __init__(self,lock,orig):
353                self.lock=lock
354                self.orig=orig
355        def __call__(self,*l):
356                self.lock.acquire()
357                r=self.orig(*l)
358                self.lock.release()
359                return r
360
361class Multiplex:
[2748]362        def __init__(self):
[2747]363                self.proc={}
364                self.lock=threading.RLock()
365                self.thread=threading.Thread(target=self.loop)
[2749]366                self.thread.daemon=True
[2747]367                self.alive=1
[2762]368                atexit.register(self.die)
[2747]369                # synchronize methods
370                for name in ['create','fds','proc_read','proc_write','dump','die','run']:
371                        orig=getattr(self,name)
372                        setattr(self,name,SynchronizedMethod(self.lock,orig))
373                self.thread.start()
[2748]374        def create(self,cmd,w=80,h=25):
[2747]375                pid,fd=pty.fork()
376                if pid==0:
377                        try:
378                                fdl=[int(i) for i in os.listdir('/proc/self/fd')]
379                        except OSError:
380                                fdl=range(256)
381                        for i in [i for i in fdl if i>2]:
382                                try:
383                                        os.close(i)
384                                except OSError:
385                                        pass
386                        env={}
387                        env["COLUMNS"]=str(w)
388                        env["LINES"]=str(h)
389                        env["TERM"]="linux"
390                        env["PATH"]=os.environ['PATH']
[2764]391                        if 'KRB5CCNAME' in os.environ:
392                                env["KRB5CCNAME"]=os.environ['KRB5CCNAME']
[2747]393                        os.execvpe(cmd[0],cmd,env)
394                else:
395                        fcntl.fcntl(fd, fcntl.F_SETFL, os.O_NONBLOCK)
396                        # python bug http://python.org/sf/1112949 on amd64
397                        fcntl.ioctl(fd, struct.unpack('i',struct.pack('I',termios.TIOCSWINSZ))[0], struct.pack("HHHH",h,w,0,0))
398                        self.proc[fd]={'pid':pid,'term':Terminal(w,h),'buf':'','time':time.time()}
399                        return fd
400        def die(self):
401                self.alive=0
402        def run(self):
403                return self.alive
404        def fds(self):
405                return self.proc.keys()
406        def proc_kill(self,fd):
407                if fd in self.proc:
408                        self.proc[fd]['time']=0
409                t=time.time()
410                for i in self.proc.keys():
411                        t0=self.proc[i]['time']
412                        if (t-t0)>120:
413                                try:
414                                        os.close(i)
415                                        os.kill(self.proc[i]['pid'],signal.SIGTERM)
416                                except (IOError,OSError):
417                                        pass
418                                del self.proc[i]
419        def proc_read(self,fd):
420                try:
421                        t=self.proc[fd]['term']
422                        t.write(os.read(fd,65536))
423                        reply=t.read()
424                        if reply:
425                                os.write(fd,reply)
426                        self.proc[fd]['time']=time.time()
427                except (KeyError,IOError,OSError):
428                        self.proc_kill(fd)
429        def proc_write(self,fd,s):
430                try:
431                        os.write(fd,s)
432                except (IOError,OSError):
433                        self.proc_kill(fd)
[2761]434        def dump(self,fd,color=1,last_hash=None):
[2747]435                try:
[2761]436                        return self.proc[fd]['term'].dumphtml(color, last_hash)
[2747]437                except KeyError:
438                        return False
439        def loop(self):
440                while self.run():
441                        fds=self.fds()
442                        i,o,e=select.select(fds, [], [], 1.0)
443                        for fd in i:
444                                self.proc_read(fd)
445                        if len(i):
446                                time.sleep(0.002)
447                for i in self.proc.keys():
448                        try:
449                                os.close(i)
450                                os.kill(self.proc[i]['pid'],signal.SIGTERM)
451                        except (IOError,OSError):
452                                pass
Note: See TracBrowser for help on using the repository browser.