/[gli]/trunk/src/net/server/gliserv.py
Gentoo

Contents of /trunk/src/net/server/gliserv.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1065 - (show annotations) (download) (as text)
Sun Nov 13 22:47:00 2005 UTC (12 years, 5 months ago) by agaffney
File MIME type: text/x-python
File size: 18201 byte(s)
just use IP instead of trying to do a reverse lookup

1 #!/usr/bin/python
2
3 import sys
4 sys.path.append("../..")
5 import os
6 import posixpath
7 import BaseHTTPServer
8 import urllib
9 import shutil
10 import mimetypes
11 from StringIO import StringIO
12 from threading import *
13 import socket
14 import SocketServer
15 import mimetools
16 import GLIServerProfile
17 import time
18 import base64
19 import traceback
20 try:
21 from SecureXMLRPCServer import SecureSocketServer
22 except:
23 pass
24
25 debug = False
26 module_mtimes = {}
27 needauth = False
28 webuser = "gli"
29 webpass = "gli"
30 progname = None
31
32 class SharedInfo(object):
33
34 __shared_state = { 'client_state': {}, 'last_visitor': "", 'clients': [], 'profiles': [], 'install_profile': None, 'client_profile':None, 'temp_use': "", 'devices':None, 'drive_to_partition':"", 'error_message': "" }
35
36 def __init__(self):
37 self.__dict__ = self.__shared_state
38
39 class Params(dict):
40
41 def __getitem__(self, item):
42 try:
43 return dict.__getitem__(self, item)
44 except KeyError:
45 return ""
46
47 class GLIHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
48
49 server_version = "GLIHTTP/0.1"
50
51 def __init__(self, request, client_address, parent):
52 self.shared_info = SharedInfo()
53 self.headers_out = []
54 BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request, client_address, parent)
55
56 def get_exception(self):
57 etype, value, tb = sys.exc_info()
58 s = traceback.format_exception(etype, value, tb)
59 content = "<pre>"
60 for line in s:
61 content += line
62 content += "</pre>"
63 return content
64
65 def wrap_in_template(self, content):
66 f = open("template.html", 'rb')
67 lines = f.readlines()
68 f.close()
69 for i in range(len(lines)):
70 if lines[i] == "Main content\n":
71 lines[i] = content
72 return "".join(lines)
73
74 def wrap_in_webgli_template(self, content):
75 f = open("webgli_template.html", 'rb')
76 lines = f.readlines()
77 f.close()
78 for i in range(len(lines)):
79 if lines[i] == "Main content\n":
80 lines[i] = content
81 return "".join(lines)
82
83 def status(self):
84 return self.wrap_in_template("This is just a prototype, fool. There isn't anything to report")
85
86 def lastvisitor(self):
87 blah = "The last visitor was " + self.shared_info.last_visitor
88 self.shared_info.last_visitor = self.client_address[0]
89 return self.wrap_in_template(blah)
90
91 def showclients(self):
92 content = """
93 <h2>Clients</h2>
94 <table width="100%" cellpadding="0" cellpadding="0">
95 <tr>
96 <td><u>Name</u></td>
97 <td><u>MAC</u></td>
98 <td><u>Current IP</u></td>
99 <td><u>Post-install IP</u></td>
100 <td><u>Profile</u></td>
101 <td><u>Install progress</u></td>
102 </tr>
103 """
104 for client in self.shared_info.clients:
105 client_info = [client['name'], client['mac'], 'N/A', client['ip'], client['profile'], 'N/A']
106 if client['mac'] in self.shared_info.client_state:
107 client_info[2] = self.shared_info.client_state[client['mac']]['ip']
108 client_info[5] = self.shared_info.client_state[client['mac']]['install_status']
109 content += """
110 <tr>
111 <td>%s</td>
112 <td>%s</td>
113 <td>%s</td>
114 <td>%s</td>
115 <td>%s</td>
116 <td>%s</td>
117 </tr>
118 """ % tuple(client_info)
119 content += """
120 </table>
121 """
122 return self.wrap_in_template(content)
123
124 def parse_path(self):
125 self.get_params = Params()
126 self.post_params = Params()
127 pathparts = self.path.split("?")
128 self.path = pathparts[0]
129 if len(pathparts) > 1:
130 args = pathparts[1]
131 for arg in args.split("&"):
132 argparts = arg.split("=")
133 name = urllib.unquote(argparts[0])
134 if len(argparts) > 1:
135 data = urllib.unquote(argparts[1])
136 if name in self.get_params:
137 if isinstance(self.get_params[name], str):
138 self.get_params[name] = [self.get_params[name]]
139 self.get_params[name].append(data)
140 else:
141 self.get_params[name] = data
142 else:
143 self.get_params[name] = ""
144 else:
145 self.args = ""
146
147 def valid_boundary(self, s, _vb_pattern="^[ -~]{0,200}[!-~]$"):
148 import re
149 return re.match(_vb_pattern, s)
150
151 def parse_content_type(self, line):
152 plist = map(lambda x: x.strip(), line.split(';'))
153 key = plist.pop(0).lower()
154 pdict = {}
155 for p in plist:
156 i = p.find('=')
157 if i >= 0:
158 name = p[:i].strip().lower()
159 value = p[i+1:].strip()
160 if len(value) >= 2 and value[0] == value[-1] == '"':
161 value = value[1:-1]
162 value = value.replace('\\\\', '\\').replace('\\"', '"')
163 pdict[name] = value
164 return key, pdict
165
166 def do_HEAD(self):
167 """Serve a HEAD request."""
168 self.do_GET(head_only=True)
169
170 def do_POST(self, head_only=False):
171 self.parse_path()
172 maxlen = 0
173 ctype = self.headers.getheader('content-type')
174 if ctype == 'application/x-www-form-urlencoded':
175 clength = self.headers.getheader('content-length')
176 if clength:
177 try:
178 bytes = int(clength)
179 except ValueError:
180 pass
181 if maxlen and clength > maxlen:
182 raise ValueError, 'Maximum content length exceeded'
183 self.args = self.rfile.read(bytes)
184 if self.args:
185 for arg in self.args.split("&"):
186 argparts = arg.split("=")
187 name = urllib.unquote(argparts[0])
188 if len(argparts) > 1:
189 data = urllib.unquote(argparts[1])
190 if name in self.post_params:
191 if isinstance(self.post_params[name], str):
192 self.post_params[name] = [self.post_params[name]]
193 self.post_params[name].append(data)
194 else:
195 self.post_params[name] = data
196 else:
197 self.post_params[name] = ""
198 else:
199 # parse_multipart in /usr/lib/python2.4/cgi.py
200 ctype, pdict = self.parse_content_type(self.headers.getheader('content-type'))
201 boundary = ""
202 if 'boundary' in pdict:
203 boundary = pdict['boundary']
204 if not self.valid_boundary(boundary):
205 raise ValueError, ('Invalid boundary in multipart form: %r' % (boundary,))
206
207 nextpart = "--" + boundary
208 lastpart = "--" + boundary + "--"
209 partdict = Params()
210 terminator = ""
211
212 while terminator != lastpart:
213 bytes = -1
214 data = None
215 if terminator:
216 # At start of next part. Read headers first.
217 headers = mimetools.Message(self.rfile)
218 clength = headers.getheader('content-length')
219 if clength:
220 try:
221 bytes = int(clength)
222 except ValueError:
223 pass
224 if bytes > 0:
225 if maxlen and bytes > maxlen:
226 raise ValueError, 'Maximum content length exceeded'
227 data = self.rfile.read(bytes)
228 else:
229 data = ""
230 # Read lines until end of part.
231 lines = []
232 while 1:
233 line = self.rfile.readline()
234 if not line:
235 terminator = lastpart # End outer loop
236 break
237 if line[:2] == "--":
238 terminator = line.strip()
239 if terminator in (nextpart, lastpart):
240 break
241 lines.append(line)
242 # Done with part.
243 if data is None:
244 continue
245 if bytes < 0:
246 if lines:
247 # Strip final line terminator
248 line = lines[-1]
249 if line[-2:] == "\r\n":
250 line = line[:-2]
251 elif line[-1:] == "\n":
252 line = line[:-1]
253 lines[-1] = line
254 data = "".join(lines)
255 line = headers['content-disposition']
256 if not line:
257 continue
258 key, params = self.parse_content_type(line)
259 if key != 'form-data':
260 continue
261 if 'name' in params:
262 name = params['name']
263 else:
264 continue
265 if name in partdict:
266 if isinstance(partdict[name], str):
267 partdict[name] = [partdict[name]]
268 partdict[name].append(data)
269 else:
270 partdict[name] = data
271 self.post_params = partdict
272 self.common_handler(head_only)
273
274 def do_GET(self, head_only=False):
275 self.parse_path()
276 self.common_handler(head_only)
277
278 def common_handler(self, head_only):
279 paths = {
280 'ProfileHandler': [ '/loadprofile', '/loadprofile2', '/saveprofile', '/saveprofile2' ],
281 'WebGLIHandler': ['/webgli', '/webgli/',
282 '/webgli/ClientConfig', '/webgli/saveclientconfig',
283 '/webgli/NetworkMounts', '/webgli/savenetmounts',
284 '/webgli/Partitioning', '/webgli/Partitioning2', '/webgli/Partitioning3', '/webgli/Partitioning4',
285 '/webgli/StageSelection', '/webgli/savestage',
286 '/webgli/PortageTree', '/webgli/saveportage',
287 '/webgli/GlobalUSE', '/webgli/saveglobaluse',
288 '/webgli/LocalUSE', '/webgli/savelocaluse',
289 '/webgli/ConfigFiles', '/webgli/saveconfigfiles',
290 '/webgli/Kernel', '/webgli/savekernel',
291 '/webgli/Bootloader', '/webgli/savebootloader',
292 '/webgli/Timezone', '/webgli/savetimezone',
293 '/webgli/Networking', '/webgli/savenetworking',
294 '/webgli/Daemons', '/webgli/savedaemons',
295 '/webgli/ExtraPackages', '/webgli/savepackages',
296 '/webgli/Services', '/webgli/saveservices',
297 '/webgli/Users', '/webgli/saveusers',
298 '/webgli/Review', '/webgli/savereview',
299 '/webgli/URIBrowser',
300 '/webgli/loadprofile', '/webgli/loadprofile2',
301 '/webgli/saveprofile', '/webgli/saveprofile2' ],
302 'Welcome': [ '/welcome' , '/showargs'],
303 'Clients': [ '/showclients' ]
304 }
305 if needauth:
306 authed = False
307 if self.headers.getheader('authorization'):
308 username, password = base64.decodestring(self.headers.getheader('authorization').split(" ")[-1]).split(":")
309 if username == webuser and password == webpass:
310 authed = True
311 if not authed:
312 self.send_response(401)
313 self.send_header("WWW-Authenticate", 'Basic realm="GLI"')
314 self.end_headers()
315 self.wfile.write("<h1>401 Not Authorized</h1>You must supply the correct username and password to access this resource")
316 return
317 return_content = ""
318 if debug:
319 print time.ctime() + " - " + self.client_address[0] + " - " + self.path
320 print "get_params: " + str(self.get_params)
321 print "post_params: " + str(self.post_params)
322 print "----------------------------------------------------"
323 for path in paths:
324 if self.path in paths[path]:
325 module = path
326 # Horrible hack until I figure out a better way to skip to sending the content
327 while 1:
328 try:
329 mtime = os.stat("handlers/" + module + ".py")[8]
330 content_module = __import__("handlers/" + module)
331 if module in module_mtimes:
332 if mtime != module_mtimes[module]:
333 reload(content_module)
334 content_module = __import__("handlers/" + module)
335 module_mtimes[module] = mtime
336 module_obj = getattr(content_module, module)(self.get_params, self.post_params, self.headers_out, self.shared_info)
337 except AttributeError:
338 return_content = "Caught %s (%s) in module. Traceback:\n%s" % (sys.exc_info()[0], sys.exc_info()[1], self.get_exception())
339 # return_content = "Unable to load module '%s'" % module
340 break
341 try:
342 function_obj = getattr(module_obj, 'handle')
343 except AttributeError:
344 return_content = "Cannot find function handle() in module '%s'" % module
345 break
346 try:
347 self.headers_out, return_content = function_obj(self.path)
348 except:
349 return_content = "Caught %s (%s) in module. Traceback:\n%s" % (sys.exc_info()[0], sys.exc_info()[1], self.get_exception())
350 break
351 break
352 # return_content = self.wrap_in_template(return_content)
353 self.send_response(200)
354 if not self.headers_out:
355 self.headers_out.append(("Content-type", "text/html"))
356 self.headers_out.append(("Content-Length", len(return_content)))
357 for header in self.headers_out:
358 self.send_header(header[0], header[1])
359 self.end_headers()
360 if not head_only:
361 self.wfile.write(return_content)
362 return
363 # No code handler...look for actual file
364 path = self.translate_path(self.path)
365 if path.endswith("/"):
366 path += "index.html"
367 ctype = self.guess_type(path)
368 try:
369 f = open(path, 'rb')
370 except IOError:
371 self.send_error(404, "File not found")
372 return None
373 filestat = os.stat(path)
374 filesize = filestat[6]
375 filemtime = filestat[8]
376 self.send_response(200)
377 self.send_header("Content-type", ctype)
378 self.send_header("Content-Length", str(filesize))
379 self.send_header("Last-Modified", time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(filemtime)))
380 self.end_headers()
381 if not head_only:
382 shutil.copyfileobj(f, self.wfile)
383 f.close()
384
385 def translate_path(self, path):
386 """Translate a /-separated PATH to the local filename syntax.
387
388 Components that mean special things to the local file system
389 (e.g. drive or directory names) are ignored. (XXX They should
390 probably be diagnosed.)
391
392 """
393 path = posixpath.normpath(urllib.unquote(path))
394 words = path.split('/')
395 words = filter(None, words)
396 path = os.getcwd() + "/html/"
397 for word in words:
398 drive, word = os.path.splitdrive(word)
399 head, word = os.path.split(word)
400 if word in (os.curdir, os.pardir): continue
401 path = os.path.join(path, word)
402 return path
403
404 def copyfile(self, source, outputfile):
405 shutil.copyfileobj(source, outputfile)
406
407 def guess_type(self, path):
408 base, ext = posixpath.splitext(path)
409 if ext in self.extensions_map:
410 return self.extensions_map[ext]
411 ext = ext.lower()
412 if ext in self.extensions_map:
413 return self.extensions_map[ext]
414 else:
415 return self.extensions_map['']
416
417 extensions_map = mimetypes.types_map.copy()
418 extensions_map.update({
419 '': 'application/octet-stream', # Default
420 '.py': 'text/plain',
421 '.c': 'text/plain',
422 '.h': 'text/plain',
423 })
424
425 def log_message(self, format, *args):
426 pass
427
428 class GLIHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
429
430 def __init__(self, server_address):
431 self.port = server_address[1]
432 SocketServer.TCPServer.__init__(self, server_address, GLIHTTPRequestHandler)
433 try:
434 class GLISecureHTTPServer(SecureSocketServer, BaseHTTPServer.HTTPServer):
435
436 def __init__(self, server_address):
437 self.port = server_address[1]
438 SecureSocketServer.__init__(self, server_address, 'server.pem', GLISecureHTTPRequestHandler)
439
440 class GLISecureHTTPRequestHandler(GLIHTTPRequestHandler):
441
442 def setup(self):
443 self.connection = self.request
444 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
445 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
446 except:
447 pass
448
449
450 class GLINetBe:
451
452 def __init__(self, loc):
453 self._path = loc
454 self.shared_info = SharedInfo()
455
456 def register_client(self, mac, ip):
457 self.shared_info.client_state[mac] = { 'ip': ip, 'install_status': "waiting for server", 'start_install': True }
458 for client in self.shared_info.clients:
459 if client['mac'] == mac: break
460 else:
461 self.shared_info.clients.append({ 'name': "", 'ip': "", 'mac': mac, 'profile': "" })
462 return True
463
464 def get_client_config(self, mac):
465 if not self.shared_info.client_state[mac]['start_install']:
466 return ""
467 for client in self.shared_info.clients:
468 if client['mac'] == mac:
469 if not client['profile']:
470 return None
471 for profile in self.shared_info.profiles:
472 if profile['name'] == client['profile']:
473 tmpfile = open(profile['ccxmlfile'], "r")
474 xml = "".join(tmpfile.readlines())
475 tmpfile.close()
476 return xml
477 return ""
478
479 def get_install_profile(self, mac):
480 if not self.shared_info.client_state[mac]['start_install']:
481 return ""
482 for client in self.shared_info.clients:
483 if client['mac'] == mac:
484 if not client['profile']:
485 return None
486 for profile in self.shared_info.profiles:
487 if profile['name'] == client['profile']:
488 tmpfile = open(profile['ipxmlfile'], "r")
489 xml = "".join(tmpfile.readlines())
490 tmpfile.close()
491 return xml
492 return ""
493
494 def update_client_status(self, mac, status):
495 self.shared_info.client_state[mac]['install_status'] = status
496 return True
497
498 def is_alive(self):
499 return True
500
501 def register():
502 host = ''
503 port = 8001
504 buf = 1024
505 addr = (host,port)
506 # Create socket and bind to address
507 UDPSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
508 UDPSock.bind(addr)
509 UDPSock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
510
511 # Receive messages
512 while 1:
513 data, fromaddr = UDPSock.recvfrom(buf)
514 if not data:
515 print "Client has exited!"
516 # break #Serve forever
517 continue
518 else:
519 print "\nReceived message '" + data + "' from " + fromaddr[0] + ":" + str(fromaddr[1])
520
521 if data == "GLIAutoInstall":
522 data = "GLIAutoInstall version 1.0"
523 else:
524 data = "This is the GLIAutoInstall Service. Please make you application aware of this."
525 UDPSock.sendto(data, (fromaddr[0], fromaddr[1]))
526
527 # Close socket
528 UDPSock.close()
529 sys.exit(0)
530 return pid
531
532 def start_httpd():
533 server_address = ('', 8000)
534 try:
535 httpd = GLISecureHTTPServer(server_address)
536 except:
537 print "Couldn't do HTTPS for web server, falling back to HTTP..."
538 httpd = GLIHTTPServer(server_address)
539 httpd.serve_forever()
540
541 def start_xmlrpc():
542 try:
543 from SecureXMLRPCServer import SecureXMLRPCServer
544 server = SecureXMLRPCServer(('', 8002), 'server.pem')
545 except:
546 print "Couldn't do HTTPS for XMLRPC, falling back to HTTP..."
547 from SimpleXMLRPCServer import SimpleXMLRPCServer
548 server = SimpleXMLRPCServer(('', 8002))
549 server.register_introspection_functions()
550 server.register_instance(GLINetBe("/tmp"))
551 server.serve_forever()
552
553 def usage():
554 print
555 print "Usage:"
556 print " " + progname + " [-h|--help] [[-d|--debug]] [[-a|--auth] <user>:<password>]"
557 print
558 print "Options:"
559 print " -h|--help That should be quite obvious"
560 print
561 print " -d|--debug Enable debug mode. This currently prints get_params and"
562 print " post_params for each request"
563 print
564 print " -a|--auth Require basic HTTP auth to access the web interface. The"
565 print " arguments <user> and <password> are required."
566 sys.exit(1)
567
568 if __name__ == '__main__':
569 progname = sys.argv.pop(0)
570 while len(sys.argv) >= 1:
571 arg = sys.argv.pop(0)
572 if arg == "-d" or arg == "--debug":
573 debug = True
574 elif arg == "-a" or arg == "--auth":
575 if len(sys.argv) >= 1:
576 auth = sys.argv.pop(0)
577 auth = auth.split(":")
578 if not len(auth) == 2:
579 print "The authentication credentials must be specified at <user>:<password>"
580 usage()
581 webuser, webpass = auth
582 needauth = True
583 else:
584 print "The --auth option required an additional argument"
585 usage()
586 elif arg == "-h" or arg == "--help":
587 usage()
588 else:
589 print "You have supplied an invalid argument"
590 usage()
591 httpd_thread = Thread(target=start_httpd)
592 httpd_thread.setDaemon(True)
593 httpd_thread.start()
594 xmlrpc_thread = Thread(target=start_xmlrpc)
595 xmlrpc_thread.setDaemon(True)
596 xmlrpc_thread.start()
597 register()

Properties

Name Value
svn:eol-style native
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.20