Work towards C SDK (#11). Frenchie is still broken.
[progcomp10.git] / src / link / pexpect / examples / bd_serv.py
1 #!/usr/bin/env python
2
3 """Back door shell server
4
5 This exposes an shell terminal on a socket.
6
7     --hostname : sets the remote host name to open an ssh connection to.
8     --username : sets the user name to login with
9     --password : (optional) sets the password to login with
10     --port     : set the local port for the server to listen on
11     --watch    : show the virtual screen after each client request
12 """
13
14 # Having the password on the command line is not a good idea, but
15 # then this entire project is probably not the most security concious thing
16 # I've ever built. This should be considered an experimental tool -- at best.
17 import pxssh, pexpect, ANSI
18 import time, sys, os, getopt, getpass, traceback, threading, socket
19
20 def exit_with_usage(exit_code=1):
21
22     print globals()['__doc__']
23     os._exit(exit_code)
24
25 class roller (threading.Thread):
26
27     """This runs a function in a loop in a thread."""
28
29     def __init__(self, interval, function, args=[], kwargs={}):
30
31         """The interval parameter defines time between each call to the function.
32         """
33
34         threading.Thread.__init__(self)
35         self.interval = interval
36         self.function = function
37         self.args = args
38         self.kwargs = kwargs
39         self.finished = threading.Event()
40
41     def cancel(self):
42
43         """Stop the roller."""
44
45         self.finished.set()
46
47     def run(self):
48
49         while not self.finished.isSet():
50             # self.finished.wait(self.interval)
51             self.function(*self.args, **self.kwargs)
52
53 def endless_poll (child, prompt, screen, refresh_timeout=0.1):
54
55     """This keeps the screen updated with the output of the child. This runs in
56     a separate thread. See roller(). """
57
58     #child.logfile_read = screen
59     try:
60         s = child.read_nonblocking(4000, 0.1)
61         screen.write(s)
62     except:
63         pass
64     #while True:
65     #    #child.prompt (timeout=refresh_timeout)
66     #    try:
67     #        #child.read_nonblocking(1,timeout=refresh_timeout)
68     #        child.read_nonblocking(4000, 0.1)
69     #    except:
70     #        pass
71
72 def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
73
74     '''This forks the current process into a daemon. Almost none of this is
75     necessary (or advisable) if your daemon is being started by inetd. In that
76     case, stdin, stdout and stderr are all set up for you to refer to the
77     network connection, and the fork()s and session manipulation should not be
78     done (to avoid confusing inetd). Only the chdir() and umask() steps remain
79     as useful. 
80
81     References:
82         UNIX Programming FAQ
83         1.7 How do I get my program to act like a daemon?
84         http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
85
86         Advanced Programming in the Unix Environment
87         W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7.
88
89     The stdin, stdout, and stderr arguments are file names that will be opened
90     and be used to replace the standard file descriptors in sys.stdin,
91     sys.stdout, and sys.stderr. These arguments are optional and default to
92     /dev/null. Note that stderr is opened unbuffered, so if it shares a file
93     with stdout then interleaved output may not appear in the order that you
94     expect. '''
95
96     # Do first fork.
97     try: 
98         pid = os.fork() 
99         if pid > 0:
100             sys.exit(0)   # Exit first parent.
101     except OSError, e: 
102         sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) )
103         sys.exit(1)
104
105     # Decouple from parent environment.
106     os.chdir("/") 
107     os.umask(0) 
108     os.setsid() 
109
110     # Do second fork.
111     try: 
112         pid = os.fork() 
113         if pid > 0:
114             sys.exit(0)   # Exit second parent.
115     except OSError, e: 
116         sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) )
117         sys.exit(1)
118
119     # Now I am a daemon!
120     
121     # Redirect standard file descriptors.
122     si = open(stdin, 'r')
123     so = open(stdout, 'a+')
124     se = open(stderr, 'a+', 0)
125     os.dup2(si.fileno(), sys.stdin.fileno())
126     os.dup2(so.fileno(), sys.stdout.fileno())
127     os.dup2(se.fileno(), sys.stderr.fileno())
128
129     # I now return as the daemon
130     return 0
131
132 def add_cursor_blink (response, row, col):
133
134     i = (row-1) * 80 + col
135     return response[:i]+'<img src="http://www.noah.org/cursor.gif">'+response[i:]
136
137 def main ():
138
139     try:
140         optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch'])
141     except Exception, e:
142         print str(e)
143         exit_with_usage()
144
145     command_line_options = dict(optlist)
146     options = dict(optlist)
147     # There are a million ways to cry for help. These are but a few of them.
148     if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]:
149         exit_with_usage(0)
150   
151     hostname = "127.0.0.1"
152     port = 1664
153     username = os.getenv('USER')
154     password = ""
155     daemon_mode = False
156     if '-d' in options:
157         daemon_mode = True
158     if '--watch' in options:
159         watch_mode = True
160     else:
161         watch_mode = False
162     if '--hostname' in options:
163         hostname = options['--hostname']
164     if '--port' in options:
165         port = int(options['--port'])
166     if '--username' in options:
167         username = options['--username']
168     print "Login for %s@%s:%s" % (username, hostname, port)
169     if '--password' in options:
170         password = options['--password']
171     else:
172         password = getpass.getpass('password: ')
173    
174     if daemon_mode: 
175         print "daemonizing server"
176         daemonize()
177         #daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log')
178     
179     sys.stdout.write ('server started with pid %d\n' % os.getpid() )
180
181     virtual_screen = ANSI.ANSI (24,80) 
182     child = pxssh.pxssh()
183     child.login (hostname, username, password)
184     print 'created shell. command line prompt is', child.PROMPT
185     #child.sendline ('stty -echo')
186     #child.setecho(False)
187     virtual_screen.write (child.before)
188     virtual_screen.write (child.after)
189
190     if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock")
191     s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
192     localhost = '127.0.0.1'
193     s.bind('/tmp/mysock')
194     os.chmod('/tmp/mysock',0777)
195     print 'Listen'
196     s.listen(1)
197     print 'Accept'
198     #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
199     #localhost = '127.0.0.1'
200     #s.bind((localhost, port))
201     #print 'Listen'
202     #s.listen(1)
203
204     r = roller (0.01, endless_poll, (child, child.PROMPT, virtual_screen))
205     r.start()
206     print "screen poll updater started in background thread"
207     sys.stdout.flush()
208
209     try:
210         while True:
211             conn, addr = s.accept()
212             print 'Connected by', addr
213             data = conn.recv(1024)
214             if data[0]!=':':
215                 cmd = ':sendline'
216                 arg = data.strip()
217             else:
218                 request = data.split(' ', 1)
219                 if len(request)>1:
220                     cmd = request[0].strip()
221                     arg = request[1].strip()
222                 else:
223                     cmd = request[0].strip()
224             if cmd == ':exit':
225                 r.cancel()
226                 break
227             elif cmd == ':sendline':
228                 child.sendline (arg)
229                 #child.prompt(timeout=2)
230                 time.sleep(0.2)
231                 shell_window = str(virtual_screen)
232             elif cmd == ':send' or cmd==':xsend':
233                 if cmd==':xsend':
234                     arg = arg.decode("hex")
235                 child.send (arg)
236                 time.sleep(0.2)
237                 shell_window = str(virtual_screen)
238             elif cmd == ':cursor':
239                 shell_window = '%x%x' % (virtual_screen.cur_r, virtual_screen.cur_c)
240             elif cmd == ':refresh':
241                 shell_window = str(virtual_screen)
242
243             response = []
244             response.append (shell_window)
245             #response = add_cursor_blink (response, row, col)
246             sent = conn.send('\n'.join(response))
247             if watch_mode: print '\n'.join(response)
248             if sent < len (response):
249                 print "Sent is too short. Some data was cut off."
250             conn.close()
251     finally:
252         r.cancel()
253         print "cleaning up socket"
254         s.close()
255         if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock")
256         print "done!"
257
258 def pretty_box (rows, cols, s):
259
260     """This puts an ASCII text box around the given string, s.
261     """
262
263     top_bot = '+' + '-'*cols + '+\n'
264     return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot
265     
266 def error_response (msg):
267
268     response = []
269     response.append ("""All commands start with :
270 :{REQUEST} {ARGUMENT}
271 {REQUEST} may be one of the following:
272     :sendline: Run the ARGUMENT followed by a line feed.
273     :send    : send the characters in the ARGUMENT without a line feed.
274     :refresh : Use to catch up the screen with the shell if state gets out of sync.
275 Example:
276     :sendline ls -l
277 You may also leave off :command and it will be assumed.
278 Example:
279     ls -l
280 is equivalent to:
281     :sendline ls -l
282 """)
283     response.append (msg)
284     return '\n'.join(response)
285
286 def parse_host_connect_string (hcs):
287
288     """This parses a host connection string in the form
289     username:password@hostname:port. All fields are options expcet hostname. A
290     dictionary is returned with all four keys. Keys that were not included are
291     set to empty strings ''. Note that if your password has the '@' character
292     then you must backslash escape it. """
293
294     if '@' in hcs:
295         p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
296     else:
297         p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)')
298     m = p.search (hcs)
299     d = m.groupdict()
300     d['password'] = d['password'].replace('\\@','@')
301     return d
302      
303 if __name__ == "__main__":
304
305     try:
306         start_time = time.time()
307         print time.asctime()
308         main()
309         print time.asctime()
310         print "TOTAL TIME IN MINUTES:",
311         print (time.time() - start_time) / 60.0
312     except Exception, e:
313         print str(e)
314         tb_dump = traceback.format_exc()
315         print str(tb_dump)
316

UCC git Repository :: git.ucc.asn.au